栈溢出笔记1.6 地址问题(1)
前面的Shellcode中,我使用的都是自己XP機器上的硬編碼地址。任何時候在Shellcode中使用硬編碼地址都不是個好主意,這一點與動態庫的重定位類似,一旦系統環境和程序編譯設置發生變化,Shellcode幾乎肯定會失效。因此,我們要找到更好一點的方法。
前面的Shellcode中,我用到了如下幾個硬編碼地址,它們的含義如下:?
?
其中,LoadLibraryA的作用比較特殊,我們用它來加載user32.dll庫。
現在我們要換掉這些硬編碼地址。那么,如何得到這些API函數的地址呢?在動態鏈接庫中獲取函數地址有一個專門的函數——GetProcAddress,這個函數的原型如下:
/*****************************************************************************/ FARPROC WINAPI GetProcAddress(_In_ HMODULE hModule,_In_ LPCSTR lpProcName ); /*****************************************************************************/- 1
- 2
- 3
- 4
- 5
- 6
這個函數第一個參數為模塊的句柄,可以調用LoadLibraryA獲得,第二個參數為函數名稱。這樣的話,MessageBoxA和ExitProcess都可以使用這種方式來獲取。但是這依賴于兩個函數:LoadLibraryA和GetProcAddress,那這兩個函數的地址又怎么得到呢?
這兩個函數都位于kernel32.dll,kernel32.dll肯定會加載,不需要我們自己加載。因此,現在的問題就是如何從kernel32.dll中找到LoadLibraryA和GetProcAddress的地址?
接下來就需要一點Windows內核和PE文件格式的知識了。我們知道,kernel32.dll為PE格式的動態鏈接庫,要導出的函數放在PE文件的導出表中,因此,為了獲取LoadLibraryA和GetProcAddress的地址,我們需要手動解析kernel32.dll的導出表。但在這之前,我們需要知道kernel32.dll在內存中的位置,也就是kernel32.dll的基地址。因此,問題總結為以下:?
(1)如何獲取kernel32.dll的基地址??
(2)如何在kernel32.dll的導出表中找到LoadLibraryA和GetProcAddress的地址?
先來解決第一個問題。我們知道,一個進程運行的時候,除了加載exe文件外,所依賴的.dll也會映射到進程的虛擬地址空間中,那么,這些加載的dll也是進程的財產,因此,進程會保存它們的信息。進程的信息都保存在PEB結構中,其中的Ldr(偏移為0xc0)指向一個PEB_LDR_DATA結構,這個結構保存的進程已加載模塊的信息。這個結構如下:
/*****************************************************************************/ typedef struct _PEB_LDR_DATA {ULONG Length; // +0x00BOOLEAN Initialized; // +0x04PVOID SsHandle; // +0x08LIST_ENTRY InLoadOrderModuleList; // +0x0cLIST_ENTRY InMemoryOrderModuleList; // +0x14LIST_ENTRY InInitializationOrderModuleList; // +0x1c } PEB_LDR_DATA,*PPEB_LDR_DATA; // +0x24 /*****************************************************************************/- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
Windows并未完全公開(文檔化)此結構。這是網上的版本,我們也可以通過Windbg來得到,這是我XP SP3機器上的該結構:?
?
圖37
這個結構的重點在于后面三個鏈表:InLoadOrderModuleList、InMemoryOrderModuleList和InInitializationOrderModuleList。從名稱上看,這三個隊列都是模塊鏈表,第一個是按加載的先后順序,第二個是按在虛擬空間中的位置,第三個是按初始化的順序。第二個容易理解,但是第一個和第三個有什么區別呢?加載是先于初始化的,加載就是完成虛擬空間的映射和與exe的鏈接,加載完成后的DLL會掛入InInitializationOrderModuleList,進行初始化,初始化就是調用其DLLMain。可以看到的一點是InLoadOrderModuleList中有exe本身的模塊,而InInitializationOrderModuleList中只有exe依賴的DLL。(當然,這隨著系統版本在發生變化)
我們要使用的是InInitializationOrderModuleList,掛入該鏈表中的為LDR_DATA_TABLE_ENTRY,也就是說,每個加載的模塊對應于一個LDR_DATA_TABLE_ENTRY,該結構如下:
/*****************************************************************************/ typedef struct _LDR_DATA_TABLE_ENTRY { LIST_ENTRY InLoadOrderLinks; LIST_ENTRY InMemoryOrderLinks; LIST_ENTRY InInitializationOrderLinks; PVOID DllBase; PVOID EntryPoint; DWORD SizeOfImage; UNICODE_STRING FullDllName; UNICODE_STRING BaseDllName; DWORD Flags; WORD LoadCount; WORD TlsIndex; LIST_ENTRY HashLinks; PVOID SectionPointer; DWORD CheckSum; DWORD TimeDateStamp; PVOID LoadedImports; PVOID EntryPointActivationContext; PVOID PatchInformation; }LDR_DATA_TABLE_ENTRY,*PLDR_DATA_TABLE_ENTRY; /*****************************************************************************/- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
同樣,在我的機器上為:?
?
前面三個鏈表對應于該結構掛入的三個鏈表,重點在于第四個成員DllBase,這是載入模塊的基地址。因此,我們只需要順著InInitializationOrderModuleList鏈表找到kernel32.dll的PLDR_DATA_TABLE_ENTRY,然后通過其DllBase成員,就知道了kernel32.dll載入的地址。那么InInitializationOrderModuleList鏈表中哪一個kernel32.dll呢?最保險的方法是解析FullDllName成員,這樣代碼會比較復雜。實際上在特定版本的系統中,動態庫初始化的順序是一定的,第一個為ntdll.dll,第二個就是kernel32.dll。Vista 以后第二個是kernelbase.dll,第三個是kernel32.dll。因此,可以避免解析FullDllName成員。
現在,我們要找到進程的PEB結構地址,PEB結構保存于線程的TEB結構中的peb成員,而Windows系統中,寄存器fs總是指向當前線程的TEB。因此,獲取kernel32.dll基地址的整個流程如下:
?
圖38
寫代碼之前,先通過調試器順著該順序看一看,我用windbg加載的是example_1,輸入:?
d fs:[0x30]查看當前peb的地址:
?
圖39
地址為0x7ffda000,輸入:!peb,驗證一下:
?
圖40
是相同的。輸入: d 7ffda000+0x0c,查看PEB_LDR_DATA結構的地址:
?
圖41
輸入: d 00251ea0+0x1c,查看InInitializationOrderModuleList鏈表:
?
圖42
先列一下LIST_ENTRY結構,Flink 指向下一個節點:
/*****************************************************************************/ typedef struct _LIST_ENTRY { struct _LIST_ENTRY *Flink; struct _LIST_ENTRY *Blink; } LIST_ENTRY, *PLIST_ENTRY; /*****************************************************************************/- 1
- 2
- 3
- 4
- 5
- 6
- 7
因此,0x00251f58是第一個元素。輸入:d 0x00251f58查看以下其內容:
?
基地址為0x7c920000,但是右邊顯示為kernel32.dll???第一個元素不是ntdll.dll嗎?
來看看下一個元素:?
?
基地址為0x7c800000,顯示為uer32.dll,看來是出了一些問題,輸入!peb來看看:
?
這里的基地址和名稱對應才是對的。
下面來寫獲取kernel32.dll基地址的代碼:
/*****************************************************************************/ // example_8 獲取kernel32.dll的基地址 #include <stdio.h>// 獲取kernel32.dll的基地址 int get_kernel32_base() { __asm { mov eax, fs:[0x30] // PEB mov eax, [eax+0x0c] // PEB->Ldr mov eax, [eax+0x1c] // PEB->Ldr.InInitializationOrderModuleList.Flink(指向第一個元素) mov eax, [eax] // 指向第二個元素 mov eax, [eax+0x08] // kernel32.dll基地址 } }int main() { printf("0x%x\n", get_kernel32_base()); return 0; } /*****************************************************************************/- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
結果如下:?
?
圖43
這里是一種方法,其它方法(包括Windows 7)請看:
http://blog.harmonysecurity.com/2009_06_01_archive.html
總結
以上是生活随笔為你收集整理的栈溢出笔记1.6 地址问题(1)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: dump 分析模式之 INCORRECT
- 下一篇: (转)性能分析之-- JAVA Thre