导出表是应用程序的导出函数的总汇。一般情况exe没有导出表,dll会有。
移动导出表的意义:
1.能够更好的了解导出的结构。
2.方便后续做修改;
与其叫移动不如复制
移动导出表的步骤(自己的方式):
1.先新增节(为了将导出表移动到该节中)
//打开文件
FILE* fp;
fp = fopen(filename, "rb");
if (!fp)
{
printf("打开文件失败!\n");
return;
}
fseek(fp, 0, SEEK_END);
int len = ftell(fp);
fseek(fp, 0, SEEK_SET);
DWORD filebuffer = malloc(len);
if (!filebuffer)
{
printf("内存申请失败!\n");
fclose(fp);
return;
}
//将文件读取到内存中
fread(filebuffer, sizeof(char), len, fp);
if ((short)*filebuffer != 0x5a4d)
{
printf("不是有效的MZ标识!\n");
fclose(fp);
free(filebuffer);
return;
}
if (*(filebuffer + *(filebuffer + 15) / 4) != 0x4550)
{
printf("不是有效的PE标识!\n");
fclose(fp);
free(filebuffer);
return;
}
//获取PE 可选PE 节 数据目录 dos与PE标识之间的垃圾文件
WORD PE_header = filebuffer + *(filebuffer + 15) / 4 + 1;
WORD optionPE_header = PE_header + 10;
DWORD section = optionPE_header + *(PE_header + 8) / 2;
DWORD DataDirectory = optionPE_header + 48;
DWORD SizeOfImage = optionPE_header + 28;
DWORD SizeOfHeaders = optionPE_header + 30;
WORD NumberOfSections = PE_header + 1;
int rubbish = *(filebuffer + 15) - 64;
//新增节 在将导出表移动到新增节中
//判断节表中是否大于80字节
if (*SizeOfHeaders - (64 + 4 + 20 + *(PE_header + 8)+ rubbish) - (*NumberOfSections * 40) < 80)
{
printf("不能新增节!\n");
fclose(fp);
free(filebuffer);
return;
}
//在文件末尾开辟空间 memry-------------------------新增节的大小
int memry = 0x10000;
*SizeOfImage = *SizeOfImage + memry;
//添加新表
DWORD end_section = section + (*NumberOfSections-1) * 10;
DWORD new_section = section + *NumberOfSections * 10;
memcpy(new_section, new_section - 10, 40);
//修改表中的值
DWORD VirtualSize = new_section + 2;
DWORD VirtualAddress = new_section + 3;
DWORD SizeOfRawData = new_section + 4;
DWORD PointerToRawData = new_section + 5;
DWORD Characteristics = new_section + 9;
*VirtualSize = memry;
if (end_section + 2 > end_section + 4)
{
*VirtualAddress = *(end_section + 3) + *(end_section + 2);
if (*VirtualAddress % 0x1000 != 0)
{
*VirtualAddress = *VirtualAddress - *VirtualAddress % 0x1000;
*VirtualAddress = *VirtualAddress + 0x1000;
}
}
else
{
*VirtualAddress = *(end_section + 3) + *(end_section + 4);
if (*VirtualAddress % 0x1000 != 0)
{
*VirtualAddress = *VirtualAddress - *VirtualAddress % 0x1000;
*VirtualAddress = *VirtualAddress + 0x1000;
}
}
*SizeOfRawData = memry;
*PointerToRawData = *(end_section + 5) + *(end_section + 4);
//修改节的数量
*NumberOfSections = *NumberOfSections + 1;
//将文件扩大
DWORD new_filebuffer = malloc(len + memry);
memset(new_filebuffer, 0, len + memry);
memcpy(new_filebuffer, filebuffer, len);
//将内存中的数据写入到文件中
FILE* fpp;
fpp = fopen(filepath, "wb");
if (!fpp)
{
printf("文件打开失败!\n");
fclose(fp);
free(filebuffer);
free(new_filebuffer);
return;
}
fwrite(new_filebuffer, sizeof(char), len + memry, fpp);
printf("新增节成功!\n");
fclose(fp);
fclose(fpp);
2.将函数地址表复制到新增节中
int Fov = RvaToFov(filepath, *DataDirectory);
DWORD Add_section = new_filebuffer + len / 4;
DWORD ExportTable = new_filebuffer + Fov / 4;
DWORD AddressOfFunctions = ExportTable + 7;
//找到真实函数地址表 并将其复制
DWORD Functions = new_filebuffer + RvaToFov(filepath, *AddressOfFunctions) / 4;
for (int i = 0; i < *(ExportTable + 5); i++)
{
*Add_section = *Functions;
Add_section++;
Functions++;
}
DWORD new_Functions = Add_section - *(ExportTable + 5);
这里用到的RvaToFov函数是自己写的,功能是将内存中的偏移转化成文件中的偏移。
复制的思路就是先得到新增节的地址Add_section,这个就是开始复制的地址。ExportTable是导出表的地址。AddressOfFunctions是函数地址表的地址,再用RvaToFov函数得到真实的函数地址表并复制到Add_section。
3.将函数名称表复制到新增节中
DWORD AddressOfNames = ExportTable + 8;
//找到真实函数名称表 并将其复制
DWORD Functions_Name = new_filebuffer + RvaToFov(filepath, *AddressOfNames) / 4;
for (int i = 0; i < *(ExportTable + 6); i++)
{
*Add_section = *Functions_Name;
Add_section++;
Functions_Name++;
}
Functions_Name = Functions_Name - *(ExportTable + 6);
DWORD new_Functions_Name = Add_section - *(ExportTable + 6);
同样是得到函数名称表的地址,在用RvaToFo函数的到真实的表并将其复制到Add_section。需要注意的是Functions_Name是原来的地方,而new_Functions_Name是新增节中的地址。
3.将函数序号表复制到新增节中
DWORD AddressOfNameOrdinals = ExportTable + 9;
//找到真实函数序号表 并将其复制
WORD short_Add_section = Add_section; //因为函数序号表是表项是两字节
WORD Functions_Ordinals = new_filebuffer + RvaToFov(filepath, *AddressOfNameOrdinals) / 4;
for (int i = 0; i < *(ExportTable + 6); i++)
{
*short_Add_section = *Functions_Ordinals;
short_Add_section++;
Functions_Ordinals++;
}
Functions_Ordinals = Functions_Ordinals - *(ExportTable + 6);
DWORD new_Functions_Ordinals = short_Add_section - *(ExportTable + 6);
Add_section = short_Add_section;//无缝衔接下一个表的复制
思路和方法基本与复制函数地址表,函数名称表一致。其中函数序号表中的项是两字节的。
4.将名称复制到新增节中 并且将函数名称地址表中的地址修复
我的疑点大概就在这个部分,因为每个dll的名字不一样,导致函数名对应的地址不正确(函数名在dll名字后面),大概差值在4个字节内。至今不知道原因只能通过先得到dll名称地址,计算dll名称大小从而得到正确的函数名称地址。
char* addsec = Add_section; //为了一起将名称同步
char* name = new_filebuffer + RvaToFov(filepath, *Functions_Name) / 4; //每个函数名称具体指针
//=============为了精确得到每个函数名称导出的名字=============开始
char* sname = new_filebuffer + RvaToFov(filepath, *(ExportTable + 3)) / 4;
char* nums = sname;
int dllNamelen = 1;
while (*nums != 0)
{
dllNamelen++;
nums++;
}
if (*sname != 0)
{
name = sname + dllNamelen;
}
//=============为了精确得到每个函数名称导出的名字=============结束
因为这个出现奇数字节的计算所以用char*的addsec先获取Add_section。如果name得到正确的值并且sname(dll名字)不正确,那么会name不变。反过来name不正确,sname正确,name = sname + dllnamelen。dllnamelen为dll名字大小。
int ka = 0;//计算所有函数名称的总大小
int total = 0;//计算三个表的大小(地址表,序号表,名称表(名称地址表+名称))
for (int i = 0; i < *(ExportTable + 6); i++)
{
//得到name后判断长度 在复制到新增节中
int namelen = 1;
while (*name != 0)
{
namelen++;
name++;
}
name = name + 1;
name = name - namelen;
memcpy(addsec, name, namelen);
//修复名字表里面的地址
*new_Functions_Name = FovToRva(filepath, (int)(addsec - new_filebuffer));
addsec = addsec + namelen;
name = name + namelen;
new_Functions_Name++;
ka += namelen;
}
total = *(ExportTable + 5) * 4 + *(ExportTable + 6) * 4 + *(ExportTable + 5) * 2 +ka;
if (total % 4 != 0)
{
addsec = addsec + (4 - total % 4);
}
new_Functions_Name = new_Functions_Name - *(ExportTable + 6);
Add_section = addsec; //继续无缝衔接
将得到函数名称复制到Add_secton(addsec)中。修改new_Functions_Name的值,每一次复制函数名称的时候都要该函数名称地址的文件偏移转化成内存中偏移,并存储在new_Function_Name中。FovToRva函数也是我自己写的在lib中。重点注意因为我们是先复制函数表,在复制导出表,所以如果函数名称不是刚好4字节整数会导致移动完成后读取函数三表出错。我们这里采用得到所有函数名称大小判断是否为4字节整数倍,然后进行4字节对齐。再将Add_section移动到对齐后的位置。
5.复制导出表结构
memcpy(Add_section, ExportTable, 40);
//修改导出表结构中的 AddressOfFunctions AddressOfNames AddressOfNameOrdinals
AddressOfFunctions = Add_section + 7;
AddressOfNames = Add_section + 8;
AddressOfNameOrdinals = Add_section + 9;
*AddressOfFunctions = FovToRva(filepath, (int)(Functions - new_filebuffer)*4);
*AddressOfNames = FovToRva(filepath, (int)(new_Functions_Name - new_filebuffer)*4);
*AddressOfNameOrdinals = FovToRva(filepath, (int)(new_Functions_Ordinals - new_filebuffer)*4);
直接将原来的导出表复制到新增节中Add_section位置,修改AddressOfFunctions AddressOfNames AddressOfNameOrdinals用FovToRva函数转成内存偏移存储。
6.修复目录项中的值,指向新的IMAGE_EXPORT_DIRECTORY
WORD new_PE_header = new_filebuffer + *(new_filebuffer + 15) / 4 + 1;
WORD new_optionPE_header = new_PE_header + 10;
DWORD new_DataDirectory = new_optionPE_header + 48;
DWORD new_ExportTable = Add_section;
*new_DataDirectory = FovToRva(filepath, (int)(new_ExportTable - new_filebuffer) * 4);
将数据目录中的第一项的内存地址改为新增节的导出表地址即可。