根据PE文件格式获取LoadLibraryA()/GetProcAddress()地址

☆ 根据PE文件格式获取LoadLibraryA()/GetProcAddress()地址

本节与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
小节,英文版第19章"Building the DLL Module"。

假设源代码中有如下声明:

__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
--------------------------------------------------------------------------

执行效果如下:

> 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 ( unsigned 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/......ownload/hardware/pecoff.doc
    http://www.microsoft.com/......ownload/hardware/pecoff.pdf

[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

你可能感兴趣的:(根据PE文件格式获取LoadLibraryA()/GetProcAddress()地址)