• 标 题:[转]根据PE文件格式获取LoadLibraryA()/GetProcAddress()地址
  • 作 者:linhanshi
  • 时 间:004-08-26,20:10
  • 链 接:http://bbs.pediy.com

作者: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