☆ 根据PE文件格式获取LoadLibraryA()/GetProcAddress()地址 https://scz.617.cn/windows/200311231551.txt 本节与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 #include #include #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 #include #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 #include #include #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/whdc/hwdev/download/hardware/pecoff.doc http://www.microsoft.com/whdc/hwdev/download/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