移动导出表
1.为什么要移动pe结构中的表
1】这些表是编译器生成的,里面存储了非常重要的信息;
比如,导出表中粗存了该pe文件有哪些导出函数并且这些函数的地址在哪;
2】在程序启动的时候,系统会根据这些表做初始化的工作:
比如,将用到的DLL中的函数地址存储到IAT表中.
3】为了保护程序,可以对.exe的二进制代码进行加密操作,
但问题是:各种表的信息与客户字节的代码和数据都混在一起了,各种表、代码、数据都在pe文件的节中
如果进行加密,那系统在初始化的时候会出问题!因为加密后表和原来不一样无法正常初始化;
因此一般会建一个新的节,并将pe中的表移动到新的节中,然后将代码和数据加密;
注意:加密时pe头是不能动的,否则程序根本无法初始化;
4】总之:学会移动各种表,是对程序加密/破解的基础.
2.移动导出表大概步骤
第一步:在DLL中新增一个节,并返回新增后的FOA
第二步:复制AddressOfFunctions
长度:4*NumberOfFunctions
第三步:复制AddressOfNameOrdinals
长度:NumberOfNames*2
第四步:复制AddressOfNames
长度:NumberOfNames*4
第五步:复制所有的函数名
长度不确定,复制时直接修复AddressOfNames
第六步:复制IMAGE_EXPORT_DIRECTORY结构
第七步:修复IMAGE_EXPORT_DIRECTORY结构中的
AddressOfFunctions
AddressOfNameOrdinals
AddressOfNames
第八步:修复目录项中的值,指向新的IMAGE_EXPORT_DIRECTORY
3.移动导出表的思路
预期目标:
修改测试用的DllHello.dll,新增一个节 .export 将导出表放入该节;
DllHello.dll中有4个导出函数,分别用来对两个数做加减乘除并返回结果;
在main函数中测试修改后的dll,依然能正常使用则修改成功;
1)新增一个节
新增节需要的步骤:插入节表、插入节、修改pe头中的NumberOfSectons、修改可选pe头的SizeOfImage;
插入节表:
判断空间是否足够,需要在节表尾部和头部结束地址(可选pe头SizeOfHeaders)之间有两个节表的空间40x2=80个字节,一个用来放新节表,另一个用来放全0的40个字节;
也就是第一个节表中的属性节的文件偏移 (PointerToRawData - 最后一个节表的末尾地址) >= 80;
如图:DllHello.dll中节表结束的地址为0298,SizeOfHeaders=1000;1000-0290>80;有足够的空间放节表;
然后在最后一个节表的尾部添加新节表的数据即可;
为了符合pe结构的规则,新节表后面的40个字节补0,可以用memset(新节表尾部地址, 0, 40)函数;
插入新的节:
要考虑新节的大小,新的节要能够放入:导出表、地址表、名字表、序号表、通过名字表地址找到的函数名;
导出表是一个固定的结构,大小 = 40个字节;
地址表 = 4字节 x 导出表中的导出函数个数NumberOfFunctions;
名字表 = 4字节 x 导出表中的以名字导出的函数个数NumberOfNames;
序号表 = 2字节 x NumberOfNames;
函数名 = 遍历查找每个函数名的长度并累加;注意函数名字符串结尾的结束标志0不能丢;
然后再看DllHello.dll的文件对齐和内存对齐:
1000个字节足够放了,且正好满足对齐;
因此需要插入一个全0的1000个字节的空间到文件最后;
修改相关pe文件头结构属性:
pe头中的NumberOfSectons + 1;
可选pe头的SizeOfImage + 1000;
2)移动导出表
将导出表、地址表、名字表、序号表、函数名拷贝到新节;
修改可选pe头中的数据目录中导出表的VirtualAddress执行新的导出表;注意是RVA;
新导出表的AddressOfFunctions、AddressOfNames、AddressOfNameOrdinals ;这里是RVA;
3.程序实现导出表的移动
petools中添加一个函数,用来将FOA转成RVA:
//将文件偏移转为内存偏移 DWORD FoaToRva(IN LPVOID pFileBuffer, IN DWORD foa){ //定义文件头 PIMAGE_DOS_HEADER dosHeader = NULL; //dos头指针 PIMAGE_NT_HEADERS ntHeader = NULL; //nt头指针 PIMAGE_FILE_HEADER peHeader = NULL; //pe头指针 PIMAGE_OPTIONAL_HEADER32 opHeader = NULL; //可选pe头指针 PIMAGE_SECTION_HEADER sectionHeader = NULL; //节表指针 //找到文件头 dosHeader = (PIMAGE_DOS_HEADER)pFileBuffer; ntHeader = (PIMAGE_NT_HEADERS) ((DWORD)pFileBuffer + dosHeader->e_lfanew); peHeader = (PIMAGE_FILE_HEADER) ((DWORD)ntHeader + 4); opHeader = (PIMAGE_OPTIONAL_HEADER32) ((DWORD)peHeader + IMAGE_SIZEOF_FILE_HEADER); sectionHeader = (PIMAGE_SECTION_HEADER) ((DWORD)opHeader + peHeader->SizeOfOptionalHeader); //1.判断是哪个节 int sec = -1; for(int i=0;i<peHeader->NumberOfSections;i++){ DWORD fa = (sectionHeader+i)->PointerToRawData; DWORD size = (sectionHeader+i)->SizeOfRawData; if(foa >=fa && foa<(fa+size) ){ sec = i; //printf("在第%d个节\n",sec); break; } } if(sec<0){ printf("文件偏移不在任何一个节\n"); return 0; } //2.转换 DWORD secOffset = foa - (sectionHeader + sec)->PointerToRawData; DWORD rva = (sectionHeader + sec)->VirtualAddress + secOffset; return rva; }
移动导出表:
#include "stdafx.h" #include "PeTool.h" #include "string.h" #define SRC "C:\\Users\\Administrator\\Desktop\\DllHello.dll" #define DEST "C:\\Users\\Administrator\\Desktop\\DllHello_new.dll" //新增一个节 DWORD addSec(LPVOID pFileBuffer, LPVOID* pSec){ //1.定义pe头结构指针 PIMAGE_DOS_HEADER dosHeader = NULL; //dos头指针 PIMAGE_FILE_HEADER peHeader = NULL; //pe头指针 PIMAGE_OPTIONAL_HEADER32 opHeader = NULL; //可选pe头指针 PIMAGE_SECTION_HEADER seHeader = NULL; //节表指针 //2.初始化头结构指针 dosHeader = (PIMAGE_DOS_HEADER)pFileBuffer; if(dosHeader->e_magic != IMAGE_DOS_SIGNATURE){ printf("不是有效MZ标记\n"); return 0; } if(*((PDWORD)((DWORD)pFileBuffer + dosHeader->e_lfanew)) != IMAGE_NT_SIGNATURE){ printf("不是有效PE标记\n"); free(pFileBuffer); return 0; } peHeader = (PIMAGE_FILE_HEADER) ((DWORD)pFileBuffer + dosHeader->e_lfanew + 4); opHeader = (PIMAGE_OPTIONAL_HEADER32) ((DWORD)peHeader + IMAGE_SIZEOF_FILE_HEADER); seHeader = (PIMAGE_SECTION_HEADER) ((DWORD)opHeader + peHeader->SizeOfOptionalHeader); //3.新增一个节表 PIMAGE_SECTION_HEADER newSec = seHeader + peHeader->NumberOfSections; //新节表的指针 if(((DWORD)pFileBuffer + opHeader->SizeOfHeaders - (DWORD)newSec) < 80){ printf("空间不足插入新的节表\n"); return 0; } //4.设置新节表 strcpy((char*)newSec->Name, ".export"); newSec->Misc.VirtualSize = 0x1000; //新节的内存镜像大小为1000 newSec->VirtualAddress = opHeader->SizeOfImage; //新节的内存偏移为内存镜像大小 newSec->SizeOfRawData = 0x1000; //新节的文件镜像大小为1000 PIMAGE_SECTION_HEADER lastSec = seHeader + (peHeader->NumberOfSections -1); //最后一个节表 newSec->PointerToRawData = lastSec->PointerToRawData + lastSec->SizeOfRawData; //新节的文件偏移紧接最后一个节 newSec->Characteristics = seHeader->Characteristics; //新节的属性和第一个节一样即可 //5.设置全0节表 memset((LPVOID)(newSec+1), 0, 40); //6.修头信息 peHeader->NumberOfSections = peHeader->NumberOfSections + 1; opHeader->SizeOfImage = opHeader->SizeOfImage + 0x1000; //7.申请内存 LPVOID sec = malloc(0x1000); if(!sec){ printf("给新节申请内存失败\n"); return 0; } memset(sec, 0, 0x1000); //8.返回 *pSec = sec; return 0x1000; } //移动导出表 void moveExport(){ //定义pe头结构指针 PIMAGE_DOS_HEADER dosHeader = NULL; //dos头指针 PIMAGE_FILE_HEADER peHeader = NULL; //pe头指针 PIMAGE_OPTIONAL_HEADER32 opHeader = NULL; //可选pe头指针 PIMAGE_DATA_DIRECTORY dataDir = NULL; //数据目录指针 PIMAGE_EXPORT_DIRECTORY exportDir = NULL; //导出表指针 //1.将文件读入内存 LPVOID pFileBuffer = NULL; DWORD fileSize = ReadPEFile(SRC, &pFileBuffer); if(!pFileBuffer){ printf("读取dll文件失败\n"); return; } //2.新增一个节 LPVOID newSec = NULL; DWORD secSize = addSec(pFileBuffer, &newSec); if(!newSec){ printf("新增节失败\n"); return; } //3.初始化头结构指针 dosHeader = (PIMAGE_DOS_HEADER) pFileBuffer; peHeader = (PIMAGE_FILE_HEADER) ((DWORD)pFileBuffer + dosHeader->e_lfanew + 4); opHeader = (PIMAGE_OPTIONAL_HEADER32) ((DWORD)peHeader + IMAGE_SIZEOF_FILE_HEADER); dataDir = opHeader ->DataDirectory; exportDir = (PIMAGE_EXPORT_DIRECTORY) ((DWORD)pFileBuffer + RvaToFileOffset(pFileBuffer, dataDir->VirtualAddress)); //4.复制地址表 LPVOID copyDest = newSec; LPDWORD newFunAddr = (LPDWORD)copyDest; LPVOID funAddr= (LPVOID) ((DWORD)pFileBuffer + RvaToFileOffset(pFileBuffer, exportDir->AddressOfFunctions)); memcpy(newSec, funAddr, exportDir->NumberOfFunctions * 4); //5.复制序号表 copyDest = (LPVOID)((DWORD)newSec + exportDir->NumberOfFunctions * 4); //复制到哪里 LPWORD newOrdAddr = (LPWORD)copyDest; LPVOID ordAddr = (LPVOID)((DWORD)pFileBuffer + RvaToFileOffset(pFileBuffer, exportDir->AddressOfNameOrdinals)); memcpy(copyDest, ordAddr, exportDir->NumberOfNames * 2); //6.复制名字表 copyDest = (LPVOID) ((DWORD)copyDest + exportDir->NumberOfNames * 2); LPDWORD newNameAddr = (LPDWORD) copyDest; LPDWORD nameAddr = (LPDWORD)((DWORD)pFileBuffer + RvaToFileOffset(pFileBuffer, exportDir->AddressOfNames)); memset(copyDest, 0, exportDir->NumberOfNames * 4); //因为名字表要修改的,先留空间 //7.复制名字同时修复名字表 copyDest = (LPVOID) ((DWORD)copyDest + exportDir->NumberOfNames * 4); for(int i=0;i<exportDir->NumberOfNames;i++){ LPSTR name = (LPSTR)((DWORD)pFileBuffer + *(nameAddr + i)); DWORD len = strlen(name) + 1; //名字长度,需要带上字符串结尾符\0; memcpy(copyDest, name, len); *(newNameAddr + i) = FoaToRva(pFileBuffer, (fileSize + (DWORD)copyDest - (DWORD)newSec));//修复名字表 copyDest = (LPVOID)((DWORD)copyDest + len); } //8.复制导出表,并修复新导出表 memcpy(copyDest, exportDir, dataDir[0].Size); PIMAGE_EXPORT_DIRECTORY newExportDir = (PIMAGE_EXPORT_DIRECTORY) copyDest; newExportDir->AddressOfFunctions = FoaToRva(pFileBuffer, (fileSize + (DWORD)newFunAddr - (DWORD)newSec)); newExportDir->AddressOfNameOrdinals = FoaToRva(pFileBuffer, (fileSize + (DWORD)newOrdAddr - (DWORD)newSec)); newExportDir->AddressOfNames = FoaToRva(pFileBuffer, (fileSize + (DWORD)newNameAddr - (DWORD)newSec)); //9.修复数据目录 dataDir[0].VirtualAddress = FoaToRva(pFileBuffer, (fileSize + (DWORD)newExportDir - (DWORD)newSec)); //10.写出新文件 FILE* newFile = fopen(DEST, "a+b"); if(!newFile){ printf("打开新文件失败\n"); free(pFileBuffer); free(newSec); return; } size_t m = fwrite(pFileBuffer, fileSize, 1, newFile); if(!m){ printf("写出文件第一部分失败\n"); fclose(newFile); free(pFileBuffer); free(newSec); return; } //写出新节 size_t n = fwrite(newSec, secSize, 1, newFile); if(!n){ printf("写出文件第二部分失败\n"); fclose(newFile); free(pFileBuffer); free(newSec); return; } //关闭文件并返回 fclose(newFile); free(pFileBuffer); free(newSec); printf("移动导出表成功\n"); return; } //测试用的主函数 int main(int argc, char* argv[]) { //移动导出表 moveExport(); //使用新的dll typedef int (__stdcall *lpPlus)(int,int); lpPlus myPlus; HINSTANCE hModule = LoadLibrary(DEST); myPlus = (lpPlus)GetProcAddress(hModule, "_Plus@8"); int a =0; a=myPlus(10,2); printf("10+2=%d\n",a); printf("导出函数可以使用,说明移动导出表成功\n"); getchar(); }
结果: