一、为什么要移动各种表
- 一个PE文件中有很多节,有的用来存储代码、有的存储数据,而PE文件的各种表也都分散存储在这些节当中,所以各种表的信息与程序的代码和数据都混在一起了,如果直接对整个程序进行加密,那系统在初始化程序时就会出问题!(比如:在程序启动的时候,这些表的数据被加密了,系统就无法根据这些表将用到的DLL中的函数地址存储到IAT表中)
- 所以对程序加密或破解之前,会先移动各种表到程序新增的节当中,再对剩下的数据(DOS头、NT头等所有头和节表都不能加密,不然操作系统根本就不认这是一个Windows可执行程序了,程序就启动不起来了)进行加密
- 学会移动各种表,是对程序加密/破解的基础
二、移动导出表
1.步骤说明
学到现在这些操作直接在FileBuffer中做即可(多一个RVA转FOA),不用先拉伸,再在ImageBuffer中做这么麻烦了
结合图示来理解移动的步骤
-
在PE文件中新增一个节,大小可以按照下面的长度求和对齐计算出来,但其实可以估算0x1000字节足够了
新增节别忘了修改PE头当中的一些字段:NumberOfSections、SizeOfImage、重新开辟一个FileBuffer、新增节表、修改节表中的字段(Name、Misc.VirtualSize、VirtualAddress、SizeOfRawData、PointerToRawData、Characteristics)。详见day33.1-新增节、扩大节-添加代码
-
复制AddressOfFunctions指向的函数地址表到新节中(2,3,4别忘了RVA转FOA),长度为4 * NumberOfFunctions字节
-
接着复制AddressOfNameOrdinals指向的函数序号表到新节中,长度为2 * NumberOfNames字节
-
复制AddressOfNames指向的函数名称表到新节中,长度为4 * NumberOfNames字节
-
还要复制函数名称表中元素所指向的函数名称字符串到新节中,长度可以遍历函数名称表,用
strlen()函数依次求出每个字符串长度,再求和;每复制一个字符串到新节中,就修改对应的函数名称表中的地址值(而且要注意:函数名称表中存的是RVA,所以把字符串复制到新节后还需要把FOA转成RVA,再存入)为什么需要移动字符串?因为如果光移动函数名称表,对剩下的数据加密(字符串也包含其中),那么当需要根据函数名去调用导出函数时,只能根据函数名称表查到名称字符串所在地址,但是字符串被加密,就无法匹配了
-
复制导出表IMAGE_EXPORT_DIRECTORY到新节中,大小固定40字节
-
修改导出表中的成员值,指向三个子表在新节中的位置(下面三个值都是RVA,所以要根据此时三张子表在新节中的FOA转成RVA)
- AddressOfFunctions
- AddressOfNameOrdinals
- AddressOfNames
-
最后修复导出表数据目录中的VirtualAddress,指向导出表IMAGE_EXPORT_DIRECTORY在新节中的位置(也要FOA转RVA)
2.代码编写
#include "stdafx.h"
#include <stdlib.h>
#include <string.h>
typedef unsigned short WORD;
typedef unsigned int DWORD;
typedef unsigned char BYTE;
#define MZ 0x5A4D
#define PE 0x4550
#define IMAGE_SIZEOF_SHORT_NAME 8
//DOS头
struct _IMAGE_DOS_HEADER {
WORD e_magic; //MZ标记
WORD e_cblp;
WORD e_cp;
WORD e_crlc;
WORD e_cparhdr;
WORD e_minalloc;
WORD e_maxalloc;
WORD e_ss;
WORD e_sp;
WORD e_csum;
WORD e_ip;
WORD e_cs;
WORD e_lfarlc;
WORD e_ovno;
WORD e_res[4];
WORD e_oemid;
WORD e_oeminfo;
WORD e_res2[10];
DWORD e_lfanew; //PE文件真正开始的偏移地址
};
//标准PE头
struct _IMAGE_FILE_HEADER {
WORD Machine; //文件运行平台
WORD NumberOfSections; //节数量
DWORD TimeDateStamp; //时间戳
DWORD PointerToSymbolTable;
DWORD NumberOfSymbols;
WORD SizeOfOptionalHeader; //可选PE头大小
WORD Characteristics; //特征值
};
//数据目录
struct _IMAGE_DATA_DIRECTORY{
DWORD VirtualAddress;
DWORD Size;
};
//可选PE头
struct _IMAGE_OPTIONAL_HEADER {
WORD Magic; //文件类型
BYTE MajorLinkerVersion;
BYTE MinorLinkerVersion;
DWORD SizeOfCode; //代码节文件对齐后的大小
DWORD SizeOfInitializedData; //初始化数据文件对齐后的大小
DWORD SizeOfUninitializedData; //未初始化数据文件对齐后大小
DWORD AddressOfEntryPoint; //程序入口点(偏移量)
DWORD BaseOfCode; //代码基址
DWORD BaseOfData; //数据基址
DWORD ImageBase; //内存镜像基址
DWORD SectionAlignment; //内存对齐粒度
DWORD FileAlignment; //文件对齐粒度
WORD MajorOperatingSystemVersion;
WORD MinorOperatingSystemVersion;
WORD MajorImageVersion;
WORD MinorImageVersion;
WORD MajorSubsystemVersion;
WORD MinorSubsystemVersion;
DWORD Win32VersionValue;
DWORD SizeOfImage; //文件装入虚拟内存后大小
DWORD SizeOfHeaders; //DOS、NT头和节表大小
DWORD CheckSum; //校验和
WORD Subsystem;
WORD DllCharacteristics;
DWORD SizeOfStackReserve; //预留堆栈大小
DWORD SizeOfStackCommit; //实际分配堆栈大小
DWORD SizeOfHeapReserve; //预留堆大小
DWORD SizeOfHeapCommit; //实际分配堆大小
DWORD LoaderFlags;
DWORD NumberOfRvaAndSizes; //目录项数目
_IMAGE_DATA_DIRECTORY DataDirectory[16]; //数据目录
};
//NT头
struct _IMAGE_NT_HEADERS {
DWORD Signature; //PE签名
_IMAGE_FILE_HEADER FileHeader;
_IMAGE_OPTIONAL_HEADER OptionalHeader;
};
//节表
struct _IMAGE_SECTION_HEADER{
BYTE Name[IMAGE_SIZEOF_SHORT_NAME]; //节表名
union{
DWORD PhysicalAddress;
DWORD VirtualSize; //内存中未对齐大小
}Misc;
DWORD VirtualAddress; //该节在内存中偏移地址
DWORD SizeOfRawData; //该节在硬盘上文件对齐后大小
DWORD PointerToRawData; //该节在硬盘上文件对齐后偏移地址
DWORD PointerToRelocations;
DWORD PointerToLinenumbers;
WORD NumberOfRelocations;
WORD NumberOfLinenumbers;
DWORD Characteristics; //该节特征属性
};
//导出表
struct _IMAGE_EXPORT_DIRECTORY{
DWORD Characteristics; //未使用
DWORD TimeDateStamp; //时间戳
WORD MajorVersion; //未使用
WORD MinorVersion; //未使用
DWORD Name; //指向该导出表文件名字符串 *
DWORD Base; //导出函数起始序号 *
DWORD NumberOfFunctions; //所有导出函数的个数 *
DWORD NumberOfNames; //以函数名字导出的函数个数 *
DWORD AddressOfFunctions; //导出函数地址表RVA *
DWORD AddressOfNames; //导出函数名称表RVA *
DWORD AddressOfNameOrdinals; //导出函数序号表RVA *
};
/*计算文件大小函数
参数:文件绝对路径
返回值:返回文件大小(单位字节)
*/
int compute_file_size(char* filePath){
FILE* fp = fopen(filePath,"rb");
if(!fp){
printf("打开文件失败");
exit(0);
}
fseek(fp,0,2);
int size = ftell(fp);
//fseek(fp,0,0); 单纯计算文件大小,就不需要还原指针了
fclose(fp);
return size;
}
/*将文件读入FileBuffer函数
参数:文件绝对路径
返回值:FileBuffer起始地址
*/
char* to_FileBuffer(char* filePath){
FILE* fp = fopen(filePath,"rb");
if(!fp){
printf("打开文件失败");
exit(0);
}
int size = compute_file_size(filePath);
char* mp = (char*)malloc(sizeof(char) * size); //分配内存空间
if(!mp){
printf("分配空间失败");
fclose(fp);
exit(0);
}
int isSucceed = fread(mp,size,1,fp);
if(!isSucceed){
printf("读取数据失败");
free(mp);
fclose(fp);
exit(0);
}
fclose(fp);
return mp;
}
/*计算NewBuffer大小函数
参数:NewBuffer起始地址
返回值:unsigned int类型(单位:字节)
*/
DWORD compute_NewBuffer_size(char* newBufferp){
_IMAGE_DOS_HEADER* _image_dos_header = NULL;
_IMAGE_FILE_HEADER* _image_file_header = NULL;
_IMAGE_OPTIONAL_HEADER* _image_optional_header = NULL;
_IMAGE_SECTION_HEADER* _image_section_header = NULL;
_image_dos_header = (_IMAGE_DOS_HEADER*)newBufferp;
_image_file_header = (_IMAGE_FILE_HEADER*)(newBufferp + _image_dos_header->e_lfanew + 4);
_image_optional_header = (_IMAGE_OPTIONAL_HEADER*)((char*)_image_file_header + 20);
_image_section_header = (_IMAGE_SECTION_HEADER*)((char*)_image_optional_header + _image_file_header->SizeOfOptionalHeader);
_IMAGE_SECTION_HEADER* last_image_section_header = _image_section_header + _image_file_header->NumberOfSections - 1;
DWORD sizeofNewBuffer = last_image_section_header->PointerToRawData + last_image_section_header->SizeOfRawData;
return sizeofNewBuffer;
}
/*NewBuffer存盘函数
参数:需要写出数据的内存首地址,保存绝对路径,写出的数据大小(单位:字节)
返回值:成功返回1,失败返回0
*/
int save_to_disk(char* newBufferp,char* storagePath,DWORD size){
FILE* fp = fopen(storagePath,"wb");
if(!fp){
printf("打开文件失败");
return 0;
}
int isSucceed = fwrite(newBufferp,size,1,fp);
if(!isSucceed){
free(newBufferp);
fclose(fp);
return 0;
}
fclose(fp);
return 1;
}
/*虚拟内存偏移地址->文件偏移地址函数
参数:FileBuffer起始地址,RVA地址值
返回值:RVA对应的FOA,返回0则表示非法RVA
*/
DWORD RVA_to_FOA(char* fileBufferp,DWORD RVA){
_IMAGE_DOS_HEADER* _image_dos_header = NULL;
_IMAGE_FILE_HEADER* _image_file_header = NULL;
_IMAGE_OPTIONAL_HEADER* _image_optional_header = NULL;
_IMAGE_SECTION_HEADER* _image_section_header = NULL;
_image_dos_header = (_IMAGE_DOS_HEADER*)fileBufferp;
_image_file_header = (_IMAGE_FILE_HEADER*)(fileBufferp + _image_dos_header->e_lfanew + 4);
_image_optional_header = (_IMAGE_OPTIONAL_HEADER*)((char*)_image_file_header + 20);
_image_section_header = (_IMAGE_SECTION_HEADER*)((char*)_image_optional_header +
_image_file_header->SizeOfOptionalHeader);
bool flag = 0; //用来判断最终RVA值是否在节中的非空白区
if(RVA < _image_section_header->VirtualAddress)
return RVA;
for(int i = 0;i < _image_file_header->NumberOfSections;i++){
if(RVA >= _image_section_header->VirtualAddress
&& RVA < _image_section_header->VirtualAddress + _image_section_header->Misc.VirtualSize){
flag = 1;
break;
}else{
_image_section_header++;
}
}
if(!flag)
return 0;
DWORD mem_offset_from_section = RVA - _image_section_header->VirtualAddress;
return _image_section_header->PointerToRawData + mem_offset_from_section;
}
/*文件偏移地址函数->虚拟内存偏移地址
参数:FileBuffer起始地址,FOA地址值
返回值:FOA对应的RVA,返回0则表示非法FOA
*/
DWORD FOA_to_RVA(char* fileBufferp,DWORD FOA){
_IMAGE_DOS_HEADER* _image_dos_header = NULL;
_IMAGE_FILE_HEADER* _image_file_header = NULL;
_IMAGE_OPTIONAL_HEADER* _image_optional_header = NULL;
_IMAGE_SECTION_HEADER* _image_section_header = NULL;
_image_dos_header = (_IMAGE_DOS_HEADER*)fileBufferp;
_image_file_header = (_IMAGE_FILE_HEADER*)(fileBufferp + _image_dos_header->e_lfanew + 4);
_image_optional_header = (_IMAGE_OPTIONAL_HEADER*)((char*)_image_file_header + 20);
_image_section_header = (_IMAGE_SECTION_HEADER*)((char*)_image_optional_header +
_image_file_header->SizeOfOptionalHeader);
bool flag = 0;//用来判断最终FOA值是否在节中的非空白区
if(FOA < _image_section_header->PointerToRawData)
return FOA;
for(DWORD i = 0;i < _image_file_header->NumberOfSections;i++){
DWORD tempSize = (_image_section_header->Misc.VirtualSize < _image_section_header->SizeOfRawData ? _image_section_header->Misc.VirtualSize : _image_section_header->SizeOfRawData);
if(FOA >= _image_section_header->PointerToRawData && FOA < _image_section_header->PointerToRawData + tempSize){
flag = 1;
break;
}
_image_section_header++;
}
if(!flag)
return 0;
DWORD file_offset_from_section = FOA - _image_section_header->PointerToRawData

文章详细阐述了如何在PE文件中移动导出表和重定位表到新的节,以便于加密或其他处理。首先,通过增加新节并将导出表的内容复制到新节,然后更新数据目录中的相应RVA。接着,介绍了移动重定位表的步骤,包括计算新节大小,复制表内容到新节,并根据新的ImageBase修复表中的地址。最后,文章提到修复重定位表以适应新的ImageBase,确保PE文件的正常运行。

最低0.47元/天 解锁文章
50

被折叠的 条评论
为什么被折叠?



