[轉]根據PE檔案格式獲取LoadLibraryA()/GetProcAddress()地址

看雪資料發表於2004-08-26

作者:scz 出處:http://bbs.nsfocus.net/index.php?act=ST&f=3&t=147799

本節與PE檔案格式相關的術語以<<微軟PE/COFF規範>>為準([13]),不再強調該點。
winnt.h中定義了部分相關資料結構,後面如未單獨註明來自哪個標頭檔案,均隱指來
自winnt.h。執行vulnerable_0.exe,進入windbg除錯狀態。

> !list -t _LIST_ENTRY.Flink -x "dd" -a "+18 L1" 241ec0
00241ed8  00400000
00241f30  77f50000
00241fd8  77e60000
... ...

從上節可知,ntdll.dll基址是0x77f50000,kernel32.dll基址是0x77e60000。最開
始的64位元組按如下資料結構解析:

--------------------------------------------------------------------------
#define IMAGE_DOS_SIGNATURE 0x5A4D      // MZ

typedef struct _IMAGE_DOS_HEADER        // DOS .EXE header
{
    WORD   e_magic;                     // +0x00 Magic number
    WORD   e_cblp;                      // +0x02 Bytes on last page of file
    WORD   e_cp;                        // +0x04 Pages in file
    WORD   e_crlc;                      // +0x06 Relocations
    WORD   e_cparhdr;                   // +0x08 Size of header in paragraphs
    WORD   e_minalloc;                  // +0x0a Minimum extra paragraphs needed
    WORD   e_maxalloc;                  // +0x0c Maximum extra paragraphs needed
    WORD   e_ss;                        // +0x0e Initial (relative) SS value
    WORD   e_sp;                        // +0x10 Initial SP value
    WORD   e_csum;                      // +0x12 Checksum
    WORD   e_ip;                        // +0x14 Initial IP value
    WORD   e_cs;                        // +0x16 Initial (relative) CS value
    WORD   e_lfarlc;                    // +0x18 File address of relocation table
    WORD   e_ovno;                      // +0x1a Overlay number
    WORD   e_res[4];                    // +0x1c Reserved words
    WORD   e_oemid;                     // +0x24 OEM identifier (for e_oeminfo)
    WORD   e_oeminfo;                   // +0x26 OEM information; e_oemid specific
    WORD   e_res2[10];                  // +0x28 Reserved words
    LONG   e_lfanew;                    // +0x3c File address of new exe header
                                        // +0x40
} IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;
--------------------------------------------------------------------------

> db 77e60000 L0n64
77e60000  4d 5a 90 00 03 00 00 00-04 00 00 00 ff ff 00 00  MZ..............
77e60010  b8 00 00 00 00 00 00 00-40 00 00 00 00 00 00 00  ........@.......
77e60020  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
77e60030  00 00 00 00 00 00 00 00-00 00 00 00 f8 00 00 00  ................
> dd 77e60000+3c L1 (顯示e_lfanew成員)
77e6003c  000000f8

其中e_lfanew成員用於定位PE頭。從註釋中理解,e_lfanew是File pointer,非RVA,
不過在這裡當成RVA處理也沒關係。e_lfanew值為0x000000f8,PE頭在基址加0xf8的
位置。PE頭最開始是標識"PE\0\0",佔4位元組,然後是20位元組固定頭。

--------------------------------------------------------------------------
#define IMAGE_NT_SIGNATURE       0x00004550  // PE00
#define IMAGE_SIZEOF_FILE_HEADER 20

typedef struct _IMAGE_FILE_HEADER
{
    WORD    Machine;               // +0x00
    WORD    NumberOfSections;      // +0x02
    DWORD   TimeDateStamp;         // +0x04
    DWORD   PointerToSymbolTable;  // +0x08
    DWORD   NumberOfSymbols;       // +0x0c
    WORD    SizeOfOptionalHeader;  // +0x10
    WORD    Characteristics;       // +0x12
                                   // +0x14
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;
--------------------------------------------------------------------------

> db 77e60000+f8 L0n24
77e600f8  50 45 00 00 4c 01 04 00-28 fa 6d 3d 00 00 00 00  PE..L...(.m=....
77e60108  00 00 00 00 e0 00 0e 21
                      ^^^^^
接下來是可選頭,SizeOfOptionalHeader成員表明可選頭佔用了224位元組(0x00e0)。

--------------------------------------------------------------------------
typedef struct _IMAGE_OPTIONAL_HEADER
{
    WORD    Magic;                        // +0x00
    BYTE    MajorLinkerVersion;           // +0x02
    BYTE    MinorLinkerVersion;           // +0x03
    DWORD   SizeOfCode;                   // +0x04
    DWORD   SizeOfInitializedData;        // +0x08
    DWORD   SizeOfUninitializedData;      // +0x0c
    DWORD   AddressOfEntryPoint;          // +0x10
    DWORD   BaseOfCode;                   // +0x14
    DWORD   BaseOfData;                   // +0x18
    DWORD   ImageBase;                    // +0x1c
    DWORD   SectionAlignment;             // +0x20
    DWORD   FileAlignment;                // +0x24
    WORD    MajorOperatingSystemVersion;  // +0x28
    WORD    MinorOperatingSystemVersion;  // +0x2a
    WORD    MajorImageVersion;            // +0x2c
    WORD    MinorImageVersion;            // +0x2e
    WORD    MajorSubsystemVersion;        // +0x30
    WORD    MinorSubsystemVersion;        // +0x32
    DWORD   Win32VersionValue;            // +0x34
    DWORD   SizeOfImage;                  // +0x38
    DWORD   SizeOfHeaders;                // +0x3c
    DWORD   CheckSum;                     // +0x40
    WORD    Subsystem;                    // +0x44
    WORD    DllCharacteristics;           // +0x46
    DWORD   SizeOfStackReserve;           // +0x48
    DWORD   SizeOfStackCommit;            // +0x4c
    DWORD   SizeOfHeapReserve;            // +0x50
    DWORD   SizeOfHeapCommit;             // +0x54
    DWORD   LoaderFlags;                  // +0x58
    DWORD   NumberOfRvaAndSizes;          // +0x5c Number of data-dictionary entries in the remainder of the Optional Header
                                          // +0x60
    IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
                                          // +0xe0
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;

typedef struct _IMAGE_DATA_DIRECTORY
{
    DWORD   VirtualAddress;  // +0x00 RVA
    DWORD   Size;            // +0x04 The size in bytes
                             // +0x08
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;

#define IMAGE_NUMBEROF_DIRECTORY_ENTRIES     16

#define IMAGE_DIRECTORY_ENTRY_EXPORT          0   // Export Directory
#define IMAGE_DIRECTORY_ENTRY_IMPORT          1   // Import Directory
#define IMAGE_DIRECTORY_ENTRY_RESOURCE        2   // Resource Directory
#define IMAGE_DIRECTORY_ENTRY_EXCEPTION       3   // Exception Directory
#define IMAGE_DIRECTORY_ENTRY_SECURITY        4   // Security Directory
#define IMAGE_DIRECTORY_ENTRY_BASERELOC       5   // Base Relocation Table
#define IMAGE_DIRECTORY_ENTRY_DEBUG           6   // Debug Directory
#define IMAGE_DIRECTORY_ENTRY_ARCHITECTURE    7   // Architecture Specific Data
#define IMAGE_DIRECTORY_ENTRY_GLOBALPTR       8   // RVA of GP
#define IMAGE_DIRECTORY_ENTRY_TLS             9   // TLS Directory
#define IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG    10   // Load Configuration Directory
#define IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT   11   // Bound Import Directory in headers
#define IMAGE_DIRECTORY_ENTRY_IAT            12   // Import Address Table
#define IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT   13   // Delay Load Import Descriptors
#define IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR 14   // COM Runtime descriptor
--------------------------------------------------------------------------

IMAGE_DATA_DIRECTORY.VirtualAddress是RVA。一般情況DataDirectory[]是含有16
個元素的結構陣列。前兩個元素分別對應Export Directory與Import Directory。但
是規範3.4.3小節指出元素個數不固定,需要檢查NumberOfRvaAndSizes成員確定元素
個數。

此外,不要假設IMAGE_DATA_DIRECTORY.VirtualAddress指向所在section的起始位置,
比如Import Directory一般位於.idata section中,但不能假設RVA指向.idata的起
始位置。不要假設Import Directory所在section一定擁有".idata"這個名字。

> db 77e60000+f8+0n24 L0n224
77e60110  0b 01 07 00 00 56 07 00-00 dc 06 00 00 00 00 00  .....V..........
77e60120  60 ae 01 00 00 10 00 00-00 20 07 00 00 00 e6 77  `........ .....w
77e60130  00 10 00 00 00 02 00 00-05 00 01 00 05 00 01 00  ................
77e60140  04 00 00 00 00 00 00 00-00 60 0e 00 00 04 00 00  .........`......
77e60150  d3 7e 0e 00 03 00 00 00-00 00 04 00 00 10 00 00  .~..............
77e60160  00 00 10 00 00 10 00 00-00 00 00 00 10 00 00 00  ................
77e60170  40 d0 06 00 39 6b 00 00-7c 3b 07 00 28 00 00 00  @...9k..|;..(...
77e60180  00 a0 07 00 d8 5e 06 00-00 00 00 00 00 00 00 00  .....^..........
77e60190  00 00 00 00 00 00 00 00-00 00 0e 00 54 53 00 00  ............TS..
77e601a0  fc 63 07 00 38 00 00 00-00 00 00 00 00 00 00 00  .c..8...........
77e601b0  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
77e601c0  a8 76 07 00 40 00 00 00-90 02 00 00 1c 00 00 00  .v..@...........
77e601d0  00 10 00 00 0c 06 00 00-00 00 00 00 00 00 00 00  ................
77e601e0  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
> dd 77e60000+f8+0n24+5c L1 (檢查NumberOfRvaAndSizes成員)
77e6016c  00000010
> dd 77e60000+f8+0n24+60 L2 (定位Export Directory,當前基是16)
77e60170  0006d040 00006b39
          ^^^^^^^^
> ? 77e60000+0006d040+00006b39
Evaluate expression: 2012035961 = 77ed3b79
> ? 77e60000+0006d040
Evaluate expression: 2012008512 = 77ecd040

現在我們知道Export Directory在"77e60000+0006d040",格式如下:

--------------------------------------------------------------------------
typedef struct _IMAGE_EXPORT_DIRECTORY
{
    DWORD   Characteristics;        // +0x00
    DWORD   TimeDateStamp;          // +0x04
    WORD    MajorVersion;           // +0x08
    WORD    MinorVersion;           // +0x0a
    DWORD   Name;                   // +0x0c Name of the DLL
    DWORD   Base;                   // +0x10 Starting ordinal number for exports
    DWORD   NumberOfFunctions;      // +0x14 Number of entries in the EAT
    DWORD   NumberOfNames;          // +0x18 Number of entries in the ENPT/EOT
    DWORD   AddressOfFunctions;     // +0x1c RVA from base of image
    DWORD   AddressOfNames;         // +0x20 RVA from base of image
    DWORD   AddressOfNameOrdinals;  // +0x24 RVA from base of image
                                    // +0x28
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;
--------------------------------------------------------------------------

AddressOfFunctions

    指向Export Address Table。

    DWORD EAT[NumberOfFunctions];

EAT[i]

    可選頭中IMAGE_DATA_DIRECTORY結構決定了引出資訊範圍。如果EAT[i]的值不在
    引出資訊範圍,則為函式地址(Export RVA)。否則,該值指向形如dllname.#27
    或者dllname.exportfunc的ASCIZ串,用MS術語說,這是一個Forwarder RVA,表
    示由另外一個dll實際引出某函式。

AddressOfNames

    指向Export Name Pointer Table

    DWORD ENPT[NumberOfNames];

ENPT[i]

    指向Export Name Table中某個位置

    ENT由ASCIZ串構成

AddressOfNameOrdinals

    指向Export Ordinal Table

    WORD EOT[NumberOfNames];

參看規範6.3.4小節,這幾個陣列之間的關係用偽C語言描述如下:

--------------------------------------------------------------------------
/*
* 懷疑該規範與MS最終實現有出入,規範中EOT[0]應該等於ordinal_base的,但MS
* 最終實現裡EOT[0]等於0。這裡的虛擬碼符合MS的最終實現。看來,理論->實踐->
* 再理論的學習過程永遠都要牢記,否則死菜了都不知道怎麼死菜的。
*/
index         = Search_ENPT( function_name );
index         = EOT[ index ];
function_addr = EAT[ index ];
ordinal       = ordinal_base + index;
--------------------------------------------------------------------------

為何多出個EOT,因為可能不同的函式名對應同一個函式地址,參[14]的12.5小節。

假設原始碼中有如下宣告:

__declspec(dllexport) int __stdcall PublicFunc ( int a, int b, int c, int d );

假設沒有使用DEF檔案:

EXPORTS

    PublicFunc

同時卻又在原始碼出現:

#pragma comment( linker, "/EXPORT:PublicFunc=_PublicFunc@32" )

此時生成PE檔案時會同時引出兩個符號,"PublicFunc"與"_PublicFunc@32",這兩個
符號對應同一個函式。反映在上述幾個陣列中,即EOT[i]等於EOT[j]。

NumberOfFunctions與NumberOfNames在這種情形下不等。除此之外,還有一種不等情
形。NumberOfNames為0,NumberOfFunctions不為0,表示模組僅透過ordinal引出函
數,這是相當極端卻有可能出現的情形。

VC有個現成的工具dumpbin,可用於觀察引出(export)資訊:

> dumpbin X:\XP\system32\kernel32.dll /exports

  Section contains the following exports for KERNEL32.dll

    00000000 characteristics
    3D6DE616 time date stamp Thu Aug 29 17:15:02 2002
        0.00 version
           1 ordinal base
         942 number of functions
         942 number of names

    ordinal hint RVA      name

          1    0 000137E8 ActivateActCtx
          2    1 000093FE AddAtomA
          3    2 0000D496 AddAtomW
          4    3 000607C5 AddConsoleAliasA
          5    4 0006078E AddConsoleAliasW
          6    5 0004E0A1 AddLocalAlternateComputerNameA
          7    6 0004DF8C AddLocalAlternateComputerNameW
          8    7 00035098 AddRefActCtx
          9    8          AddVectoredExceptionHandler (forwarded to NTDLL.RtlAddVectoredExceptionHandler)
         10    9 00036909 AllocConsole
        ... ...
        401  190 0001B332 GetProcAddress
        ... ...
        571  23A 0001D961 LoadLibraryA
        572  23B 0001D941 LoadLibraryExA
        573  23C 0001D839 LoadLibraryExW
        574  23D 00013B38 LoadLibraryW
        ... ...

回windbg驗證一下:

> db 77e60000+0006d040 L28
77ecd040  00 00 00 00 16 e6 6d 3d-00 00 00 00 34 f5 06 00  ......m=....4...
77ecd050  01 00 00 00 ae 03 00 00-ae 03 00 00 68 d0 06 00  ............h...
77ecd060  20 df 06 00 d8 ed 06 00
> da 77e60000+poi(0x77e60000+0x0006d040+0xc) (Name)
77ecf534  "KERNEL32.dll"
> dd 0x77e60000+0x0006d040+0x10 L1 (Base)
77ecd050  00000001
> dd 0x77e60000+0x0006d040+0x14 L1 (NumberOfFunctions)
77ecd054  000003ae
> dd 0x77e60000+0x0006d040+0x18 L1 (NumberOfNames)
77ecd058  000003ae
> dd 0x77e60000+0x0006d040+0x1c L3 (AddressOfFunctions、AddressOfNames、AddressOfNameOrdinals)
77ecd05c  0006d068 0006df20 0006edd8

套用這個公式:

--------------------------------------------------------------------------
0x190      = Search_ENPT( "GetProcAddress" );
0x190      = EOT[ 0x190 ];
0x77e7b332 = EAT[ 0x190 ];
0x191      = 1 + 0x190;
--------------------------------------------------------------------------

> da 77e60000+poi(77e60000+0006df20+0x190*4) (訪問ENPT、ENT)
77ed1393  "GetProcAddress"
> dw 77e60000+0006edd8+0x190*2 L1 (訪問EOT)
77ecf0f8  0190
> ? 77e60000+poi(77e60000+0006d068+0x190*4) (訪問EAT)
Evaluate expression: 2011673394 = 77e7b332
> u 77e7b332 (這個地址不在[77ecd040, 77ed3b79)內)
kernel32!GetProcAddress:
77e7b332 55               push    ebp
77e7b333 8bec             mov     ebp,esp
77e7b335 51               push    ecx
77e7b336 51               push    ecx
77e7b337 53               push    ebx
77e7b338 57               push    edi
77e7b339 8b7d0c           mov     edi,[ebp+0xc]
77e7b33c bbffff0000       mov     ebx,0xffff

再來驗證一下Forwarder RVA的情形:

--------------------------------------------------------------------------
8          = Search_ENPT( "AddVectoredExceptionHandler" );
8          = EOT[ 8 ];
0x77ed38ad = EAT[ 8 ];
9          = 1 + 8;
--------------------------------------------------------------------------

> da 77e60000+poi(77e60000+0006df20+8*4) (訪問ENPT、ENT)
77ecf5cf  "AddVectoredExceptionHandler"
> dw 77e60000+0006edd8+8*2 L1 (訪問EOT)
77ecede8  0008
> ? 77e60000+poi(77e60000+0006d068+8*4) (訪問EAT)
Evaluate expression: 2012035245 = 77ed38ad
> da 77e60000+poi(77e60000+0006d068+8*4) (這個地址在[77ecd040, 77ed3b79)內)
77ed38ad  "NTDLL.RtlAddVectoredExceptionHan"
77ed38cd  "dler"

現在總結一下"根據PE檔案格式獲取LoadLibraryA()/GetProcAddress()地址"全過程:

a. 透過TEB/PEB獲取kernel32.dll基址

b. 在(基址+0x3c)處獲取e_lfanew

c. 在(基址+e_lfanew+0x78)處獲取Export Directory地址(後面為描述方便簡稱export)

d. 在(基址+export+0x1c)處獲取AddressOfFunctions、AddressOfNames、AddressOfNameOrdinals

e. 搜尋ENPT,確定"LoadLibraryA"、"GetProcAddress"所對應的index

f. index = EOT[ index ];

g. function_addr = EAT[ index ];

下面是完整的C語言演示程式,彙編化留到編寫完整shellcode時進行。

--------------------------------------------------------------------------
/*
* -----------------------------------------------------------------------
* Compile : For x86/EWindows XP SP1 & VC 7
*         : cl GetAddr.c /nologo /Os /G6 /W3 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /link /RELEASE
*         :
* Create  : 2003-08-14 15:11
* Modify  :
* -----------------------------------------------------------------------
*/

#include <stdio.h>
#include <stdlib.h>
#include <windows.h>

#pragma comment( linker, "/INCREMENTAL:NO"    )
#pragma comment( linker, "/subsystem:console" )

static void * __stdcall SearchAPI ( char *BASE, DWORD *EAT, DWORD *ENPT, WORD *EOT, DWORD num, char *name )
{
    DWORD  index         = 0;
    char  *function_name = NULL;
    void  *function_addr = NULL;

    for ( index = 0; index < num; index++ )
    {
        function_name = ENPT[index] + BASE;
        /*
         * 大小寫敏感比較
         */
        if ( 0 == strcmp( function_name, name ) )
        {
            index         = EOT[index];
            function_addr = EAT[index] + BASE;
            return( function_addr );
        }
    }  /* end of for */
    return( NULL );
}  /* end of SearchAPI */

int __cdecl main ( int argc, char * argv[] )
{
    void  *PEB                   = NULL,
          *Ldr                   = NULL,
          *Flink                 = NULL,
          *kernel32_BaseAddress  = NULL,
          *kernel32_BaseDllName  = NULL,
          *ExportDirectory       = NULL,
          *PrivateLoadLibraryA   = NULL,
          *PrivateGetProcAddress = NULL;
    DWORD  NumberOfNames         = 0,
          *AddressOfFunctions    = NULL,
          *AddressOfNames        = NULL;
    WORD  *AddressOfNameOrdinals = NULL;
    LONG   e_lfanew              = 0;

    __asm
    {
        mov     eax,fs:[0x30]
        mov     PEB,eax
    }
    printf( "PEB                   = 0x%08X\n", PEB );
    Ldr                   = *( ( void ** )( ( unsigned char * )PEB + 0x0c ) );
    printf( "Ldr                   = 0x%08X\n", Ldr );
    Flink                 = *( ( void ** )( ( unsigned char * )Ldr + 0x1c ) );
    printf( "Flink                 = 0x%08X\n", Flink );
    Flink                 = *( ( void ** )Flink );
    kernel32_BaseAddress  = *( ( void ** )( ( unsigned char * )Flink + 0x08 ) );
    kernel32_BaseDllName  = *( ( void ** )( ( unsigned char * )Flink + 0x20 ) );
    printf( "kernel32_BaseAddress  = 0x%08X\n", kernel32_BaseAddress );
    wprintf( L"kernel32_BaseDllName  = %s\n", kernel32_BaseDllName );
    /*
     * 根據PE檔案格式進行解析
     */
    e_lfanew              = *( ( LONG * )( ( unsigned char * )kernel32_BaseAddress + 0x3c ) );
    printf( "e_lfanew              = 0x%08X\n", e_lfanew );
    ExportDirectory       = *( ( DWORD * )( ( unsigned char * )kernel32_BaseAddress + e_lfanew + 0x78 ) ) +
                            ( unsigned char * )kernel32_BaseAddress;
    printf( "ExportDirectory       = 0x%08X\n", ExportDirectory );
    NumberOfNames         = *( ( DWORD * )( ( unsigned char * )ExportDirectory + 0x18 ) );
    printf( "NumberOfNames         = %u\n", NumberOfNames );
    AddressOfFunctions    = ( DWORD * )
                            (
                                *( ( DWORD * )( ( unsigned char * )ExportDirectory + 0x1c ) ) +
                                ( unsigned char * )kernel32_BaseAddress
                            );
    printf( "AddressOfFunctions    = 0x%08X\n", AddressOfFunctions );
    AddressOfNames        = ( DWORD * )
                            (
                                *( ( DWORD * )( ( unsigned char * )ExportDirectory + 0x20 ) ) +
                                ( unsigned char * )kernel32_BaseAddress
                            );
    printf( "AddressOfNames        = 0x%08X\n", AddressOfNames );
    AddressOfNameOrdinals = ( WORD * )
                            (
                                *( ( DWORD * )( ( unsigned char * )ExportDirectory + 0x24 ) ) +
                                ( unsigned char * )kernel32_BaseAddress
                            );
    printf( "AddressOfNameOrdinals = 0x%08X\n", AddressOfNameOrdinals );
    PrivateLoadLibraryA   = SearchAPI
                            (
                                kernel32_BaseAddress,
                                AddressOfFunctions,
                                AddressOfNames,
                                AddressOfNameOrdinals,
                                NumberOfNames,
                                "LoadLibraryA"
                            );
    printf( "PrivateLoadLibraryA   = 0x%08X\n", PrivateLoadLibraryA );
    printf( "LoadLibraryA          = 0x%08X\n", LoadLibraryA );
    PrivateGetProcAddress = SearchAPI
                            (
                                kernel32_BaseAddress,
                                AddressOfFunctions,
                                AddressOfNames,
                                AddressOfNameOrdinals,
                                NumberOfNames,
                                "GetProcAddress"
                            );
    printf( "PrivateGetProcAddress = 0x%08X\n", PrivateGetProcAddress );
    printf( "GetProcAddress        = 0x%08X\n", GetProcAddress );
    return( EXIT_SUCCESS );
}  /* end of main */

#if 0

/*
* 按初始化順序前向遍歷連結串列,第二個節點對應kernel32.dll。
*/

#include <stdio.h>
#include <stdlib.h>

#pragma comment( linker, "/INCREMENTAL:NO"    )
#pragma comment( linker, "/subsystem:console" )

int __cdecl main ( int argc, char * argv[] )
{
    void *PEB         = NULL,
         *Ldr         = NULL,
         *Flink       = NULL,
         *p           = NULL,
         *BaseAddress = NULL,
         *BaseDllName = NULL;

    __asm
    {
        mov     eax,fs:[0x30]
        mov     PEB,eax
    }
    printf( "PEB   = 0x%08X\n", PEB );
    Ldr   = *( ( void ** )( ( unsigned char * )PEB + 0x0c ) );
    printf( "Ldr   = 0x%08X\n", Ldr );
    Flink = *( ( void ** )( ( unsigned char * )Ldr + 0x1c ) );
    printf( "Flink = 0x%08X\n", Flink );
    p     = Flink;
    do
    {
        BaseAddress = *( ( void ** )( ( unsigned char * )p + 0x08 ) );
        BaseDllName = *( ( void ** )( ( unsigned char * )p + 0x20 ) );
        printf( "p     = 0x%08X 0x%08X ", p, BaseAddress );
        wprintf( L"%s\n", BaseDllName );
        p = *( ( void ** )p );
    }
    while ( Flink != p );
    return( EXIT_SUCCESS );
}  /* end of main */

#endif
--------------------------------------------------------------------------

執行效果如下,對比PrivateLoadLibraryA與LoadLibraryA的值,應該相等。

> GetAddr
PEB                   = 0x7FFDF000
Ldr                   = 0x00241E90
Flink                 = 0x00241F28
kernel32_BaseAddress  = 0x77E60000
kernel32_BaseDllName  = kernel32.dll
e_lfanew              = 0x000000F8
ExportDirectory       = 0x77ECD040
NumberOfNames         = 942
AddressOfFunctions    = 0x77ECD068
AddressOfNames        = 0x77ECDF20
AddressOfNameOrdinals = 0x77ECEDD8
PrivateLoadLibraryA   = 0x77E7D961
LoadLibraryA          = 0x77E7D961
PrivateGetProcAddress = 0x77E7B332
GetProcAddress        = 0x77E7B332

這裡演示的技巧不只用於exploit、shellcode、virus,還大量用於特殊Driver程式設計
中。根據PE檔案格式解析記憶體中的模組映像是很基礎的知識,我今天才接觸了一下,
慚愧。

SearchAPI()用到了被搜尋函式名,直接在shellcode中儲存完整函式名會佔用不少空
間。可以考慮在shellcode中儲存函式名的某種雜湊值,同時SearchAPI()比較雜湊值
而非函式名。任何一種雜湊演算法都會丟失部分原有資訊,以致不同函式名產生的雜湊
值相同,所謂"碰撞"。virus程式設計中為此常常使用標準CRC32演算法([8]),參29A-4.227。
但是LSD認為CRC32的彙編演算法太長了([15]),他們用了一個相當簡單的演算法:

while ( *c )
{
    h = ( ( h << 5 ) | ( h >> 27 ) ) + *c++;
}

不超過10行彙編程式碼。據LSD的報告稱,對超過5000個不同的dll測試,覆蓋50000個
不同的函式名,該雜湊演算法未產生一次碰撞。若真是如此,對於編寫shellcode來講,
完全足夠了。

下面是完整的C語言演示程式,真正彙編化後應提前計算函式名的雜湊值。

--------------------------------------------------------------------------
/*
* -----------------------------------------------------------------------
* Compile : For x86/EWindows XP SP1 & VC 7
*         : cl GetAddr_0.c /nologo /Os /G6 /W3 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /link /RELEASE
*         :
* Create  : 2003-08-14 17:24
* Modify  :
* -----------------------------------------------------------------------
*/

#include <stdio.h>
#include <stdlib.h>
#include <windows.h>

#pragma comment( linker, "/INCREMENTAL:NO"    )
#pragma comment( linker, "/subsystem:console" )

static DWORD __stdcall GetHash ( char *c )
{
    DWORD h = 0;

    while ( *c )
    {
        h = ( ( h << 5 ) | ( h >> 27 ) ) + *c++;
    }
    return( h );
}  /* end of GetHash */

static void * __stdcall SearchAPI ( char *BASE, DWORD *EAT, DWORD *ENPT, WORD *EOT, DWORD num, DWORD NameHash )
{
    DWORD  index         = 0,
           h             = 0;
    char  *function_name = NULL;
    void  *function_addr = NULL;

    for ( index = 0; index < num; index++ )
    {
        function_name = ENPT[index] + BASE;
        h             = GetHash( function_name );
        if ( h == NameHash )
        {
            index         = EOT[index];
            function_addr = EAT[index] + BASE;
            return( function_addr );
        }
    }  /* end of for */
    return( NULL );
}  /* end of SearchAPI */

int __cdecl main ( int argc, char * argv[] )
{
    void  *PEB                   = NULL,
          *Ldr                   = NULL,
          *Flink                 = NULL,
          *kernel32_BaseAddress  = NULL,
          *kernel32_BaseDllName  = NULL,
          *ExportDirectory       = NULL,
          *PrivateLoadLibraryA   = NULL,
          *PrivateGetProcAddress = NULL;
    DWORD  NumberOfNames         = 0,
          *AddressOfFunctions    = NULL,
          *AddressOfNames        = NULL,
           NameHash              = 0;
    WORD  *AddressOfNameOrdinals = NULL;
    LONG   e_lfanew              = 0;

    __asm
    {
        mov     eax,fs:[0x30]
        mov     PEB,eax
    }
    printf( "PEB                   = 0x%08X\n", PEB );
    Ldr                   = *( ( void ** )( ( unsigned char * )PEB + 0x0c ) );
    printf( "Ldr                   = 0x%08X\n", Ldr );
    Flink                 = *( ( void ** )( ( unsigned char * )Ldr + 0x1c ) );
    printf( "Flink                 = 0x%08X\n", Flink );
    Flink                 = *( ( void ** )Flink );
    kernel32_BaseAddress  = *( ( void ** )( ( unsigned char * )Flink + 0x08 ) );
    kernel32_BaseDllName  = *( ( void ** )( ( unsigned char * )Flink + 0x20 ) );
    printf( "kernel32_BaseAddress  = 0x%08X\n", kernel32_BaseAddress );
    wprintf( L"kernel32_BaseDllName  = %s\n", kernel32_BaseDllName );
    /*
     * 根據PE檔案格式進行解析
     */
    e_lfanew              = *( ( LONG * )( ( unsigned char * )kernel32_BaseAddress + 0x3c ) );
    printf( "e_lfanew              = 0x%08X\n", e_lfanew );
    ExportDirectory       = *( ( DWORD * )( ( unsigned char * )kernel32_BaseAddress + e_lfanew + 0x78 ) ) +
                            ( unsigned char * )kernel32_BaseAddress;
    printf( "ExportDirectory       = 0x%08X\n", ExportDirectory );
    NumberOfNames         = *( ( DWORD * )( ( unsigned char * )ExportDirectory + 0x18 ) );
    printf( "NumberOfNames         = %u\n", NumberOfNames );
    AddressOfFunctions    = ( DWORD * )
                            (
                                *( ( DWORD * )( ( unsigned char * )ExportDirectory + 0x1c ) ) +
                                ( unsigned char * )kernel32_BaseAddress
                            );
    printf( "AddressOfFunctions    = 0x%08X\n", AddressOfFunctions );
    AddressOfNames        = ( DWORD * )
                            (
                                *( ( DWORD * )( ( unsigned char * )ExportDirectory + 0x20 ) ) +
                                ( unsigned char * )kernel32_BaseAddress
                            );
    printf( "AddressOfNames        = 0x%08X\n", AddressOfNames );
    AddressOfNameOrdinals = ( WORD * )
                            (
                                *( ( DWORD * )( ( unsigned char * )ExportDirectory + 0x24 ) ) +
                                ( unsigned char * )kernel32_BaseAddress
                            );
    printf( "AddressOfNameOrdinals = 0x%08X\n", AddressOfNameOrdinals );
    NameHash              = GetHash( "LoadLibraryA" );
    printf( "NameHash              = 0x%08X\n", NameHash );
    PrivateLoadLibraryA   = SearchAPI
                            (
                                kernel32_BaseAddress,
                                AddressOfFunctions,
                                AddressOfNames,
                                AddressOfNameOrdinals,
                                NumberOfNames,
                                NameHash
                            );
    printf( "PrivateLoadLibraryA   = 0x%08X\n", PrivateLoadLibraryA );
    printf( "LoadLibraryA          = 0x%08X\n", LoadLibraryA );
    NameHash              = GetHash( "GetProcAddress" );
    printf( "NameHash              = 0x%08X\n", NameHash );
    PrivateGetProcAddress = SearchAPI
                            (
                                kernel32_BaseAddress,
                                AddressOfFunctions,
                                AddressOfNames,
                                AddressOfNameOrdinals,
                                NumberOfNames,
                                NameHash
                            );
    printf( "PrivateGetProcAddress = 0x%08X\n", PrivateGetProcAddress );
    printf( "GetProcAddress        = 0x%08X\n", GetProcAddress );
    return( EXIT_SUCCESS );
}  /* end of main */
--------------------------------------------------------------------------

> GetAddr_0
PEB                   = 0x7FFDF000
Ldr                   = 0x00241E90
Flink                 = 0x00241F28
kernel32_BaseAddress  = 0x77E60000
kernel32_BaseDllName  = kernel32.dll
e_lfanew              = 0x000000F8
ExportDirectory       = 0x77ECD040
NumberOfNames         = 942
AddressOfFunctions    = 0x77ECD068
AddressOfNames        = 0x77ECDF20
AddressOfNameOrdinals = 0x77ECEDD8
NameHash              = 0x331ADDDC <- "LoadLibraryA"的雜湊值
PrivateLoadLibraryA   = 0x77E7D961
LoadLibraryA          = 0x77E7D961
NameHash              = 0x99C95590 <- "GetProcAddress"的雜湊值
PrivateGetProcAddress = 0x77E7B332
GetProcAddress        = 0x77E7B332

☆ 參考資源

[13] Microsoft Portable Executable and Common Object File Format Specification
     http://www.microsoft.com/whdc/hwdev/download/hardware/pecoff.doc
     http://www.microsoft.com/whdc/hwdev/download/hardware/pecoff.pdf

[14] <<Programming Applications for Microsoft Windows, Fourth Edition>> - Jeffrey Richter

[15] http://www.lsd-pl.net/documents/winasm-1.0.1.pdf
     http://www.lsd-pl.net/documents/winasm.ppt
     http://lsd-pl.net/projects/winasm-1.0.1.tar.gz

相關文章