PE
文件是任何可执行模块或者DLL的文件格式。PE文件以64字节的DOS文件头(IMAGE_DOS_HEADER,)开始,之后是一小段DOS程序(DOS头的概念是从16位的WINDOWS可执行程序(NE格式)中来的,这个部分主要用在OS/2可执行程序、自解压文档及其他应用程序),然后是248字节的NT文件头(IMAGE_NT_HEADERS),NT文件头的偏移地址由IMAGE_DOS_HEADER结构的e_lfanew给出。PE文件通常保留了16个数据目录,最常见就是导入表,导出表,资源和重定位表。
windows为DOS文件标记和PE文件标记都定义了宏标记:
#difine IMAGE_DOS_SIGNATURE 0x5A4D //MZ(可以用ultraedit看到)
#difine IMAGE_NT_SIGNATURE 0x00004550 // PE00
这两个宏,主要用来判断该文件是否位PE文件。
列举出该可执行文件包含的模块中的导入函数,及其导入函数的地址:
HMODULE hMod = ::GetModuleHandle(NULL);
IMAGE_DOS_HEADER* pDosHeader = (IMAGE_DOS_HEADER*)hMod;
IMAGE_OPTIONAL_HEADER * pOptHeader =
(IMAGE_OPTIONAL_HEADER *)((BYTE*)hMod + pDosHeader->e_lfanew + 24);
IMAGE_IMPORT_DESCRIPTOR* pImportDesc = (IMAGE_IMPORT_DESCRIPTOR*)
((BYTE*)hMod + pOptHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);
while(pImportDesc->FirstThunk)
{
char* pszDllName = (char*)((BYTE*)hMod +pImportDesc->Name);
printf("\n模块名称:%s \n", pszDllName);
// 一个IMAGE_THUNK_DATA就是一个双字,它指定了一个导入函数
IMAGE_THUNK_DATA* pThunk = (IMAGE_THUNK_DATA*)
((BYTE*)hMod + pImportDesc->OriginalFirstThunk);
int n = 0;
while(pThunk->u1.Function)
{
// 取得函数名称。hint/name表前两个字节是函数的序号,后4个字节是函数名称字符串的地址
char* pszFunName = (char*)
((BYTE*)hMod + (DWORD)pThunk->u1.AddressOfData + 2);
// 取得函数地址。IAT表就是一个DWORD类型的数组,每个成员记录一个函数的地址
PDWORD lpAddr = (DWORD*)((BYTE*)hMod + pImportDesc->FirstThunk) + n;
// 打印出函数名称和地址
printf(" 从此模块导入的函数:%-25s,", pszFunName);
printf("函数地址:%X \n", lpAddr);
n++; pThunk++;
}
pImportDesc++;
}
PE文件中的重要结构:
typedef struct _IMAGE_DOS_HEADER { // DOS .EXE header
...........
.........
LONG e_lfanew; // File address of new exe header
} IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;
typedef struct _IMAGE_NT_HEADERS {
DWORD Signature;
IMAGE_FILE_HEADER FileHeader;
IMAGE_OPTIONAL_HEADER32 OptionalHeader;
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;
typedef struct _IMAGE_OPTIONAL_HEADER {
.....
.....
IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;
typedef struct _IMAGE_DATA_DIRECTORY {
DWORD VirtualAddress; // 指向IMAGE_IMPORT_DESCRIPTOR的指针值
DWORD Size;
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
typedef struct _IMAGE_IMPORT_DESCRIPTOR {
union {
DWORD Characteristics; // 0 for terminating null import descriptor
DWORD OriginalFirstThunk; // RVA to original unbound IAT (PIMAGE_THUNK_DATA)
};
DWORD TimeDateStamp; // 0 if not bound,
// -1 if bound, and real date\time stamp
// in IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT (new BIND)
// O.W. date/time stamp of DLL bound to (Old BIND)
DWORD ForwarderChain; // -1 if no forwarders
DWORD Name;
DWORD FirstThunk; // RVA to IAT (if bound this IAT has actual addresses)
} IMAGE_IMPORT_DESCRIPTOR;
注意:
1、PE格式文件中经常用到RVA,即相关虚拟地址,用在不知道基地址的情况下
表示一个内存地址。它需要加上基地址才能得到线性地址(Linear address)。
2、DWORD OriginalFirstThunk;表示函数(hint/name)表的偏移量,记录导入函数名称
3、DWORD Name; 模块的名称
4、DWORD FirstThunk;:IAT(导入地址表)的偏移量,记录导入函数地址
typedef struct _IMAGE_THUNK_DATA32 {
union {
PBYTE ForwarderString;
PDWORD Function;
DWORD Ordinal;
PIMAGE_IMPORT_BY_NAME AddressOfData;
} u1;
} IMAGE_THUNK_DATA32;
typedef struct _IMAGE_IMPORT_BY_NAME {
WORD Hint;
BYTE Name[1]; //函数名称
} IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;