数据⽬录(Data Directory)有16个_IMAGE_DATA_DIRECTORY结构体元素,该结构体数组是可选PE头中最后⼀个成员。这⼗六个元素分别存储了不同信息,分别是:导⼊表、导出表、资源、异常信息、安全证书、重定位表、调试信息、版权所有、全局指针、TLS、加载配置、绑定导⼊、IAT、延迟导⼊、COM信息、最后⼀个保留未使⽤。和程序运⾏时息息相关的表有:导出表、导⼊表、重定位表、IAT表的灯,这⼏种也是PE解析中重点研究的⼏张表。
该结构体标记了各个表(元素)在内存中的VirtualAddress与Size。VirtualAddress是内存中的偏移地址,我们要直接在⽂件中根据VirtualAddress到对应的表,就需要进⾏判断。判断VirtualAddress在哪个节,并且计算在节中的偏移量,即RVA->FOA的转换。 1、导出表基本结构:
根据_IMAGE_DATA_DIRECTORY结构体数组的第1个元素索引处导出表。⼀般情况下,dll的函数导出供其他⼈使⽤,exe将别⼈的dll的函数导⼊运⾏。 所以,⼀般.exe没有导出表(但是并⾮说.exe⼀定没有导出表)。
导出表结构:
注意:
⾥⾯的地址均是RVA,⽽如果我们不想转换成ImageBuffer就⼀定要进⾏RVA->FOA转换,根据FOA直接在FileBuffer中寻导出表。每个dll都有⼀个导出表。⽽每个导出表有三个⼦导出表(地址AddressOfFunctions、名字AddressOfNames、序号 AddressOfOrdinals)。NumberOfFunctions是函数序号最⼤值与最⼩值之间的差值,NumberOfNames是函数以名字导出的个数,⼆者可以不⼀样⼤。⼀个函数必定有地址,但不⼀定有名字(如果是以⽆名字的⽅式导出,eg:func @12 NONAME)。
我们⾃定义⼀个dll库,在导出时采⽤.def⽂件的⽅式导出:typedef struct _IMAGE_DATA_DIRECTORY{ DWORD VirtualAddress ; DWORD Size ;} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY ;
1
2
3
4typedef struct _IMAGE_EXPORT_DIRECTORY { DWORD Characteristics; //未使⽤ DWORD TimeDateStamp; //时间戳 WORD MajorVersion; //未使⽤ WORD MinorVersion; //未使⽤ DWORD Name; //指向改导出表⽂件名字符串 DWORD Base; //导出表的起始序号 DWORD NumberOfFunctions; //导出函数的个数(更准确来说是AddressOfFunctions 的元素数,⽽不是函数个数) DWORD NumberOfNames; //以函数名字导出的函数个数 DWORD AddressOfFunctions; //导出函数地址表RVA:存储所有导出函数地址(表元素宽度为4,总⼤⼩NumberOfFunctions * 4) DWORD AddressOfNames; //导出函数名称表RVA:存储函数名字符串所在的地址(表元素宽度为4,总⼤⼩为NumberOfNames * 4) DWORD AddressOfNameOrdinals; //导出函数序号表RVA:存储函数序号(表元素宽度为2,总⼤⼩为NumberOfNames * 2)} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;
1
2
3
4
5
6
7
8
9
10
11
12
13//dll .def EXPORTS Plus @1 Sub @3 NONAME div @5 NONAME mul @6
1
2
3
4
5
6
则即解析得到的信息如下(“——–”代表没有名字,Ordina显⽰的值与内存⾥的值差⼀个Base,因为在⽤代码进⾏解析时已经把Base算过了,⽽内存中没有动):
导出的函数有两个函数我们声明为noname,故其在导出表中不存在名字。则其导出表的NumberOfNames = 2,NumberOfFunctions = (6-1+1) = 6。即地址表长度为6宽度为4,Size为24;名字表长度为2,宽度为4,Size为8;序号表长度为2,宽度为2,Size为4。对应的内存图如下(名字表的地址是按照从⼩到⼤排的,地址表有与我们.def中指定了序号,因此是乱序的,如果不指定,编译器⾃动分配
的⼀般也是有序的):
虽然导出时div、sub没有序号与名字,但是⼆者是有地址的。并且由于序号计算地址表时,地址表中有些值没有映射,则填充为0。⽽⽂件
中如下所⽰(导出表在⽂件中开始地址0002DF10): Offset to Export Table :[0002DF10] Characteristics : [00000000] TimeDateStamp : [59945264] MajorVersion : [0000] MinorVersion : [0000] NameAddr : [0002DF5C] NameString : [dll.dll] Base :
[00000001] NumberOfFunctions : [00000006] NumberOfNames : [00000002] AddressOfFunctions : [0002DF38] AddressOfNames : [0002DF50] AddressOfNameOrdinals : [0002DF58] Ordina func_FOA name_FOA FunctionName 0001 00001005 0002DF68 plus 0003 00001019 -------- -------- 0005 00015060 -------- -------- 0006 00001014 0002DF64 mul
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
①导出表中AddressOfFunction指向的地址表⼤⼩根据 NumberOfFunctions 决定:地址表⼤⼩ = NumberOfFunctions * 4; ②⽽AddressOfNames指向的名字表⼤⼩不由 NumberOfFunctions 决定,⽽由NumberOfNames决定:名字表⼤⼩ =NumberOfNames * 4;
③AddressOfNameOrdinals指向的序号表中的值是⾮准确的,应该均加上Base才是真正的序号(Base等于序号表中最⼩的值)。⽽序号表⼤⼩ = NumberOfNames * 2。
④地址表可能⼤于等于名字表,也有可能⼩于名字表,因为⼀个函数可能没有名字,也可能有多个名字。但是⼀般情况下,名字表均不会⼤于地址表。并且⼀个函数必然有地址,不⼀定有名字,名字表和序号表⼀⼀对应。
2、导出表解析:
知道⼀个函数名字func,如何到其在PE⽂件中的地址?步骤(根据三张⼦表查):
①在名字表遍历RVA地址,转换成FOA地址,然后根据FOA⽐较FOA指向的字符串与func是否相等,不相等则判断下⼀个。 ②如果相等则获取到其在名字表中的索引(下标),根据该索引获取对应的序号表中同⼀下标索引到的序号值value。
③value作为地址表的索引,索引到的值即为func()的地址。
也就是我们上⾯图中所描述的。
生态养猪场RVA->FOA的转换函数如下:
导出表的解析实现如下(省略⽂件到内存的读⼊)://输⼊RVA (内存相对偏移地址),返回FOA (⽂件偏移地址)DWORD PETool::RVAToFOA(DWORD imageAddr){ /* * 相对虚拟地址转⽂件偏移地址 * ①获取Section 数⽬ * ②获取SectionAlignment * ③判断需要转换的RVA 位于哪个Section 中(section[n]), * offset = 需要转换的RVA-VirtualAddress ,计算出RVA 相对于本节的偏移地址 * ④section[n].PointerToRawData + offset 就是RVA 转换后的FOA */ if (imageAddr > imageSize){ printf("RVAToFOA in_addr is error!%08X\n",imageAddr); exit(EXIT_FAILURE); } if (imageAddr < section_header[0].PointerToRawData){ return imageAddr;//在头部(包括节表与对齐)则直接返回 } IMAGE_SECTION_HEADER * section = section_header; DWORD offset = 0; for (int i = 0; i < sectionNum; i++){ DWORD lower = section[i].VirtualAddress;//该节下限 DWORD upper = section[i].VirtualAddress+section[i].Misc.VirtualSize;//该节上限 if (imageAddr >= lower && imageAddr <= upper){ offset = imageAddr - lower + section[i].PointerToRawData;//计算出RVA 的FOA break ; } } return offset;}接触式位移传感器
1
2
3
4
5
6
7
8
9
10
11
缘11412
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
void PETool::print_ExportTable()
{
fprintf(fp_peMess, "导出表(export table):\n");
if(dataDir[0].VirtualAddress == 0){
fprintf(fp_peMess, "\t不存在导出表!\n");
return;
}
DWORD offset = RVAToFOA(dataDir[0].VirtualAddress);
IMAGE_EXPORT_DIRECTORY * exportTb = (IMAGE_EXPORT_DIRECTORY * )(pFileBuffer + offset);
fprintf(fp_peMess, "\tOffset to Export Table:[%08X]\n",dataDir[0].VirtualAddress);
fprintf(fp_peMess, "\tCharacteristics: [%08X]\n", exportTb->Characteristics);
fprintf(fp_peMess, "\tTimeDateStamp: [%08X]\n", exportTb->TimeDateStamp);
fprintf(fp_peMess, "\tMajorVersion: [%04X]\n", exportTb->MajorVersion);
fprintf(fp_peMess, "\tMinorVersion: [%04X]\n", exportTb->MinorVersion);
fprintf(fp_peMess, "\tNameAddr: [%08X]\n", exportTb->Name);
fprintf(fp_peMess, "\tNameString: [%s]\n", pFileBuffer + RVAToFOA(exportTb->Name));
fprintf(fp_peMess, "\tBase: [%08X]\n", exportTb->Base);
fprintf(fp_peMess, "\tNumberOfFunctions: [%08X]\n", exportTb->NumberOfFunctions);
fprintf(fp_peMess, "\tNumberOfNames: [%08X]\n", exportTb->NumberOfNames);
fprintf(fp_peMess, "\tAddressOfFunctions: [%08X]\n", exportTb->AddressOfFunctions);
fprintf(fp_peMess, "\tAddressOfNames: [%08X]\n", exportTb->AddressOfNames);
fprintf(fp_peMess, "\tAddressOfNameOrdinals: [%08X]\n", exportTb->AddressOfNameOrdinals);
//打印导出表
fprintf(fp_peMess, "\n\tOrdina\tfunc_FOA\tname_FOA\tFunctionName\n");
DWORD * addrFunc = (DWORD *)(pFileBuffer + RVAToFOA(exportTb->AddressOfFunctions));
DWORD * addrName = (DWORD *)(pFileBuffer + RVAToFOA(exportTb->AddressOfNames));
WORD * addrOrdi = (WORD *)(pFileBuffer + RVAToFOA(exportTb->AddressOfNameOrdinals));
//Base--->NumberOfFunctions
DWORD i, j;
for(i = 0; i < exportTb->NumberOfFunctions; i++){//导出时序号有NumberOfFunctions个
if(addrFunc[i] == 0){
continue;//地址值为0代表该序号没有对应的函数,是空余的
}
for(j = 0; j < exportTb->NumberOfNames; j++){//序号表序号有NumberOfNames个
if(addrOrdi[j] == i){//序号表的值为地址表的索引
钢结构安装fprintf(fp_peMess, "\t%04X\t%08X\t%08X\t%s\n", i + exportTb->Base, addrFunc[i], addrName[j], pFileBuffer + addrName[j]);
break;
}
}睫毛器
//存在addrOrdi[j]时,i(索引)等于addrOrdi[j](值),不存在,则i依旧有效,i+Base依旧是序号
if(j != exportTb->NumberOfNames){
continue;//在序号表中到
台历架
}
else{//如果在序号表中没有到地址表的索引,说明函数导出是以地址导出的,匿名函数
fprintf(fp_peMess, "\t%04X\t%08X\t%s\t%s\n", i + exportTb->Base, addrFunc[i], "--------", "--------");
}
}
}