首先,PE文件中的数据被载入内存后根据不同页面属性被划分成很多区块(节),并有区块表(节表)的数据来描述这些区块,这里我们需要注意一点:一个区块中的数据仅仅知识由于属性相同而放一起,并不一定是同一种用途的内容。例如输入表、输出表等就有可能和只读常量一起被放在同一个区块中。因为他们的属性都是可读不可写的。
其次,由于不同用途的数据有可能被放入同一个区块中,因此仅仅依靠区块表是无法确定和定位的。因此还需要通过PE文件头中的IMAGE_OPTIONAL_READER32结构的数据目录表来指出他们的位置,我们可以由数据目录表来定位他们的位置,我们可以由数据目录表来定位的数据包括输入表、输出表、资源、重定位表和TLS等15种数据。
导入表 导入表的作用 当程序运行时,需要多个PE文件共同组成
PE文件提供哪些功能给其他PE文件是导出表的作用
PE文件需要依赖的模块以及依赖模块中的哪些函数是导出表的作用
什么是导出表 导出表是用于记录该PE文件还需要依赖的模块以及依赖这些模块中的那些函数的一种结构
如何定位导入表 定位导入表的原理
根据之前所学可知,导入、导出等表的起始位置和大小都存放在了IMAGE_OPTIONAL_HEADERS结构的DataDirectory数组当中。而导入表对应的下标为1
宏定义
值
含义
IMAGE_DIRECTORY_ENTRY_IMPORT
1
导入表
定位导入表流程
找到扩展PE头IMAGE_OPTIONAL_HEADERS的最后一个成员DataDirectory[1]
根据DataDirectory[1].VirtualAddress 得到导入表的RVA
将导入表的RVA转为FOA,在文件中定位到导入表
根据流程定位导入表 分析demo 使用everEdit.exe
找到DataDirectory[1]
根据索引,我们找到DataDirectory[1]即第二个表导出表的数据目录
iMAGE_data_directory成员
值
说明
VirtualAddress
0x001CF47C
导出表的RVA地址
Size
0x00000140
导出表的大小
得到导出表的RVA 导出表的RVA即0x001CF47C
修改VA转FOA程序代码,求出导出表FOA
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 #include <stdio.h> #include <malloc.h> #include <windows.h> #include <winnt.h> #include <math.h> #define IMAGE_FILE_MACHINE_AMD64 0x8664 UINT VaToFoa32 (UINT va, _IMAGE_DOS_HEADER *dos,_IMAGE_NT_HEADERS* nt, _IMAGE_SECTION_HEADER** sectionArr) { UINT rva = va - nt->OptionalHeader.ImageBase; printf ("rva:%X\n" , rva); UINT PeEnd = (UINT)dos->e_lfanew+sizeof (_IMAGE_NT_HEADERS); printf ("PeEnd:%X\n" , PeEnd); if (rva < PeEnd) { printf ("foa:%X\n" , rva); return rva; } else { int i; for (i = 0 ; i < nt->FileHeader.NumberOfSections; i++) { UINT SizeInMemory = ceil ((double )max ((UINT)sectionArr[i]->Misc.VirtualSize ,(UINT)sectionArr[i]->SizeOfRawData ) / (double )nt->OptionalHeader.SectionAlignment)* nt->OptionalHeader.SectionAlignment; if (rva >= sectionArr[i]->VirtualAddress && rva < (sectionArr[i]->VirtualAddress + SizeInMemory)) { printf ("SizeInMemory:%X\n" , SizeInMemory); break ; } } if (i >= nt->FileHeader.NumberOfSections) { printf ("没有找到匹配的节\n" ); return -1 ; } else { UINT offset = rva - sectionArr[i]->VirtualAddress; UINT foa = sectionArr[i]->PointerToRawData + offset; printf ("foa:%X\n" , foa); return foa; } } } UINT VaToFoa64 (UINT va, _IMAGE_DOS_HEADER* dos, _IMAGE_NT_HEADERS64* nt, _IMAGE_SECTION_HEADER** sectionArr) { UINT rva = va - nt->OptionalHeader.ImageBase; printf ("rva:%X\n" , rva); UINT PeEnd = (UINT)dos->e_lfanew + sizeof (_IMAGE_NT_HEADERS64); printf ("PeEnd:%X\n" , PeEnd); if (rva < PeEnd) { printf ("foa:%X\n" , rva); return rva; } else { int i; for (i = 0 ; i < nt->FileHeader.NumberOfSections; i++) { UINT SizeInMemory = ceil ((double )max ((UINT)sectionArr[i]->Misc.VirtualSize ,(UINT)sectionArr[i]->SizeOfRawData ) / (double )nt->OptionalHeader.SectionAlignment)* nt->OptionalHeader.SectionAlignment; if (rva >= sectionArr[i]->VirtualAddress && rva < (sectionArr[i]->VirtualAddress + SizeInMemory)) { printf ("SizeInMemory:%X\n" , SizeInMemory); VaToFoa32 (nt->OptionalHeader.ImageBase +0x1CF47C ,dos,(_IMAGE_NT_HEADERS*)nt,sectionArr); break ; } } if (i >= nt->FileHeader.NumberOfSections) { printf ("没有找到匹配的节\n" ); return -1 ; } else { int offset = rva - sectionArr[i]->VirtualAddress; int foa = sectionArr[i]->PointerToRawData + offset; printf ("foa:%X\n" , foa); return foa; } } } int main (int argc, char * argv[]) { _IMAGE_DOS_HEADER* dos; HANDLE hFile = CreateFileA ("C:\\Users\\86156\\Desktop\\everEdit.exe" , GENERIC_READ, FILE_SHARE_READ, NULL , OPEN_EXISTING, 0 , 0 ); HANDLE hMap = CreateFileMappingA (hFile, NULL , PAGE_READONLY, 0 , 0 , 0 ); LPVOID pFile = MapViewOfFile (hMap, FILE_MAP_READ, 0 , 0 , 0 ); dos = (_IMAGE_DOS_HEADER*)pFile; printf ("dos->e_magic:%X\n" , dos->e_magic); DWORD* peId; peId = (DWORD*)((UINT)dos + dos->e_lfanew); printf ("peId:%X\n" , *peId); WORD* magic; magic = (WORD*)((UINT)peId + sizeof (DWORD) + sizeof (_IMAGE_FILE_HEADER)); printf ("magic:%X\n" , *magic); switch (*magic) { case IMAGE_NT_OPTIONAL_HDR32_MAGIC: { printf ("32位程序\n" ); _IMAGE_NT_HEADERS* nt; nt = (_IMAGE_NT_HEADERS*)peId; printf ("Machine:%X\n" , nt->FileHeader.Machine); printf ("Magic:%X\n" , nt->OptionalHeader.Magic); _IMAGE_SECTION_HEADER** sectionArr = (_IMAGE_SECTION_HEADER**) malloc (sizeof (_IMAGE_SECTION_HEADER*) * nt->FileHeader.NumberOfSections); _IMAGE_SECTION_HEADER* sectionHeader; sectionHeader = (_IMAGE_SECTION_HEADER*)((UINT)nt + sizeof (_IMAGE_NT_HEADERS)); int cnt = 0 ; while (cnt< nt->FileHeader.NumberOfSections){ _IMAGE_SECTION_HEADER* section; section = (_IMAGE_SECTION_HEADER*)((UINT)sectionHeader + sizeof (_IMAGE_SECTION_HEADER)*cnt); sectionArr[cnt++] = section; printf ("%s\n" , section->Name); } VaToFoa32 (nt->OptionalHeader.ImageBase +0x11320 ,dos,nt,sectionArr); break ; } case IMAGE_NT_OPTIONAL_HDR64_MAGIC: { printf ("64位程序\n" ); _IMAGE_NT_HEADERS64* nt; nt = (_IMAGE_NT_HEADERS64*)peId; printf ("Machine:%X\n" , nt->FileHeader.Machine); printf ("Magic:%X\n" , nt->OptionalHeader.Magic); _IMAGE_SECTION_HEADER** sectionArr = (_IMAGE_SECTION_HEADER**)malloc (sizeof (_IMAGE_SECTION_HEADER*) * nt->FileHeader.NumberOfSections); _IMAGE_SECTION_HEADER* sectionHeader; sectionHeader = (_IMAGE_SECTION_HEADER*)((UINT)nt + sizeof (_IMAGE_NT_HEADERS64)); int cnt = 0 ; while (cnt < nt->FileHeader.NumberOfSections) { _IMAGE_SECTION_HEADER* section; section = (_IMAGE_SECTION_HEADER*)((UINT)sectionHeader + sizeof (_IMAGE_SECTION_HEADER) * cnt); sectionArr[cnt++] = section; printf ("%s\n" , section->Name); } break ; } default : { printf ("error!\n" ); break ; } } getchar (); return 0 ; }
此代码本是将VA转为FOA值,但由于VA = ImageBase + RVA,我们可以修改增加一句关键代码使其RVA转FOA
1 VaToFoa32 (nt->OptionalHeader.ImageBase +0x1CF47C ,dos,nt,sectionArr);
拿到导出表文件偏移FOA:0x10720
完成定位
导入表结构 导入表个数 与导出表不同,导入表通常需要包含多个模块,而不像导出表只需要提供PE文件需要提供的导出函数即可
因此,导出表只有一个,但导入表可能会有多个
当程序运行时,需要依赖几个模块,就有对应几个导出表
导出表的结构体
在C语言中,导出表的结构如下(在winnt.h中有定义)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 typedef struct _IMAGE_IMPORT_DESCRIPTOR { union { DWORD Characteristics; DWORD OriginalFirstThunk; } DUMMYUNIONNAME; DWORD TimeDateStamp; DWORD ForwarderChain; DWORD Name; DWORD FirstThunk; } IMAGE_IMPORT_DESCRIPTOR;
成员
数据宽度
说明
Characteristics
DWORD
标志为0表示结束,没有导入描述符了
OriginalFirstThunk
DWORD
RVA指向IMAGE_THUNK_DATA结构数组 (桥1)
TimeDateStamp
DWORD
时间戳
ForwarderChain
DWORD
链表的前一个结构
Name
DWORD
RVA,指向DLL名字,该名字以‘\0’为结尾
FirstThunk
DWORD
RVA指向IMAGE_THUNK_DATA结构数组 (桥2)
Characteristics 标志 为0表示结束 没有导入描述符了
IMAGE_THUNK_DATA 在介绍OriginalFirstThunk之前,要先了解一下OriginalFirstThunk和FirstThunk所指向的结构数组
指向的数组中每一项为一个结构,此结构名称是IMAGE_THUNK_DATA
数组最后以一个内容全为0的IMAGE_THUNK_DATA作为结束
IMAGE_THUNK_DATA实际上只是一个DWORD,但在不同的时刻却拥有不同的解释
IMAGE_THUNK_DATA有两种解释 :
DWORD最高位为0,那么该数值是一个RVA,指向_IMAGE__IMPORT_BY_NAME结构,表明函数是字符串类型的函数名导入的
DWORD最高位为1,那么该数值的低31位就是函数的导出函数的序号
_IMAGE_IMPORT_BY_NAME结构:
1 2 3 4 typedef struct _IMAGE_IMPORT_BY_NAME { WORD Hint; BYTE Name[1 ]; } IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;
该结构即为:”编号—名称”(Hint/Name)描述部分
Hint:导出函数地址表的索引编号 ,可能为空且不一定准确 ,由编译器决定,一般不使用该值
Name:这个是一个以”\0”结尾的字符串,表示函数名
此时可发现,IMAGE_THUNK_DATA最终提供的数据只有两个:
DWORD最高位为0时:需要导入函数的名称(Hint不一定准确,所以不使用)
DWORD最高位为1时:需要导入的函数在导出表中的序号
此处对应了导出表笔记中的由导出表获得导出函数所需的两种方法
根据函数名称获取导出函数地址
根据函数序号获取导出函数地址
OriginalFirstThunk 因为它是指向另外数据结构的通路,因此简称为桥1。该字段指向一个包含了一系列结构的数组:IMAGE_THUNK_DATA
桥1所指向的地址列表被定义为:INT (Import Name Table) 导入名称表
导入表的双桥结构 桥1 与 桥 2 最终的目的地都是一致的,都指向了引入函数的“编号-名称”(Hint/Name)描述部分
桥1到IMAGE_THUNK_DATA的过程中,经过了:INT (Import Name Table) 导入名称表
而桥2到IMAGE_THUNK_DATA的过程中,经过了: IAT (Import Address Table)导入地址表
PE文件加载前
PE文件载入后
加载前后对比
在PE文件加载前: 桥1指向INT和桥2指向的IAT的数据是相同 的,但是其存储的位置是不同的
在PE文件加载后:桥1指向的INT不变 ,但桥2指向的IAT的数值变成了函数相应的RVA地址
另:函数相应的RVA地址是根据IAT中的函数名称或者导入表中的序号获得的。
根据结构分析导入表 回到先前得到的导入表的FOA,在16进制编辑器中跳转至FOA:0x1CDA7C位置