2020gslabCTF 初赛 PC方向wp

  • A+

题目链接

https://gslab.qq.com/html/competition/2020/race-pre.htm

RIng3

题目

winmine.exe是一个扫雷游戏程序,winmine.dmp是该程序的一份进程dump, 在这份dump中,winmine.exe的内存映像有指令被篡改,篡改实现了外挂功能。

  • 请找出dump中,winmine.exe的内存映像中2处被篡改实现外挂功能的指令(被篡改指令的偏移、篡改前后的指令分别是什么),并分析这些指令篡改所实现的外挂功能是什么

猜测

由于是扫雷外挂,无非是修改时间、修改判定条件,修改雷区等,因此针对这几个函数进行对比即可知道修改处

修改效果

时间不动,点雷不爆炸

image-20200403190843203.png

第一处修改 偏移0x2fe0

image-20200403170447857.png

image-20200403170600061.png

原指令:inc time,就是把时间+1,修改后时间停止不动

第二处修改 0x358b

image-20200403190142473.png

image-20200403190248675.png

原指令压入0,调用sub_100347C,被修改位为jmp winmine+0x35b0 (010035b0),直接跳过爆炸,效果即是点雷也不会结束游戏

Ring0

题目

DriverDemo.sys是一个驱动程序,它内置了一些限制。

  • 不能篡改该文件,尝试使驱动成功加载。(3分)

  • 该驱动程序成功加载后,突破它的限制,但不允许patch文件或内存,使它成功打印出(用dbgview可接受)调试信息"hello world!".

参考

pizza大佬太强了!

前期准备

DbgView

需要设置Capture->Capture Kernel才能捕获到@hello world

UTOOLS1586191209505.png

虚拟机设置

必须设为单核单线程,不然会出现以下情况(T为单步步入调试)

UTOOLS1586236591474.png

过TP反双机调试

ew KdDisableDebugger 0xc390

知识点

x64 调用约定

64位平台下只有一种变形的__fastcall的调用约定,前4参数则先放入ecx、edx、r8、r9寄存器,更多的参数放入栈区

官方文档

MmGetSystemRoutine

返回指向系统程序名称指定的函数的指针

语法

c++
PVOID MmGetSystemRoutineAddress(
PUNICODE_STRING SystemRoutineName
);

参数SystemRoutineName需要返回地址的函数名

KdDisableDebugger

KdDisableDebugger例程禁用内核调试器

_UNICODE_STRING

c
typedef struct _UNICODE_STRING
{
USHORT Length;
USHORT MaximumLength;
PWCHAR Buffer;
}UNICODE_STRING,*PUNICODE_STRING;

ZwOpenKey

ZwOpenKey例程打开现有注册表项

语法

c++
NTSYSAPI NTSTATUS ZwOpenKey(
PHANDLE KeyHandle,
ACCESS_MASK DesiredAccess,
POBJECT_ATTRIBUTES ObjectAttributes
);

其中:

KeyHandle 指向接收密钥句柄的 HANDLE 变量的指针
DesiredAccess 定确定请求对对象的访问的ACCESS_MASK值
ObjectAttributes 指向指定对象名称和其他属性的OBJECT_ATTRIBUTES结构的指针。

ObjectAttributes

c++
typedef struct _OBJECT_ATTRIBUTES
{
ULONG Length;
PVOID RootDirectory;
PUNICODE_STRING ObjectName;
ULONG Attributes;
PVOID SecurityDescriptor;
PVOID SecurityQualityOfService;
} OBJECT_ATTRIBUTES, *POBJECT_ATTRIBUTES;

ZwQueryValueKey

例程返回注册表项的值条目

语法

NTSYSAPI NTSTATUS ZwQueryValueKey(
HANDLE KeyHandle,
PUNICODE_STRING ValueName,
KEY_VALUE_INFORMATION_CLASS KeyValueInformationClass,
PVOID KeyValueInformation,
ULONG Length,
PULONG ResultLength
);

其中

```
KeyHandle

处理从中读取值条目的键。此句柄是通过成功调用ZwCreateKey或ZwOpenKey创建的。

ValueName

指向值条目的名称以获取其数据的指针。

ResultLength

指向接收密钥信息的大小(以字节为单位)的变量的指针。如果ZwQueryValueKey例程返回STATUS_SUCCESS,则调用方可以使用此变量的值来确定返回的数据量。如果例程返回STATUS_BUFFER_OVERFLOW或STATUS_BUFFER_TOO_SMALL,调用方可以使用此变量的值来确定保存密钥信息所需的缓冲区大小。
```

IoCreateNotifyEvent

IoCreateNotifyEvent例程创建或打开命名通知事件,用于通知一个或多个执行线程事件已发生

语法

PKEVENT IoCreateNotificationEvent(
PUNICODE_STRING EventName,
PHANDLE EventHandle
);

KeResetEven

KeResetEvent 例程将指定的事件对象重置为未发出信号状态,并返回该事件对象的上一个状态

语法

LONG KeResetEvent(
PRKEVENT Event
);

PsCreateSystemThread

PsCreateSystemThread例程创建一个系统线程,该线程在内核模式下执行并返回线程的句柄

语法

c++
NTSTATUS PsCreateSystemThread(
PHANDLE ThreadHandle,
ULONG DesiredAccess,
POBJECT_ATTRIBUTES ObjectAttributes,
HANDLE ProcessHandle,
PCLIENT_ID ClientId,
PKSTART_ROUTINE StartRoutine,
PVOID StartContext
);

```
StartRoutine 线程中要执行的函数

StartContext 传递给上述要执行的函数的参数
```

DbgPrint

DbgPrint例程向内核调试器发送消息

KeWaitForSingleObject

KeWaitForSingleObject例程将当前线程置于等待状态,直到给定调度程序对象设置为信号状态或(可选)直到等待超时

语法

c++
NTSTATUS KeWaitForSingleObject (
PVOID Object,
KWAIT_REASON WaitReason,
KPROCESSOR_MODE WaitMode,
BOOLEAN Alertable,
PLARGE_INTEGER Timeout
);

Object 指向初始化调度程序对象(事件、互斥、信号量、线程或计时器)的指针,调用方为其提供存储。

WaitReason 为6的意思
UserRequest 6 线程正在等待用户请求。

分析

反调试

IDA打开sys,发现加了vmp2壳,查看导入表,还是还是有一些导入表的

UTOOLS1586336559545.png

双机调试,由于驱动没有签名,所以需要开启禁用驱动程序强制签名,接着用Driver Monitor加载驱动,断在MmGetSystemRoutineAddress(断点需要提前设置)

1: kd> bp MmGetSystemRoutineAddress
1: kd> g
Breakpoint 0 hit
nt!MmGetSystemRoutineAddress:
fffff803`95a50510 4053 push rbx

查看参数

1: kd> dt _UNICODE_STRING @rcx
ntdll!_UNICODE_STRING
"KdDisableDebugger"
+0x000 Length : 0x22
+0x002 MaximumLength : 0x24
+0x008 Buffer : 0xffff828a`9df7f7c0 "KdDisableDebugger"

发现调用了KdDisableDebugger反调试函数,断在KdDisableDebugger,直接将KdDisableDebugger改为ret 跳过反调试

1: kd> ew KdDisableDebugger 0xc390
1: kd> bp KdDisableDebugger
1: kd> g
Breakpoint 1 hit
nt!KdDisableDebugger:
fffff803`95620ac0 90 nop
1: kd> u KdDisableDebugger KdDisableDebugger+1
nt!KdDisableDebugger:
fffff803`95620ac0 90 nop
fffff803`95620ac1 c3 ret

也可以采用Pizza大佬的方法,直接修改rip绕过反调试

1: kd> r rip=poi(rsp)+11
1: kd> u @rip @rip
fffff803`94a11158 c3 ret

读取注册表

接着单步跟进,一直到发现有大量的赋值操作。

1: kd> u fffff802`eb8d1189 fffff802`eb8d1189+0x500
fffff802`eb8d1189 b85c000000 mov eax,5Ch
fffff802`eb8d118e 66898424b0000000 mov word ptr [rsp+0B0h],ax
fffff802`eb8d1196 b852000000 mov eax,52h
fffff802`eb8d119b 66898424b2000000 mov word ptr [rsp+0B2h],ax
fffff802`eb8d11a3 b845000000 mov eax,45h
fffff802`eb8d11a8 66898424b4000000 mov word ptr [rsp+0B4h],ax
fffff802`eb8d11b0 b847000000 mov eax,47h
fffff802`eb8d11b5 66898424b6000000 mov word ptr [rsp+0B6h],ax

跳过这段以后,由于基本是可见字符,看一下字符串是什么:

1: kd> du @rsp+A0
ffffa889`49b0f720 "key"
1: kd> du @rsp+B0 @rsp+158
ffffa889`49b0f730 "REGISTRYMACHINESOFTWAREAppDa"
ffffa889`49b0f770 "taLowTencent{61B942F7-A946-458"
ffffa889`49b0f7b0 "5-B624-B2C0228FFEBC}"

既为:

```
key

REGISTRYMACHINESOFTWAREAppDataLowTencent{61B942F7-A946-4585-B624-B2C0228FFEBC}
```

看着像个注册表的位置,继续跟踪进入ZwOpenKey

1: kd> t
nt!ZwOpenKey:
fffff801`d1245300 488bc4 mov rax,rsp
1: kd> dt _OBJECT_ATTRIBUTES @r8
nt!_OBJECT_ATTRIBUTES
+0x000 Length : 0x30
+0x008 RootDirectory : (null)
+0x010 ObjectName : 0xfffff98d`b9e9f6e0 _UNICODE_STRING "REGISTRYMACHINESOFTWAREAppDataLowTencent{61B942F7-A946-4585-B624-B2C0228FFEBC}"
+0x018 Attributes : 0x240
+0x020 SecurityDescriptor : (null)
+0x028 SecurityQualityOfService : (null)

既用ZwOpenKey例程打开现有注册表项

由于我们没有所以要新建一个

UTOOLS1586100165423.png

打开成功后,用ZwQueryValueKey例程返回注册表项key的值条目

1: kd> g
Breakpoint 4 hit
nt!ZwQueryValueKey:
fffff801`d12453a0 488bc4 mov rax,rsp
1: kd> dt _UNICODE_STRING @rdx
nt!_UNICODE_STRING
"key"
+0x000 Length : 6
+0x002 MaximumLength : 8
+0x008 Buffer : 0xfffff98d`b9e9f720 "key"

那么我们新建key = 0xA以供读取

UTOOLS1586165085138.png

1: kd> r @rax
rax=000000000000000a

查看返回值,可以发现程序已读取了我们的值

UTOOLS1586165586955.png

即为读取了注册表项 REGISTRYMACHINESOFTWAREAppDataLowTencent{61B942F7- A946-4585-B624-B2C0228FFEBC} 的键 key 的值, 不为1则驱动加载失败

我们回到注册表把key的值改为1,驱动成功加载,但是没有输出@helloworld,继续跟踪

UTOOLS1586174188587.png

事件设置

驱动加载成功后创建Event,名称为tp2020

1: kd> g
Breakpoint 2 hit
nt!IoCreateNotificationEvent:
fffff801`e26fa6f0 488bc4 mov rax,rsp
1: kd> dt _UNICODE_STRING @rcx
ntdll!_UNICODE_STRING
"BaseNamedObjectstp2020"
+0x000 Length : 0x30
+0x002 MaximumLength : 0x32
+0x008 Buffer : 0xfffff803`03092140 "BaseNamedObjectstp2020"

根据函数返回指向事件的指针,查看该指针

0: kd> pt
nt!KeResetEvent:
fffff801`e26fa78d c3 ret
1: kd> r @rax
rax=ffffdb8ede8c9fe0

接着把这个事件重置

1: kd> g
Breakpoint 5 hit
nt!KeResetEvent:
fffff801`e21d39e0 48895c2410 mov qword ptr [rsp+10h],rbx
1: kd> r @rcx
rcx=ffffdb8ede8c9fe0

接着创建线程,查看执行函数(StartRoutine)

1: kd> g
Breakpoint 4 hit
nt!PsCreateSystemThread:
fffff803`0093f9f0 4c8bdc mov r11,rsp
kd> u poi(@rsp+8*6)
fffff802`06d91c70 48894c2408 mov qword ptr [rsp+8],rcx
fffff802`06d91c75 4883ec28 sub rsp,28h
fffff802`06d91c79 488b4c2430 mov rcx,qword ptr [rsp+30h]
fffff802`06d91c7e e81d000000 call fffff802`06d91ca0
fffff802`06d91c83 eb00 jmp fffff802`06d91c85
fffff802`06d91c85 33c9 xor ecx,ecx
fffff802`06d91c87 e83d720000 call fffff802`06d98ec9
fffff802`06d91c8c cc int 3

调用KeWaitForSingleObject等待之前的Event,如果Event被设置则打印helloworld

UTOOLS1586247107286.png

用process hacker打开system进程,找到句柄中名称tp2020的Event,直接设置,然后在DbgView看到helloworld

UTOOLS1586191111351.png

相关推荐: 对某大型企业的一次web漏洞挖掘过程

注:本文相关漏洞均已报告 SRC 并完成修复 前言 近几年工作转型,已经很久没挖过漏洞,突然想检验一下自己当前是否还有挖洞能力。因此本次以挖到某个大型厂商一个中危级别以上漏洞作为目标,最终挖到了一个低危 (存储 xss ) + 一个高危 (任意文件读取) ,完…