windbg分析Kernel32.dll導出表


 

寫在前面的話

繼續上篇,在獲得了Kernel32.dll基址的基礎上,分析它的導出表結構;

對PE結構不太熟悉的同學,可以參考看雪論壇里的一篇帖子:https://bbs.pediy.com/thread-224265.htm

零、思路說明

分析之前,要明確我們的目的是,為了能在程序里獲得某些API的地址;

I)   遍歷導出名稱表(下面統稱為ENT),匹配到需要的函數【匹配過程中,設置一個自增的變量,這樣找到后,變量里就是該函數在ENT里的下標】

II)  根據導出序號表(下面統稱EOT),結合下標,找到該函數在導出地址表(EAT)里的索引

III) 然后,從EAT里取出來這個索引處的地址,就是我們要搜尋的函數的地址了;

一、分析Kernel32.dll在文件中的結構

由於是在win10_64位上進行的分析;不得不提的一點:

I)  如果程序是32位的,那么程序會鏈接C:\Windows\SysWOW64\kernel32.dll

II) 如果程序是64位的,那么程序會鏈接C:\Windows\System32\kernel32.dll

這一點,也可以通過分析基址開始80處的偏移得知;大家可以自行去分析,上篇獲得基址后,db 看下內存,然后和這兩個文件對比下內容就明白了;

這里,就以32位程序為例,進行分析,那么自然,我們首先需要在010里看下kernel32.dll的結構;

對應的文件偏移是:

 這里,再次看下導出表的數據結構體(40Byte)

typedef struct _IMAGE_EXPORT_DIRECTORY {
    DWORD   Characteristics;
    DWORD   TimeDateStamp;
    WORD    MajorVersion;
    WORD    MinorVersion;
    DWORD   Name; // DLL的名稱地址
    DWORD   Base; // 索引基數
    DWORD   NumberOfFunctions; // 函數地址表大小
    DWORD   NumberOfNames; // 函數名表大小 == 函數序號表大小
    DWORD   AddressOfFunctions;     // 函數地址表——首地址
    DWORD   AddressOfNames;         // 函數名表——首地址
    DWORD   AddressOfNameOrdinals;  // 函數序號表——首地址
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;

00 00 00 00
CE 65 47 74【時間戳】
00 00      【主版本】
00 00      【次版本】 ]

B6 45 08 00【DLL名稱地址】
01 00 00 00【索引基數】

3B 06 00 00【函數地址表大小】
3B 06 00 00【函數名表大小 == 函數序號表大小】

68 07 08 00【函數地址表——首地址】
54 20 08 00【函數名表——首地址】
40 39 08 00【函數序號表——首地址】

至此,我們可以得出以下幾個結論:

I)   擴展頭里的導入表區段偏移RVA:00080740,距離PE頭位置0x168

II)  都是函數名導入

III)  EAT_Offset:80768 - 80740 = 0x28

      ENT_Offset:82054 - 80740 = 0x1914

      EOT_Offset:83940 - 80740 = 0x3200

 下面的圖,可以幫助大家理解這些偏移的含義,以及如何根據這些偏移找到運行時,載入內存后的對應位置;

0. 根據BaseAddr+0x168,我們可以找到ExportStart RVA

1. 根據BaseAddr + ExportStart_RVA + EAT/ENT/EOT_Offset就能知道真正載入內存后的EAT/ENT/EOT位置了;

二、分析Kernel32.dll在內存中的結構

根據上面的分析,其實,我們已經知道了偏移量,接下來,事情就簡單了,同樣的,用windbg附加打開一個進程,方法和上一篇帖子一致;

我們要做的,就是根據我們計算的偏移,算出真正的3張表的地址,並進行解析;

ExportStart_RVA = 0x80740;

拿一個舉例,就第1個吧,RVA+基址,看下

這個函數是AcquireSRWLockExclusive,ENT里的第0號元素;那看下EOT[0]的內容:

EOT[0] = 0x03;也就是說,這個函數的地址,在EAT[0x3]里;那看下EAT[3]的內容:

EAT[3] = 0x08463a,這是RVA,那么函數地址就是BaseAddr + 0x08463A = 7536463a 

 

看到這,說實話,有點懷疑人生了,這不是個字符串嗎,我們不是在看EAT嗎,不應該是函數地址了嗎?

這里,不得不再說下Kernel32.dll的奇葩之處,它會“轉發”別的dll的導出函數,就像我們在調試器中見識到的那樣的導入表結構:

為了方便大家進一步看清楚EAT的正常的真實情況,這里,就再次分析一個別的函數LoadLibraryExA,

套路也是一樣,ENT->EOT->EAT,EAT里存放的是RVA,因為一個一個去在ENT里匹配LoadLibraryExA實在是太繁瑣,而且,在上面的分析中,

相信大家都已經理解了ENT和EOT以及EAT的相互間的關系;這里,就直接在LoadPE里找到這個函數的RVA,我們看下:

RVA = 159A0,然后,我們在windbg里用基址加上這個偏移,對齊進行反匯編,看下是不是和我們想的一致:

結論得到了驗證,我們也可以在調試器中,進行再次確認,Ctrl + G,輸入LoadLibraryExA,如下:

注意看那個地址,這也從側面證明,我們對EAT的解析是成功的;

三、編寫代碼,實現加載user32.dll

    char srcLoadLibraryExA[] = "LoadLibraryA";
    char srcuser32[] = "user32.dll";
    int kernel32Addr = GetKernel32Base(); // 上一篇中已經寫過了
    int EATAddr = 0;
    int ENTAddr = 0;
    int EOTAddr = 0;

    _asm {
        push eax;
        push ebx;
        mov eax, [kernel32Addr]; // ExportStart_RVA
        mov eax, [eax + 0x168]; // ExportStart_RVA
        add eax, kernel32Addr; // ExportStart_VA
        mov ebx, eax;
        add ebx, 0x28; // EAT
        mov EATAddr, ebx;
        mov ebx, eax;
        add ebx, 0x1914;
        mov ENTAddr, ebx; // ENT
        mov ebx, eax;
        add ebx, 0x3200;
        mov EOTAddr, ebx; // EOT
        pop ebx;
        pop eax;
    }

    _asm {
        push eax;
        push ebx;
        push ecx;
        push esi;
        push edi;
        xor ebx, ebx;
        mov eax, 0x63B;
        cld;

    _ENT_FIND:
        mov ecx, 13;
        mov esi, ENTAddr; // ENT RVA
        mov esi, [esi + 4 * ebx];
        add esi, kernel32Addr; // ENT VA
        lea edi, srcLoadLibraryExA;
        repe cmpsb;
        je _ENT_OK;
        inc ebx;
        dec eax;
        cmp eax, 0;
        jg _ENT_FIND;
        jmp _ENT_END;

    _ENT_OK:
        mov ecx, EOTAddr; // EOT Number
        mov ecx, [ecx + 2 * ebx];
        and ecx, 0xFFFF;
        mov esi, EATAddr; // EAT Address RVA
        mov esi, [esi + 4 * ecx];
        add esi, kernel32Addr; // EAT Address VA

        lea eax, srcuser32;
        push eax;
        call esi;

    _ENT_END:
        pop edi;
        pop esi;
        pop ecx;
        pop ebx;
        pop eax;
    }

至此,對Kernel32.dll的導出表解析便告一段落了;


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM