前面章节我们了解了PE文件中的导出表和重定位表,今天我们来学习如何来移动导出表和重定位表。
一.为什么要移动各种表?
首先,这些表都是由编译器生成的,里面存储了非常重要的信息,在程序启动的时候,系统会根据这些表的内容来做一些初始化的工作,比如将用到的dll中的函数地址存储到IAT表(后续我们将会学习)中,为了保护程序,我们可以对exe二进制进行加密操作,但是问题是:各种表的信息,与代码和数据混在一起(存在于各个节中)如果进行加密的话,系统在初始化的时候就会出问题。
学会移动各种表,是对程序加密解密的基础
二.移动导出表
这里给出移动导出表的基本思路,在文章后面会贴出源码
1.首先,我们在文件中新增一个节,我们可以将导出表等信息移动到导出表中
2.将函数地址表移动到新节(也就是AddressOfFunctions所指向的内容)长度:NumberOfFunctions*4
注意函数地址表中存储的是函数地址,因为我们没有移动函数,所以我们在移动导出表后无需修正
3.复制函数导出序号表到新节(也就是AddressOfNameOrdinals所指向的内容)长度:NumberOfNames*2
注意导出序号表中存储的就是导出序号,移动导出表后无需修正
4.复制函数名称表移动到新节(也就是AddressOfNames所指向的内容)长度:NumberOfNames*4
注意函数名称表中存储的是地址,指向函数名称字符串,这里在复制函数名字符串后立即修正函数名称表
5.复制所有函数名字符串长度=字符串长度
6.复杂IMAGE_EXPORT_DIRECTORY结构
7.修复IMAGE_EXPORT_DIRECTORY结构中的AddressOfFunctions,AddressOfOrdinals,AddressOfNames
字段
8.修复数据目录表IMAGE_DATA_DIRECTORY[IMAGE_DIRECTORY_ENTRY_EXPORT]
中的VirsualAddress字段,使其指向我们移动后的导出表
三.移动导出表源码
PEFunction.h:
#include <stdio.h> #include <windows.h> #include <string.h> //*************打开文件函数(将文件二进制读取到FileBuffer) ****************** //该函数需要一个char类型的指针,指向想要打开的文件路径 //该函数返回一个char类型的指针,指向FileBuffer //该函数完成文件的打开,并且将文件的二进制读取到FIleBuffer //该函数读取完成后会将文件关闭,避免出现误操作 //该函数会通过移动文件指针获取文件大小 //获取的文件大小用于动态申请内存 //该函数会动态申请内存,用于存放FileBuffer //*************************************************************************** char* ReadToFileBuffer(char* filename); //***************************************获取文件大小函数********************************* //该函数返回文件大小 //**************************************************************************************** int SizeOfFile(char* filename); //*****************************获取PE导出表数据函数************************** //该函数需要一个指针,指向FileBuffer //该函数在找到导出表之前,需要定义DOS结构指针,标准PE头结构指针和可选PE头指针,以方便找到导出表 //该函数会在控制台输出函数地址表,函数名称表和函数导出序号表 //*************************************************************************** void ShowExportDirectory(char* FileBuffer); //*****************************RVA转换为FOA函数****************************** //该函数需要一个指针,指向FileBuffer //该函数需要一个整数,为要转换的RVA //该函数返回一个整数,为转换后的FOA地址 //*************************************************************************** int RVAToFOA(IN LPVOID pFileBuffer, IN DWORD dwRva); //*************************根据函数名称查找函数地址函数********************** //该函数需要一个指针,指向要打开的文件路径 //该函数返回一个整数,指向得到的函数地址 //*************************************************************************** void GetAddressOfFunctionByName(char* filename); //*************************根据函数序号查找函数地址函数********************** //该函数需要一个指针,指向要打开的文件路径 //该函数返回一个整数,指向得到的函数地址 //*************************************************************************** void GetAddressOfFunctionByOrdinal(char* filename); //******************************打印PE重定位表函数*************************** //该函数需要一个指针,指向FIleBuffer //该函数会在控制台打印出重定位表信息(重定位表偏移,重定位表大小,但不打印重定位表具体项 //*************************************************************************** void ShowBaseRelocation(char* FileBuffer); //**************************************新增节函数*************************************** //该函数需要一个指针,指向FileBuffer //该函数需要一个指针,表示FileBuffer大小 //该函数需要一个字符串,为原文件文件名 //该函数完成在PE文件中新增一个节 //该函数在新增节之前,PE头部是否足够新增一个节表 //若PE文件头部内存不够新增一个节表,该函数将会提升所有头部,将DOSstob覆盖掉,并且让e_lfanew指向下一个字节 //该函数会生成一个exe文件,存放于调用该函数的程序源目录下 //该函数返回一个指针,指向新增节后的FileBuffer //**************************************************************************************** char* AddNewSection(char* FileBuffer,int SizeOfFileBuffer); //*****************************覆盖DOSstob,抬升NT头和原有节表**************************** //该函数需要一个指针,指向需要改动的FileBuffer //该函数将FileBuffer中的DOSstob覆盖 //该函数将PE头部中,DOS头以后的内容提升到DOSstob的位置 //抬升成功后会将最后两个节表的数据全初始化为0 //若头部抬升失败,则返回0 //若抬升成功,则返回1 //**************************************************************************************** int UpFile(char* FileBuffer); //******************************计算文件对齐后的值函数************************************ //Align:计算对齐后的值 //参数说明: //x 需要进行对齐的值 //Alignment 对齐大小 //返回值说明: //返回x进行Alignment值对齐后的值 //**************************************************************************************** int Align(int x, int Alignment); //**************************************移动导出表**************************************** //该函数需要一个指针,指向FileBuffer //该函数需要一个整形参数,表示FileBuffer大小 //该函数将移动导出表到新节,节名称:.NewSec //该函数返回一个指针,指向NewFileBuffer //**************************************************************************************** char* RemoveExportDirectory(char* FileBuffer,int SizeOfFileBuffer);
main.c:
#include <stdio.h> int main(int argc, char** argv) { char filename1[50]; char filename2[50]; printf("****** 欢迎使用移动导出表程序 ******\n"); printf("请输入想要操作的文件路径:"); scanf("%s",filename1); char* FileBuffer=NULL; char* NewFileBuffer=NULL; int length; int* plength=&length; FileBuffer=ReadToFileBuffer(filename1); length=SizeOfFile(filename1); NewFileBuffer=RemoveExportDirectory(FileBuffer,length); printf("请输入您想要保存的文件名称:"); scanf("%d",filename2); FILE* fp; if((fp=fopen(filename2,"wb"))==NULL){ printf("文件新建失败!\n"); exit(0); }else{ printf("文件新建成功,正在写入数据...\n"); } if(fwrite(NewFileBuffer,length+1000,1,fp)){ printf("数据写入成功。\n"); }else{ printf("数据写入失败!\n"); exit(0); } free(FileBuffer); free(NewFileBuffer); return 0; }