[轉]根據PE檔案格式獲取LoadLibraryA()/GetProcAddress()地址
作者: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
相關文章
- JAVA根據IP地址獲取詳細的地域資訊(轉)2015-07-16Java
- js根據ip地址獲取所在城市2015-01-10JS
- js根據ip地址獲取省份城市的方法2018-09-10JS
- js根據IP地址獲取當前的省市2018-06-11JS
- PE檔案格式2015-11-15
- win32 PE 檔案格式 (轉)2007-12-15Win32
- js根據ip地址獲取城市地理位置2020-07-29JS
- C#根據經緯度獲取實體地址2020-04-04C#
- 根據ip獲取城市2017-11-10
- JSF/JAVA 根據IP獲取客戶端Mac地址2017-07-06JSJava客戶端Mac
- 根據聲音獲取物件2014-05-16物件
- 根據IP獲取國家2014-12-12
- javascript根據class獲取物件2012-05-22JavaScript物件
- PE檔案格式的RVA概念2008-04-14
- 根據 IP 獲取省市名稱2020-12-10
- 根據IP獲取國家省市2017-11-10
- 根據IP頭格式 寫sniff (轉)2007-12-11
- 核心分析PE獲取DLL匯出函式地址2013-08-10函式
- 根據副檔名識別檔案型別(轉)2007-08-10型別
- PE檔案格式詳細解析(一)2020-03-08
- 初步瞭解PE檔案格式(上)2017-07-03
- js根據class值獲取元素物件2017-03-22JS物件
- 根據使用者來獲取渠道2024-10-19
- 讀取DXF格式檔案 (轉)2007-12-05
- C語言程式設計獲取PE檔案DOS頭2016-02-02C語言程式設計
- 根據需要動態include不同的檔案 (轉)2007-12-12
- PE檔案格式詳細解析(二)--IAT2020-03-08
- js根據經緯度,獲取省市區。(百度地圖逆地址解析)2020-11-14JS地圖
- C++ 獲取PE檔案自校驗值的程式碼2019-01-05C++
- nodeJS根據檔案字尾名讀取檔案並返回符合檔案總數2020-12-30NodeJS
- PHP根據身份證號碼,獲取性別、獲取生日、計算年齡、獲取地址、獲取生肖、獲取星座、以及判斷是否是身份證號(方法教程)2017-08-22PHP
- 根據api檔案生成程式碼2021-10-21API
- 根據key集合批次從map中獲取value2024-07-02
- 根據id獲取元素的寬度的方法2024-05-18
- [轉載]淺析.NET
Framework對PE檔案格式的擴充套件2004-08-26Framework套件
- [Java]根據檔案取得Mime Type的各種方法(轉)2016-09-12Java
- Java 根據模板生成 PDF 檔案 以及 excel 檔案2024-09-02JavaExcel
- Oracle根據表名查詢表空間及資料檔案的地址2017-02-19Oracle