1.通过DOS头部找到PE头部
DOS头部的数据结构如下:
_IMAGE_DOS_HEADER
+0x000 e_magic : Uint2B
+0x002 e_cblp : Uint2B
+0x004 e_cp : Uint2B
+0x006 e_crlc : Uint2B
+0x008 e_cparhdr : Uint2B
+0x00a e_minalloc : Uint2B
+0x00c e_maxalloc : Uint2B
+0x00e e_ss : Uint2B
+0x010 e_sp : Uint2B
+0x012 e_csum : Uint2B
+0x014 e_ip : Uint2B
+0x016 e_cs : Uint2B
+0x018 e_lfarlc : Uint2B
+0x01a e_ovno : Uint2B
+0x01c e_res : [4] Uint2B
+0x024 e_oemid : Uint2B
+0x026 e_oeminfo : Uint2B
+0x028 e_res2 : [10] Uint2B
+0x03c e_lfanew : Int4B
偏移0x3c处是PE头部的相对虚拟地址。
2.通过PE头部找到数据目录项
PE头部的数据结构如下:
typedef struct _IMAGE_NT_HEADERS {
DWORD Signature;
IMAGE_FILE_HEADER FileHeader;
IMAGE_OPTIONAL_HEADER32 OptionalHeader;
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;
_IMAGE_FILE_HEADER
+0x000 Machine : Uint2B
+0x002 NumberOfSections : Uint2B
+0x004 TimeDateStamp : Uint4B
+0x008 PointerToSymbolTable : Uint4B
+0x00c NumberOfSymbols : Uint4B
+0x010 SizeOfOptionalHeader : Uint2B
+0x012 Characteristics : Uint2B
_IMAGE_OPTIONAL_HEADER
+0x000 Magic : Uint2B
+0x002 MajorLinkerVersion : UChar
+0x003 MinorLinkerVersion : UChar
+0x004 SizeOfCode : Uint4B
+0x008 SizeOfInitializedData : Uint4B
+0x00c SizeOfUninitializedData : Uint4B
+0x010 AddressOfEntryPoint : Uint4B
+0x014 BaseOfCode : Uint4B
+0x018 BaseOfData : Uint4B
+0x01c ImageBase : Uint4B
+0x020 SectionAlignment : Uint4B
+0x024 FileAlignment : Uint4B
+0x028 MajorOperatingSystemVersion : Uint2B
+0x02a MinorOperatingSystemVersion : Uint2B
+0x02c MajorImageVersion : Uint2B
+0x02e MinorImageVersion : Uint2B
+0x030 MajorSubsystemVersion : Uint2B
+0x032 MinorSubsystemVersion : Uint2B
+0x034 Win32VersionValue : Uint4B
+0x038 SizeOfImage : Uint4B
+0x03c SizeOfHeaders : Uint4B
+0x040 CheckSum : Uint4B
+0x044 Subsystem : Uint2B
+0x046 DllCharacteristics : Uint2B
+0x048 SizeOfStackReserve : Uint4B
+0x04c SizeOfStackCommit : Uint4B
+0x050 SizeOfHeapReserve : Uint4B
+0x054 SizeOfHeapCommit : Uint4B
+0x058 LoaderFlags : Uint4B
+0x05c NumberOfRvaAndSizes : Uint4B
+0x060 DataDirectory : [16] _IMAGE_DATA_DIRECTORY
PE头部的DataDirectory相对于PE头部的偏移为0x78。
DataDirectory的第一项为导出表目录。
_IMAGE_DATA_DIRECTORY
+0x000 VirtualAddress : Uint4B
+0x004 Size : Uint4B
可以根据第一个字段寻找到导出表。
3.导出表数据结构
typedef struct _IMAGE_EXPORT_DIRECTORY {
DWORD Characteristics;
DWORD TimeDateStamp;
WORD MajorVersion;
WORD MinorVersion;
DWORD Name;
DWORD Base;
DWORD NumberOfFunctions;
DWORD NumberOfNames;
DWORD AddressOfFunctions; // RVA from base of image
DWORD AddressOfNames; // RVA from base of image
DWORD AddressOfNameOrdinals; // RVA from base of image
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;
NumberOfFunctions字段是总的导出函数个数;NumberOfNames字段是有名字的函数个数;AddressOfNames是函数的名称字符串相对虚拟地址;AddressOfNameOrdinals是与AddressOfNames一一对应的此函数在AddressOfFunctions里的索引;AddressOfFunctions存的函数的相对虚拟地址。
4.遍历导出表
遍历导出表中NumberOfNames个有名称的函数,并根据AddressOfNameOrdinals里的索引找到其在AddressOfFunctions里存放的地址。
以遍历user32.dll的导出表为例:
#include <stdio.h>
#include <Windows.h>
int main()
{
HMODULE hDll = LoadLibraryA("user32.dll");
if (!hDll)
return 0;
IMAGE_EXPORT_DIRECTORY* exportDir;
int baseAddr = (int)hDll;
int RVA, VA;
RVA = *((int*)(baseAddr + 0x3c));
VA = baseAddr + RVA; // File address of new exe header
VA += 0x78; //DataDirectory
RVA = *((int*)VA);
exportDir = (IMAGE_EXPORT_DIRECTORY*)(baseAddr + RVA);
int* RVAFunctions = (int*)(baseAddr + exportDir->AddressOfFunctions);
int* RVANames = (int*)(baseAddr + exportDir->AddressOfNames);
short* Ordinals = (short*)(baseAddr + exportDir->AddressOfNameOrdinals);
int i, VAFunction, ordinal;
int numName = exportDir->NumberOfNames;
for (i = 0; i < numName; i++)
{
ordinal = *(Ordinals + i);
RVA = *(RVAFunctions + ordinal);
VAFunction = baseAddr + RVA;
RVA = *(RVANames + i);
VA = baseAddr + RVA;
printf("%d: %s - 0x%x\n", ordinal, (char*)VA, VAFunction);
}
return 0;
}
验证正确性:
在代码语句最后调用MessageBoxA函数。控制台中打印的关于MessageBoxA的地址为0x775b7e60:
再调试进入MessageBoxA函数,起始地址也为0x775b7e60: