buffer_overflow 第3章 筆記4 在kernel32.dl 抓取LoadLibraryA函數
本節目的:
筆記3的最後我們撰寫了一個組語,可以動態抓取kernel32.dll的基址,有了基址後我們要在kernel32.dll裡面尋找一個函數叫做LoadLibraryA()
小知識:
1.
前面抓到kernel32.dll的基底後,我們知道kernel32.dll有很多函數,我們需要的是LoadLibraryA函數。2.
PE文件的全稱是Portable Executable,意為可移植的可執行的文件,常見的EXE、DLL、OCX、SYS、COM都是PE文件,PE文件是微軟Windows操作系統上的程序文件(可能是間接被執行,如DLL)。3.
所以我們要知道DLL是按照PE文件格式的結構組成的,接下來要了解PE文件。本節只會講相關的部分,不相關的一律跳過。
4.
先給大家一個小概念,我們需要找出PE文件中的導出表Export Directory,導出表是啥?當應用程式需要某個DLL裡面的函數時,該DLL就會被載入到記憶體,但DLL裡面有那摸多函數,我們需要那些不需要那些,這時就是導入表,跟導出表的作用了。
5.導出表:
你可以想像我們進入到一家名為kernel32.dll的店裡,店家會提供菜單,所有的API函數都在菜單上面,這就是導出表,導出表提供該DLL全部的API函數。6.導入表:
導入表的概念則是,我們想吃啥就在單子上面寫上啥,也就是說,此程式需要用到該DLL裡面的哪一個函數,導入表裡面就會存放哪個函數。7.思路:從導出表找出導入表未提供的函數:
我們想要用shellcode攻擊A程式,A程式本身有加載kernel32.dll,A程式正常操作下,也只會加載kernel32.dll幾個正常被需要函數,放在導入表內,但我們的shellcode需要使用kernel32.dll裡面其他的函數,所以我們就需要去導出表挖出想要的函數。首先,我們找到kernel32.dll的基址在7c800000,看一下PE大體架構:
1.
首先從7c800000+0x00開始就是整個PE文件結構,PE文件結構一開始是DOS頭結構,DOS Header是早期DOS作業系統下使用的,現在已經不怎使用,在DOS Header結構中的最後一個成員e_flanew,占4bytes,裡面存放一個值, 此值加kernel32.dll的基址7c800000,可以定位到kernel32.dll的NT Header起始位置。在WinNT.h表頭下對_IMAGE_DOS_HEADER 的定義:
看到DOS頭結構中最後一個成員e_flanew,偏移為0x3c。
typedef struct _IMAGE_DOS_HEADER { // DOS .EXE header
WORD e_magic; // Magic number
WORD e_cblp; // Bytes on last page of file
WORD e_cp; // Pages in file
WORD e_crlc; // Relocations
WORD e_cparhdr; // Size of header in paragraphs
WORD e_minalloc; // Minimum extra paragraphs needed
WORD e_maxalloc; // Maximum extra paragraphs needed
WORD e_ss; // Initial (relative) SS value
WORD e_sp; // Initial SP value
WORD e_csum; // Checksum
WORD e_ip; // Initial IP value
WORD e_cs; // Initial (relative) CS value
WORD e_lfarlc; // File address of relocation table
WORD e_ovno; // Overlay number
WORD e_res[4]; // Reserved words
WORD e_oemid; // OEM identifier (for e_oeminfo)
WORD e_oeminfo; // OEM information; e_oemid specific
WORD e_res2[10]; // Reserved words
LONG e_lfanew; // File address of new exe header
} IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;
用WinDbg看一下e_flanew的值是多少:
值為f0
------------------------------------------------------------------------------------------------------------------------2.
7c800000加上f0,代表來到了NT Header的結構,該結構的重點是成員OptionalHeader是_IMAGE_OPTIONAL_HEADER結構,在偏移0x18的地方。
在WinNT.h表頭下對_IMAGE_DOS_HEADER 的定義:
typedef struct _IMAGE_NT_HEADERS64 {
DWORD Signature;
IMAGE_FILE_HEADER FileHeader;
IMAGE_OPTIONAL_HEADER64 OptionalHeader;
} IMAGE_NT_HEADERS64, *PIMAGE_NT_HEADERS64;
觀察WinDbg的輸出:
-------------------------------------------------------------------------------------------------------------------------
3.
成員Optional Header 是 _IMAGE_OPTIONAL_HEADER結構
看一下_IMAGE_OPTIONAL_HEADER結構。該結構的重點:在偏移0x60的地方,有一個成員DataDirectory,該成員是一個16個元素的陣列,每個元素都是_IMAGE_DATA_DIRECTORY結構。
看一下成員DataDirectory的定義,可以發現第0個元素就是導出表,該陣列每個元素都是_IMAGE_DATA_DIRECTORY結構:
WinDbg觀察一下_IMAGE_DATA_DIRECTORY結構:
可以發現裡面有兩個成員:
第一個 VirtualAddress 存放著導出表的相對位址 0x262c
第二個Size 存放著導出表有多大 0x6d19
7c800000+0x262c可以來到導出表的絕對位址
我們先看一下導出表定義的結構長啥樣子
但可惜WinDbg沒有dt ntdll!_IMAGE_EXPORT_DIRECTORY的指令來解釋導出表。
所以直接解釋:
看到上圖最後四個成員是關鍵:
NumberOfNames //記錄總共有多少個函數名也就是 kernel32.dll總共的函數
AddressOfFunctions //RVA 存放一個相對位置
AddressOfNames //RVA
AddressOfNameOrdinals //RVA
後面這三個比較複雜
1.AddressOfFunctions:
存放著一個值,該值是一個相對位置,加上7c800000後,得到一個絕對位置,該絕對位置是一個陣列,陣列裡面的元素全部都是32位元長的相對位址,也就是說[RVA,RVA,RVA.......] ,該陣列元素有NumberOfNames個。
假如陣列裡面的某個元素,RVA加上7c800000後得到一個絕對位址,此絕對位址就會是
kernel32.dll中某個函數的真正位置。
陣列代稱:[Functions 陣列]
--------------------------------------------------------------------------------------------------------------------------
2.AddressOfNames :
存放著一個值,該值是一個相對位置,加上7c800000後,得到一個絕對位置,該絕對位置是一個陣列,陣列裡面的元素全部都是32位元長的相對位址,也就是說[RVA,RVA,RVA.......] ,元素有NumberOfNames個。
假如陣列裡面的某個元素,RVA加上7c800000後得到一個絕對位置,此絕對位址存放函數名稱字串。
函數名稱的順序按照A開頭升幕第一個為函數名稱字串"ActivateActCtx"
陣列代稱:[Names 陣列]
-------------------------------------------------------------------------------------------------------------------------
3.AddressOfNameOrdinals
存放著一個值,該值是一個相對位置,加上7c800000後,得到一個絕對位置, 該絕對位置是一個陣列,陣列裡面的元素都是16位元長的整數,也就是說[12,55,87.......]
陣列代稱:[ordinals 陣列]
-------------------------------------------------------------------------------------------------------------------------
[ordinals 陣列] 跟 [Names 陣列] 互相對應 ,也就是說 [Names 陣列] 第一個元素指向的函數名稱字串"ActivateActCtx" ,[ordinals 陣列]第一個元素就對應到ActivateActCtx函數。
例如:
1.
在[Names 陣列]第一個元素中取得一個RVA,該 RVA+7c800000 位址存放字串"ActivateActCtx"。
2.
[ordinals 陣列] 跟 [Names 陣列]互相對應,找到[ordinals 陣列] 第一個元素的值,此值為[Functions 陣列]的索引值。
3.
把此值拿去給[Functions 陣列]索引,找到索引對應的RVA ,把7c800000+該RVA 就得到ActivateActCtx 真正的函數位置。
前面我們來到了7c800000+0x262c導出表的絕對位址
我們要找函數LoadlibraryA就在導出表內,導出表總共0x6d19大小。
先列出最後四個成員:
NumberOfNames //記錄總共有多少個函數名也就是 kernel32.dll總共的函數
AddressOfFunctions //RVA 存放一個相對位置
AddressOfNames //RVA
AddressOfNameOrdinals //RVA
--------------------------------------------------------------------------------------------------------------------------
WinDbg輸出:
可以看到:
NumberOfNames 值為3b9 代表3b9個函數
AddressOfFunctions 值為2654是一個相對位址(RVA
AddressOfNames 值為3538是一個相對位址(RVA
AddressOfNameOrdinals 值為441c是一個相對位址(RVA
AddressOfFunctions 值為2654是一個相對位址(RVA
AddressOfNames 值為3538是一個相對位址(RVA
AddressOfNameOrdinals 值為441c是一個相對位址(RVA
--------------------------------------------------------------------------------------------------------------------
上面最後三個成員都存放一個RVA ,該RVA 加上7c800000可以得到:
[Functions 陣列] 位址為 (7c800000+2654)
[Names 陣列] 位址為 (7c800000+3538)
[ordinals 陣列] 位址為 (7c800000+441c)
---------------------------------------------------------------------------------------------------------------------
列出三個陣列開頭往後40位元組的記憶體內容:
-------------------------------------------------------------------------------------------------------------------------
驗證看看[Names 陣列]第一個元素找到的RVA加上7c800000是否為ActivateActCtx字串位置。
上面紅色框起來的就是[Names 陣列]第一個元素 ,大小為4bytes 值為9b 4b 00 00,但windows存放順序是little endian 所以反過來看 00 00 4b 9b 所以此RVA = 0x4b9b,加上7c800000
驗證成功!!!
接下來我們要真正來找LoadLibraryA了
我們剛剛驗證出(7c800000+0x4b9b) 存放了第一個函數名稱ActivateActCtx,那這個位址往後應該會存放我們要的LoadLibraryA字串。
我們用Windbg幫我們搜尋看看,給他一個搜尋範圍 就給他導出表的大小(0x6d19)往後搜尋好了。
找到了 7c80763d位址 存放著LoadLibraryA的字串。
7c80763d -7c800000=763d , [Names 陣列] 為[RVA ,RVA,RVA, ......]
代表說763d就是 [Names 陣列] 裡面的某個元素的值。
我們從[Names 陣列] 的第一個元素值開始搜尋 看哪一個元素存放763d
找到了在位址7c803e48, (7c803e48 - 0x7c803538 )/4就是索引值=0x0244,除4是因為陣列每個元素為4bytes。 代表[Names 陣列] 第0x0244個元素
接著把這個索引值給[ordinals 陣列] ,[ordinals 陣列] 在(7c800000+0x441c),所以
(7c800000+0x441c+0x0244*2) 乘以2是因為 [ordinals 陣列]每個元素佔2bytes
在 [ordinals 陣列]的索引號0x244找到了一個值剛好也是0x0244,把這0x0244值當作是[Functions 陣列]的索引值,(7c800000+0x2654+0x0244*4) 乘以4是因為[Functions 陣列] 每個元素佔4bytes
最終在[Functions 陣列]索引號0x0244 找到了一個值,該值為1d7b,加上7c800000就是
LoadLibraryA函數的真正位址 (7c801d7b)。
驗證:
拿出WinDbg 看看
留言
張貼留言