我們這次的實驗所要研究的是如何編寫通用的ShellCode。可能大家會有疑惑,我們上次所編寫的ShellCode已能夠很好地完成任務,哪里不通用了呢?其實這就是由于我們上次所編寫的ShellCode,是采取“硬編址”的方式來調用相應API函數的。也就是說,我們需要首先獲得所要使用函數的地址,然后將該地址寫入ShellCode,從而實現調用。這類方式對所有的函數,通用性都是相當地差,試想,如果系統的版本變了,那末很多函數的地址常常都會產生變化,那末調用肯定就會失敗了。所以本次的課程主要討論如何在ShellCode中動態地尋覓相干API函數的地址,從而解決通用性的問題。
這里可以首先總結1下我們將要用到的函數。
首先為了顯示對話框,需要使用MessageBoxA這個函數,它位于user32.dll里面。為了使用這個動態鏈接庫,還需要使用LoadLibraryA來讀取這個DLL文件,而LoadLibraryA又位于kernel32.dll中。由于所有的Win32程序都會自動加載kernel32.dll,因此這里我們無需再使用LoadLibraryA來加載kernel32.dll。最后為了正常退出程序,還需要使用ExitProcess,它一樣位于kernel32.dll里面。
由于ShellCode終究是要放進緩沖區的,為了使得ShellCode更加通用,能被大多數緩沖區容納,我們總是希望ShellCode盡量地短小精悍。因此我們在系統中搜索函數名的時候,1般情況下其實不會使用諸如“LoadLibraryA”這么長的字符串直接進行比較查找。而是首先會對函數名進行hash運算,而在系統中搜索所要使用的函數時,也會先對系統中的函數進行hash運算,這樣只需要比較2者的hash值就可以夠判定目標函數是否是我們想要查找的了。雖然這樣會引入額外的hash算法,但是卻可以節省出存儲函數名字的空間。
計算以上3個API函數的hash值的程序以下:
可見,通過hash算法,我們能夠將任意長度的函數名稱變成4個字節(DWORD)的長度。
這里給大家簡單分析1下上述hash值的計算方法。假定現在有1個函數,名為“AB”,然后調用GetHash函數:
hash =GetHash("AB");
進入GetHash函數,它會將函數名稱中的字符1個1個地分別取出進行計算,有幾個字符就循環計算幾次。首先是第1次循環,取出字符“A”,然后有:
digest= ((digest << 25) | (digest >> 7 ));
這里由于digest在上面被賦值為0,且為DWORD類型,因此這里不管怎樣計算,它的值都是0。然后計算:
digest+= *fun_name;
此時的digest是0,*fun_name保存的是第1個字符“A”,它們相加也就是ASCII碼值的相加,結果就是digest的值為“00000000 0000000000000000 01000001”。然后履行語句:
fun_name++;
令指針指向第2個字符“B”,從而進入第2次循環。首先計算:
digest= ((digest << 25) | (digest >> 7 ));
首先將digest左移25位,即“10000010 0000000000000000 00000000”,然后將其右移7位,即“10000010 00000000 00000000 00000000”,然后江這兩個值做“或”運算,則digest的值為“10000010 0000000000000000 00000000”。事實上,上述語句的目的是實現digest的循環右移7位(或循環左移25位),由于C語言沒有直接實現循環移位的運算符號,因此只能通過這類方式運算。然后計算:
digest+= *fun_name;
也就是將digest的值加上“B”的ASCII碼值,結果為“1000001000000000 00000000 01000010”,這也就是終究的運算結果,以106進制顯示就是0x82000042。
下面就能夠編寫匯編代碼,首先是讓函數的hash值入棧:
push 0x1e380a6a ; MessageBoxA的hash值
push 0x4fd18963 ; ExitProcess的hash值
push 0x0c917432 ; LoadLibraryA的hash值
mov esi,esp ; esi保存的是棧頂第1個函數,即LoadLibraryA的hash值
然后編寫用于計算hash值的代碼:
hash_loop:
movsx eax,byte ptr[esi] // 每次取出1個字符放入eax中
cmp al,ah // 驗證eax是不是為0x0,即結束符
jz compare_hash // 如果上述結果為零,說明hash值計算終了,則進行hash值的比較
ror edx,7 // 如果cmp的結果不為零,則進行循環右移7位的操作
add edx,eax // 將循環右移的值不斷累加
inc esi // esi自增,用于讀取下1個字符
jmp hash_loop // 跳到hash_loop的位置繼續計算
這樣通過循環,就可以夠計算出函數名稱的hash值,請大家注意匯編的這類寫法。
由于我們需要動態獲得LoadLibraryA()和ExitProcess()這兩個函數的地址,而這兩個函數又是存在于kernel32.dll中的,因此這里需要先找到kernel32.dll的地址,然后通過對其進行解析,從而查找那兩個函數。
所有的Win32程序都會自動加載ntdll.dll和kernel32.dll這兩個最基礎的動態鏈接庫。因此如果想要在 Win32平臺下定位kernel32.dll中的API地址,可使用以下方法(這里結合WinDbg來給大家演示):
(1)通過段選擇字FS在內存中找到當前的線程環境塊TEB。這里可以利用本地調試,輸入”!teb”指令:
圖2
(2)線程環境塊偏移位置為0x30的地方寄存著指向進程環境塊PEB的指針。結合上圖可見,PEB的地址為0x7ffde000。
(3)進程環境塊中偏移位置為0x0c的地方寄存著指向PEB_LDR_DATA結構體的指針,其中,寄存著已被進程裝載的動態鏈接庫的信息。
圖3
(4)PEB_LDR_DATA結構體偏移位置為0x1c的地方寄存著指向模塊初始化鏈表的頭指針InInitializationOrderModuleList。
圖4
(5)模塊初始化鏈表InInitializationOrderModuleList中按順序寄存著PE裝入運行時初始化模塊的信息,第1個鏈表節點是ntdll.dll,第2個鏈表結點就是kernel32.dll。比如可以先看看InInitializationOrderModuleList中的內容:
圖5
這里的0x00191f28保存的是第1個鏈節點的指針,解析1下這個結點:
圖6
然后繼續解析,查看第2個結點:
圖7
可見第2個節點偏移0x08個字節正是kernel32.dll,其地址為0x7c800000。如果不放心,可以驗證1下:
圖8
綜合以上,可以編寫匯編代碼為:
既然已找到了kernel32.dll,由于它也是屬于PE文件,那末我們可以根據PE文件的結構特點,對其導出表進行解析,不斷遍歷搜索,從而找到我們所需要的API函數。其步驟以下:
(1)從kernel32.dll加載基址算起,偏移0x3c的地方就是其PE頭。
(2)PE頭偏移0x78的地方寄存著指向函數導出表的指針。
(3)至此,可以按以下方式在函數導出表中算出所需函數的入口地址:
● 導出表偏移0x1c處的指針指向存儲導出函數偏移地址(RVA)的列表。
● 導出表偏移0x20處的指針指向存儲導出函數函數名的列表。
● 函數的RVA地址和名字依照順序寄存在上述兩個列表中,我們可以在名稱列表中定位到所需的函數是第幾個,然后在地址列表中找到對應的RVA。
● 取得RVA后,再加上前邊已得到的動態鏈接庫的加載地址,就取得了所需API此刻在內存中的虛擬地址,這個地址就是我們終究在ShellCode中調用時需要的地址。
依照這個方法,就能夠取得kernel32.dll中的任意函數。 至此,所有匯編代碼編寫終了。利用VC生成可履行文件,運行結果以下:
圖11
下面就是ShellCode的提取。
圖12
然后將這些代碼全部提取出來,可保存為txt文件格式,然后使用UE的“列塊模式“,就可以輕松對其編輯:
圖13
這樣就能夠生成我們所需要的ShellCode了。
圖14
當程序履行到0x00401511處的時候,就會卡住了,這條語句位于strcpy()中,作用是將我們所編寫的ShellCode拷貝到緩沖區中,而接下來要拷貝的,就是EDX中的“3C8B66DD“,需要拷貝到0x00130000這個位置。但是由于0x0012FFFF為系統默許的棧的底端,我們沒法超出這個位置繼續拷貝,因而我們的棧溢出利用就失敗了。那末計算1下,我們這個程序允許我們使用的棧空間的長度為0x0012FFFF減去0x0012FF78,也就是136個字節,超過了就會利用失敗。所以從這個角度來講,我們還需要精簡我們的ShellCode,或采取其他的方式,使得我們的代碼能夠得到履行。
這里我們首先將buffer的空間修改成256個字節,然后修改我們上文中所生成的ShellCode,這里的修改主要是用x90將buffer空間和EBP填充滿,然后將返回地址修改成0x0012FE80,也就是系統為buffer分配的首地址。其原理就是我們正經常使用ShellCode填充buffer,將返回地址覆蓋為buffer首地址,這樣函數返回時,就可以夠履行我們的ShellCode了:
圖15
至此,ShellCode部份就先講授到這里。