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


--------------------------------------------------------------------------------------------------------------------
上面最後三個成員都存放一個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




驗證成功!!!

                             ã€Œgood meme」的圖片搜尋結果

接下來我們要真正來找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 看看




完美


「MEME 」的圖片搜尋結果

接下來我們可以寫個邏輯式來自動抓取LoadLibraryA 不過在那之前,我們還有最後一步路要走。






留言

這個網誌中的熱門文章

buffer_overflow 第1章 筆記 介紹

Buffer_overflow 第2章 筆記1 函數堆疊的故事

buffer_overflow 第3章 筆記6 拼出我們第二個shellcode。