多多色-多人伦交性欧美在线观看-多人伦精品一区二区三区视频-多色视频-免费黄色视屏网站-免费黄色在线

國內最全IT社區平臺 聯系我們 | 收藏本站
阿里云優惠2
您當前位置:首頁 > 互聯網 > 緩沖區溢出分析第05課:編寫通用的ShellCode

緩沖區溢出分析第05課:編寫通用的ShellCode

來源:程序員人生   發布時間:2015-06-18 09:29:31 閱讀次數:4306次

前言

        我們這次的實驗所要研究的是如何編寫通用的ShellCode。可能大家會有疑惑,我們上次所編寫的ShellCode已能夠很好地完成任務,哪里不通用了呢?其實這就是由于我們上次所編寫的ShellCode,是采取“硬編址”的方式來調用相應API函數的。也就是說,我們需要首先獲得所要使用函數的地址,然后將該地址寫入ShellCode,從而實現調用。這類方式對所有的函數,通用性都是相當地差,試想,如果系統的版本變了,那末很多函數的地址常常都會產生變化,那末調用肯定就會失敗了。所以本次的課程主要討論如何在ShellCode中動態地尋覓相干API函數的地址,從而解決通用性的問題。

 

計算函數名稱的hash值

        這里可以首先總結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值的程序以下:

#include <stdio.h> #include <windows.h> DWORD GetHash(char *fun_name) { DWORD digest = 0; while(*fun_name) { digest = ((digest << 25) | (digest >> 7 )); digest += *fun_name; fun_name++; } return digest; } int main() { DWORD hash; hash = GetHash("MessageBoxA"); printf("The hash of MessageBoxA is 0x%.8x ", hash); hash = GetHash("ExitProcess"); printf("The hash of ExitProcess is 0x%.8x ", hash); hash = GetHash("LoadLibraryA"); printf("The hash of LoadLibraryA is 0x%.8x ", hash); getchar(); return 0; }
        運行結果以下:


圖1

        可見,通過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值,請大家注意匯編的這類寫法。

 

獲得kernel32.dll的地址

        由于我們需要動態獲得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

        綜合以上,可以編寫匯編代碼為:

mov ebx,fs:[edx+0x30] // [TEB+0x30]是PEB的位置 mov ecx,[ebx+0xC] // [PEB+0xC]是PEB_LDR_DATA的位置 mov ecx,[ecx+0x1C] // [PEB_LDR_DATA+0x1C]是InInitializationOrderModuleList的位置 mov ecx,[ecx] // 進入鏈表第1個就是ntdll.dll mov ebp,[ecx+0x8] // ebp保存的是kernel32.dll的基地址
        這樣就實現了動態獲得kernel32.dll的地址:


圖9

 

解析kernel32.dll的導出表

        既然已找到了kernel32.dll,由于它也是屬于PE文件,那末我們可以根據PE文件的結構特點,對其導出表進行解析,不斷遍歷搜索,從而找到我們所需要的API函數。其步驟以下:

        (1)從kernel32.dll加載基址算起,偏移0x3c的地方就是其PE頭。

        (2)PE頭偏移0x78的地方寄存著指向函數導出表的指針。

        (3)至此,可以按以下方式在函數導出表中算出所需函數的入口地址:

        ● 導出表偏移0x1c處的指針指向存儲導出函數偏移地址(RVA)的列表。

        ● 導出表偏移0x20處的指針指向存儲導出函數函數名的列表。

        ● 函數的RVA地址和名字依照順序寄存在上述兩個列表中,我們可以在名稱列表中定位到所需的函數是第幾個,然后在地址列表中找到對應的RVA。

        ● 取得RVA后,再加上前邊已得到的動態鏈接庫的加載地址,就取得了所需API此刻在內存中的虛擬地址,這個地址就是我們終究在ShellCode中調用時需要的地址。

        依照這個方法,就能夠取得kernel32.dll中的任意函數。

// ==== 在PE文件中查找相應的API函數 ==== find_functions: pushad // 保護所有寄存器中的內容 mov eax,[ebp+0x3C] // PE頭 mov ecx,[ebp+eax+0x78] // 導出表的指針 add ecx,ebp mov ebx,[ecx+0x20] // 導出函數的名字列表 add ebx,ebp xor edi,edi // 清空edi中的內容,用作索引 // ==== 循環讀取導出表函數 ==== next_function_loop: inc edi // edi不斷自增,作為索引 mov esi,[ebx+edi*4] // 從列表數組中讀取 add esi,ebp // esi保存的是函數名稱所在地址 cdq // 把edx的每位置成eax的最高位,再把edx擴大為eax的高位,即變成64位
        截圖以下:


圖10

        至此,所有匯編代碼編寫終了。利用VC生成可履行文件,運行結果以下:


圖11

        下面就是ShellCode的提取。

 

提取ShellCode

        這次我使用OD進行提取,并利用UE對其進行編輯。首選需要在OD中找到我們所編寫的代碼的位置:


圖12

        然后將這些代碼全部提取出來,可保存為txt文件格式,然后使用UE的“列塊模式“,就可以輕松對其編輯:


圖13

        這樣就能夠生成我們所需要的ShellCode了。

 

ShellCode的使用

        我們這次所生成的ShellCode比較長,所以雖然我們這次已得到了1段具有跨平臺、硬朗性、穩定性、通用性等各方面比較優秀的ShellCode,但是不見得能夠用于所有的緩沖區溢出的情況。比如如果直接將這個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部份就先講授到這里。

生活不易,碼農辛苦
如果您覺得本網站對您的學習有所幫助,可以手機掃描二維碼進行捐贈
程序員人生
------分隔線----------------------------
分享到:
------分隔線----------------------------
關閉
程序員人生
主站蜘蛛池模板: 2017琪琪理论影院 | 99精品国产一区二区三区 | 最近的中文字幕免费完整 | 国产午夜精品一区二区三区不卡 | v片免费观看 | 日本v视频| 91最新地址永久入口 | 性欧美精品videofree高清hd | 欧美洲久久日韩欧美 | 最近免费中文字幕大全视频 | 三级全黄在线观看www桃花 | 亚洲天堂v | 欧美毛片 | 国产成人精品免费视频网页大全 | 亚洲成年人影院 | 国产精品爱久久久久久久 | 亚洲欧美日韩精品久久 | 成人网在线观看 | 视频在线观看免费视频 | 欧美一级精品高清在线观看 | 欧美一级毛片一级 | 波多野结衣99 | freesex欧美 | 国产欧美日韩在线观看一区二区三区 | 日本欧美一区二区三区 | 欧美一级毛片日本 | 国产精品福利社 | 亚洲一区二区三区精品国产 | 性欧美与印度人xxx 性欧美孕妇xxxx | 伊人焦| 国产成人免费a在线资源 | 国产真人毛片一级视频 | 精品一区二区三区在线观看 | 亚洲精品国产第一区第二区国 | 伊人五月天综合 | 午夜免费体验 | 精品视频一区二区三区四区五区 | 精品久久久久久国产免费了 | 性感美女视频免费网站午夜 | 成人性毛片| 久久国产精品免费 |