OriginalFirstThunk与FirstThunk指向的是同一个数据结构,在PE文件中既可以通过OriginalFirstThunk来找到函数名,也可以通过FirstThunk来找到函数名,为什么会出现两个指针指向同一个数据结构的现象呢,其实这个与PE文件的加载有关第一个数组(由
OriginalFirstThunk 所指向)是单独的一项,而且不能被改写,我们前边称为 INT。第二个数组(由 FirstThunk
所指向)事实上是由 PE 装载器重写的。PE 装载器首先搜索 OriginalFirstThunk
,找到之后加载程序迭代搜索数组中的每个指针,找到每个 IMAGE_IMPORT_BY_NAME
结构所指向的输入函数的地址,然后加载器用函数真正入口地址来替代由 FirstThunk 数组中的一个入口,也就是说此时的FirstThunk
不在指向这个INAGE_IMPORT_BY_NAME结构,而是真实的函数的RVA。因此我们称为输入地址表(IAT)。所以,当我们的 PE
文件装载内存后准备执行时,刚刚的图就会转化为下图:
最近又在准备CTF,也是最近三四天才想起来PE还差几个表就解析完了,也算是在论坛上做笔记吧,这次来打印导入表,导入表的作用就不详细说了……IDE:VS2013
系统:win7
首先在数据目录项中找到找到导入表的偏移,导入表是数据目录项的第二位,里边的VirtualAddress是给出了导入表的RVA:
typedef struct _IMAGE_DATA_DIRECTORY {
DWORD VirtualAddress;
DWORD Size;
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
#define IMAGE_NUMBEROF_DIRECTORY_ENTRIES 16
自己编写一个从RVA转成FOA的函数:
DWORD RVATOFOA(IN DWORD RVA,OUT DWORD FOA,IN LPVOID pFileBuffer){
PIMAGE_DOS_HEADER pDosHeader = NULL;
PIMAGE_NT_HEADERS pNtHeaders = NULL;
PIMAGE_FILE_HEADER pFileHeader = NULL;
PIMAGE_OPTIONAL_HEADER pOptionalHeader = NULL;
PIMAGE_SECTION_HEADER pSectionHeader = NULL;
pDosHeader = (PIMAGE_DOS_HEADER)pFileBuffer;
pNtHeaders = (PIMAGE_NT_HEADERS)((DWORD)pFileBuffer + pDosHeader->e_lfanew);
pFileHeader = (PIMAGE_FILE_HEADER)(((DWORD)pNtHeaders) + 4);
pOptionalHeader = (PIMAGE_OPTIONAL_HEADER)((DWORD)pFileHeader + IMAGE_SIZEOF_FILE_HEADER); pSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)pOptionalHeader + pFileHeader->SizeOfOptionalHeader); size_t count = 0; //先判断在哪个节里边
for (count = 1; RVA >(pSectionHeader->VirtualAddress+pSectionHeader->Misc.VirtualSize);count++,pSectionHeader++);
FOA = RVA - pSectionHeader->VirtualAddress + pSectionHeader->PointerToRawData;
return FOA;
}
DWORD RVATOFOA(IN DWORD RVA,OUT DWORD FOA,IN LPVOID pFileBuffer){
PIMAGE_DOS_HEADER pDosHeader = NULL;
PIMAGE_NT_HEADERS pNtHeaders = NULL;
PIMAGE_FILE_HEADER pFileHeader = NULL;
PIMAGE_OPTIONAL_HEADER pOptionalHeader = NULL;
PIMAGE_SECTION_HEADER pSectionHeader = NULL;
pDosHeader = (PIMAGE_DOS_HEADER)pFileBuffer;
pNtHeaders = (PIMAGE_NT_HEADERS)((DWORD)pFileBuffer + pDosHeader->e_lfanew);
pFileHeader = (PIMAGE_FILE_HEADER)(((DWORD)pNtHeaders) + 4);
pOptionalHeader = (PIMAGE_OPTIONAL_HEADER)((DWORD)pFileHeader + IMAGE_SIZEOF_FILE_HEADER); pSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)pOptionalHeader + pFileHeader->SizeOfOptionalHeader); size_t count = 0; //先判断在哪个节里边
for (count = 1; RVA >(pSectionHeader->VirtualAddress+pSectionHeader->Misc.VirtualSize);count++,pSectionHeader++);
FOA = RVA - pSectionHeader->VirtualAddress + pSectionHeader->PointerToRawData;
return FOA;
}
转换完成之后就能在文件中定位导入表的位置,当看到导入表的格式之后觉得有点诡异:
typedef struct _IMAGE_IMPORT_DESCRIPTOR {
union {
DWORD Characteristics; // 0 for terminating null import descriptor
DWORD OriginalFirstThunk; // RVA to original unbound IAT (PIMAGE_THUNK_DATA)
} DUMMYUNIONNAME;
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;
typedef IMAGE_IMPORT_DESCRIPTOR UNALIGNED *PIMAGE_IMPORT_DESCRIPTOR;
typedef struct _IMAGE_IMPORT_DESCRIPTOR {
union {
DWORD Characteristics; // 0 for terminating null import descriptor
DWORD OriginalFirstThunk; // RVA to original unbound IAT (PIMAGE_THUNK_DATA)
} DUMMYUNIONNAME;
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;
typedef IMAGE_IMPORT_DESCRIPTOR UNALIGNED *PIMAGE_IMPORT_DESCRIPTOR;
这个OriginalFirstThunk跟这个FirstThunk这么像没有道理吧,到了后边有更加诡异的事情发生,在OriginalFirstThunk里边存储的是一个RVA指向的是这个结构体:
typedef struct _IMAGE_THUNK_DATA32 {
union {
DWORD ForwarderString; // PBYTE
DWORD Function; // PDWORD
DWORD Ordinal;
DWORD AddressOfData; // PIMAGE_IMPORT_BY_NAME
} u1;
} IMAGE_THUNK_DATA32;
typedef IMAGE_THUNK_DATA32 * PIMAGE_THUNK_DATA32;
typedef struct _IMAGE_THUNK_DATA32 {
union {
DWORD ForwarderString; // PBYTE
DWORD Function; // PDWORD
DWORD Ordinal;
DWORD AddressOfData; // PIMAGE_IMPORT_BY_NAME
} u1;
} IMAGE_THUNK_DATA32;
typedef IMAGE_THUNK_DATA32 * PIMAGE_THUNK_DATA32;
但是!!!FIrstThunk指向的也是这个结构体,刚开始看的时候就觉得这特码有病吧,一个导入表结构体里边有两个指针指向同一个数据结构?等到后来把所有的导入表打印出来之后就有更诡异的事情发生了,先不说,嘿嘿。这个THUNK结构体里边是一个联合体我们就可以把这个结构体看成是一个双字四字节的数据,最高位是标志位如果是1那么代表的是按序号导入,除去最高位之外的31位代表的是导出序号,如果是0代表的是按照名称导入,除去最高位之外的31位代表的是一个偏移指向一个新的结构体:
typedef struct _IMAGE_IMPORT_BY_NAME {
WORD Hint;
CHAR Name[1]
;} IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;
typedef struct _IMAGE_IMPORT_BY_NAME {
WORD Hint;
CHAR Name[1]
;} IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;
这个时候自己刚开始想到的判断方式是&0x80000000,但是!!!windows给出了一个函数来直接判断该函数的定义是:
#define IMAGE_SNAP_BY_ORDINAL32(Ordinal) ((Ordinal & IMAGE_ORDINAL_FLAG32) != 0)
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)