一、为什么要移动各种表
- 一个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;
return _image_section_header->VirtualAddress + file_offset_from_section;
}
/*在文件最后新增节函数(直接在FileBuffer中操作)
参数:要新增节的可执行文件的FileBuffer起始地址
返回值:新增节后的NewFileBuffer内存的起始地址
*/
char* add_one_section_inFileBuffer(char* fileBufferp){
_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;
//定义原来的FileBuffer几个指针(因为要用到几个值用于创建新的NewFileBuffer内存)
_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);
//先计算添加新节后的NewFileBuffer的大小,并初始化内存
//DWORD expand_value = 0x3000; //这里0x3000字节足够了
DWORD expand_value = 0xF000; //如果对于重定位表很大的PE文件,要视情况而定
DWORD fileBufferSize = (_image_section_header + _image_file_header->NumberOfSections - 1)->PointerToRawData + (_image_section_header + _image_file_header->NumberOfSections - 1)->SizeOfRawData;
char* newFileBufferp = (char*)malloc(fileBufferSize + expand_value);
for(DWORD i = 0;i < fileBufferSize + expand_value;i++){
*(newFileBufferp + i) = 0x00;
}
memcpy(newFileBufferp,fileBufferp,fileBufferSize);//不改变指针哦
_image_dos_header = (_IMAGE_DOS_HEADER*)newFileBufferp;
_image_file_header = (_IMAGE_FILE_HEADER*)(newFileBufferp + _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);
//判断节表末尾是否有空间新增节表
if(_image_optional_header->SizeOfHeaders - _image_dos_header->e_lfanew - 4 - 20 - _image_file_header->SizeOfOptionalHeader -
_image_file_header->NumberOfSections * 40 < 80){
printf("空间不足,无法新增节表");
getchar();
exit(0);
}
for(i = 0;i < 80;i++){
if(*((char*)(_image_section_header + _image_file_header->NumberOfSections)+ i) != 0){
printf("节表后80字节数据不全为0x00,有文件数据,无法新增节表");
getchar();
exit(0);
}
}
//新增节
DWORD original_SizeOfImage = _image_optional_header->SizeOfImage; //记录下原来的SizeOfImage
_image_optional_header->SizeOfImage += expand_value;
//新增节表
_IMAGE_SECTION_HEADER* _image_section_header_new = (_IMAGE_SECTION_HEADER*)(char*)_image_section_header + _image_file_header->NumberOfSections;
for(i = 0;i < 40;i++){
*((char*)_image_section_header_new + i) = *((char*)_image_section_header + i);
}
_image_file_header->NumberOfSections++;
//修改新增节表信息
char* name = (char*)_image_section_header_new->Name;
char* newName = ".newsec";
strncpy(name,newName,IMAGE_SIZEOF_SHORT_NAME);
_image_section_header_new->Misc.VirtualSize = expand_value;
_image_section_header_new->VirtualAddress = original_SizeOfImage;
_image_section_header_new->SizeOfRawData = expand_value;
_image_section_header_new->PointerToRawData = fileBufferSize;
for(i = 0,_image_section_header_new->Characteristics = 0x00000000;i < _image_file_header->NumberOfSections;i++){
_image_section_header_new->Characteristics = _image_section_header_new->Characteristics | (_image_section_header + i)->Characteristics;
}
return newFileBufferp;
}
/*将PE文件导出表移到新增节中函数
参数:PE文件的FileBuffer首地址
返回值:新增节并且移动导出表后的NewFileBuffer首地址
*/
char* move_ExportTable(char* fileBufferp){
//新增节
char* newFileBufferp = add_one_section_inFileBuffer(fileBufferp);
_IMAGE_DOS_HEADER* _image_dos_header = NULL;
_IMAGE_FILE_HEADER* _image_file_header = NULL;
_IMAGE_OPTIONAL_HEADER* _image_optional_header = NULL;
_IMAGE_DATA_DIRECTORY* _image_data_directory = NULL;
_IMAGE_SECTION_HEADER* _image_section_header = NULL;
_IMAGE_EXPORT_DIRECTORY* _image_export_directory = NULL;
_image_dos_header = (_IMAGE_DOS_HEADER*)newFileBufferp;
_image_file_header = (_IMAGE_FILE_HEADER*)(newFileBufferp + _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_data_directory = (_IMAGE_DATA_DIRECTORY*)_image_optional_header->DataDirectory;
if(_image_data_directory->VirtualAddress == 0){
printf("此PE文件没有导出表");
getchar();
exit(0);
}
_image_export_directory = (_IMAGE_EXPORT_DIRECTORY*)(RVA_to_FOA(newFileBufferp,_image_data_directory->VirtualAddress) + newFileBufferp);
//复制函数地址表到新节中
char* functionAddressTablep = RVA_to_FOA(newFileBufferp,_image_export_directory->AddressOfFunctions) + newFileBufferp;
char* newSectionp = (_image_section_header + _image_file_header->NumberOfSections - 1)->PointerToRawData + newFileBufferp; //newSectionp指向:向新节中复制的位置
char* funcAddressTable_in_newSectionp = newSectionp;//把函数地址表在新节中的首地址记下来,后面要用
memcpy(newSectionp,functionAddressTablep,_image_export_directory->NumberOfFunctions * 4);
//复制函数序号表到新节中
char* functionOrdinalTablep = RVA_to_FOA(newFileBufferp,_image_export_directory->AddressOfNameOrdinals) + newFileBufferp;
newSectionp += _image_export_directory->NumberOfFunctions * 4;
char* funcOrdinalTable_in_newSectionp = newSectionp;//把函数序号表在新节中的首地址记下来,后面要用
memcpy(newSectionp,functionOrdinalTablep,_image_export_directory->NumberOfNames * 2);
//复制函数名称表到新节中
char* functionNameTablep = RVA_to_FOA(newFileBufferp,_image_export_directory->AddressOfNames) + newFileBufferp;
newSectionp += _image_export_directory->NumberOfNames * 2;
char* funcNameTable_in_newSectionp = newSectionp;//把函数名称表在新节中的首地址记下来,后面要用
memcpy(newSectionp,functionNameTablep,_image_export_directory->NumberOfNames * 4);
//复制函数名称字符串到新节中,每复制一个就修改函数名称表中的对应地址值RVA
newSectionp += _image_export_directory->NumberOfNames * 4;
DWORD* funcNameTable_in_newSectionp_temp = (DWORD*)funcNameTable_in_newSectionp;//用于修改新节中的函数名称表中地址值
for(DWORD i = 0;i < _image_export_directory->NumberOfNames;i++){
char* nameStr = (char*)(RVA_to_FOA(newFileBufferp,*(DWORD*)functionNameTablep) + newFileBufferp);
memcpy(newSectionp,nameStr,strlen(nameStr) + 1);//字符串结尾的\0别忘了复制
*funcNameTable_in_newSectionp_temp = FOA_to_RVA(newFileBufferp,(DWORD)(newSectionp - newFileBufferp));//修改对应的函数名称表中的地址值
newSectionp += (strlen(nameStr) + 1);
functionNameTablep = (char*)((DWORD*)functionNameTablep + 1);
funcNameTable_in_newSectionp_temp++;
}
//复制导出表到新节中
memcpy(newSectionp,(char*)_image_export_directory,40);
//修正新节中导出表的成员值
((_IMAGE_EXPORT_DIRECTORY*)newSectionp)->AddressOfFunctions = FOA_to_RVA(newFileBufferp,(DWORD)(funcAddressTable_in_newSectionp - newFileBufferp));
((_IMAGE_EXPORT_DIRECTORY*)newSectionp)->AddressOfNameOrdinals = FOA_to_RVA(newFileBufferp,(DWORD)(funcOrdinalTable_in_newSectionp - newFileBufferp));
((_IMAGE_EXPORT_DIRECTORY*)newSectionp)->AddressOfNames = FOA_to_RVA(newFileBufferp,(DWORD)(funcNameTable_in_newSectionp - newFileBufferp));
//修正导出表数据目录中的成员
_image_data_directory->VirtualAddress = FOA_to_RVA(newFileBufferp,(DWORD)(newSectionp - newFileBufferp));
return newFileBufferp;
}
int main(int argc, char* argv[])
{
//char* filePath = "D:/LordPE/16Edit.DLL"; //选一个有导出表的PE文件,选LordPE文件下的16Edit.DLL是因为这个DLL的导出函数比较少,方便我们验证是否可以正常解析
char* filePath = "D:/OllyICE_1.10/OllyDBG.EXE"; //选ollyDBG.EXE是因为不光可以用LordPE解析其导出表验证是否成功;也可以直接双击运行,看移动导出表后能否正常运行,去验证是否成功
char* fileBufferp = to_FileBuffer(filePath);
char* newFileBufferp = move_ExportTable(fileBufferp);
//将移动导出表后的PE文件存盘
DWORD size = compute_NewBuffer_size(newFileBufferp);
//char* storagePath = "D:/LordPE/16Edit_add.DLL";
char* storagePath = "D:/OllyICE_1.10/OllyDBG_add.EXE";
int isSucceed = save_to_disk(newFileBufferp,storagePath,size);
if(!isSucceed){
printf("存盘失败");
getchar();
}else
printf("存盘成功");
free(fileBufferp);
free(newFileBufferp);
return 0;
}
验证一下:
先验证
char* filePath = "D:/LordPE/16Edit.DLL";
,char* storagePath = "D:/LordPE/";
使用PETool打开
16Edit.DLL
和16Edit_add.DLL
,查看新增节是否成功,且记录下新节的RVA为0xE000再使用LordPE打开
16Edit.DLL
和16Edit_add.DLL
。发现可以正常解析16Edit_add.DLL
的三个函数的序号、RVA和函数名称;且导出表的RVA为0xE05F、以及AddressOfFunctions为0xE000、AddressOfNames为0xE012和AddressOfNameOrdinals为0xE00C,这些RVA都在新节中。故验证成功!再验证一下
char* filePath = "D:/OllyICE_1.10/OllyDBG.EXE";
,char* storagePath = "D:/OllyICE_1.10/OllyDBG_add.EXE";
使用PETool打开
OllyDBG.EXE
和OllyDBG_add.EXE
,查看新增节是否成功,且记录下新节的RVA为0x183000再使用LordPE打开
OllyDBG.EXE
和OllyDBG_add.EXE
。发现可以正常解析OllyDBG_add.EXE
的函数的序号、RVA和函数名称;且导出表的RVA为0x1842C6、以及AddressOfFunctions为0x183000、AddressOfNames为0x183468和AddressOfNameOrdinals为0x1832F0,这些RVA都在新节中。最后再双击运行一下
OllyDBG_add.EXE
,成功打开,故验证成功!
三、移动重定位表
1.步骤说明
结合图示来理解移动重定位表过程:
- 新增节,大小可以循环遍历重定位表的每个块,把所有块的SizeOfBlock求和 + 结束标记8字节最后再对齐;也可以估算0x1000就满足大部分情况了
- 复制重定位表到新节中,大小为所有块的SizeOfBlock求和 + 结束标记8字节
- 修改重定位表数据目录中的VirtualAddress,指向重定位表在新节中的位置即可(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 *
};
//重定位表
struct _IMAGE_BASE_RELOCATION{
DWORD VirtualAddress;
DWORD SizeOfBlock;
//具体项
};
/*计算文件大小函数
参数:文件绝对路径
返回值:返回文件大小(单位字节)
*/
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;
return _image_section_header->VirtualAddress + file_offset_from_section;
}
/*在文件最后新增节函数(直接在FileBuffer中操作)
参数:要新增节的可执行文件的FileBuffer起始地址
返回值:新增节后的NewFileBuffer内存的起始地址
*/
char* add_one_section_inFileBuffer(char* fileBufferp){
_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;
//定义原来的FileBuffer几个指针(因为要用到几个值用于创建新的NewFileBuffer内存)
_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);
//先计算添加新节后的NewFileBuffer的大小,并初始化内存
//DWORD expand_value = 0x3000; //这里0x3000字节足够了
DWORD expand_value = 0xF000; //如果对于重定位表很大的PE文件,要视情况而定
DWORD fileBufferSize = (_image_section_header + _image_file_header->NumberOfSections - 1)->PointerToRawData + (_image_section_header + _image_file_header->NumberOfSections - 1)->SizeOfRawData;
char* newFileBufferp = (char*)malloc(fileBufferSize + expand_value);
for(DWORD i = 0;i < fileBufferSize + expand_value;i++){
*(newFileBufferp + i) = 0x00;
}
memcpy(newFileBufferp,fileBufferp,fileBufferSize);//不改变指针哦
_image_dos_header = (_IMAGE_DOS_HEADER*)newFileBufferp;
_image_file_header = (_IMAGE_FILE_HEADER*)(newFileBufferp + _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);
//判断节表末尾是否有空间新增节表
if(_image_optional_header->SizeOfHeaders - _image_dos_header->e_lfanew - 4 - 20 - _image_file_header->SizeOfOptionalHeader -
_image_file_header->NumberOfSections * 40 < 80){
printf("空间不足,无法新增节表");
getchar();
exit(0);
}
for(i = 0;i < 80;i++){
if(*((char*)(_image_section_header + _image_file_header->NumberOfSections)+ i) != 0){
printf("节表后80字节数据不全为0x00,有文件数据,无法新增节表");
getchar();
exit(0);
}
}
//新增节
DWORD original_SizeOfImage = _image_optional_header->SizeOfImage; //记录下原来的SizeOfImage
_image_optional_header->SizeOfImage += expand_value;
//新增节表
_IMAGE_SECTION_HEADER* _image_section_header_new = (_IMAGE_SECTION_HEADER*)(char*)_image_section_header + _image_file_header->NumberOfSections;
for(i = 0;i < 40;i++){
*((char*)_image_section_header_new + i) = *((char*)_image_section_header + i);
}
_image_file_header->NumberOfSections++;
//修改新增节表信息
char* name = (char*)_image_section_header_new->Name;
char* newName = ".newsec";
strncpy(name,newName,IMAGE_SIZEOF_SHORT_NAME);
_image_section_header_new->Misc.VirtualSize = expand_value;
_image_section_header_new->VirtualAddress = original_SizeOfImage;
_image_section_header_new->SizeOfRawData = expand_value;
_image_section_header_new->PointerToRawData = fileBufferSize;
for(i = 0,_image_section_header_new->Characteristics = 0x00000000;i < _image_file_header->NumberOfSections;i++){
_image_section_header_new->Characteristics = _image_section_header_new->Characteristics | (_image_section_header + i)->Characteristics;
}
return newFileBufferp;
}
/*将PE文件重定位表移到新增节中函数
参数:PE文件的FileBuffer首地址
返回值:新增节并且移动重定位表后的NewFileBuffer首地址
*/
char* move_RelocationTable(char* fileBufferp){
//新增节
char* newFileBufferp = add_one_section_inFileBuffer(fileBufferp);
_IMAGE_DOS_HEADER* _image_dos_header = NULL;
_IMAGE_FILE_HEADER* _image_file_header = NULL;
_IMAGE_OPTIONAL_HEADER* _image_optional_header = NULL;
_IMAGE_DATA_DIRECTORY* _image_data_directory = NULL;
_IMAGE_SECTION_HEADER* _image_section_header = NULL;
_IMAGE_BASE_RELOCATION* _image_base_relocation = NULL;
_image_dos_header = (_IMAGE_DOS_HEADER*)newFileBufferp;
_image_file_header = (_IMAGE_FILE_HEADER*)(newFileBufferp + _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_data_directory = (_IMAGE_DATA_DIRECTORY*)(_image_optional_header->DataDirectory + 5);
if(_image_data_directory->VirtualAddress == 0){
printf("此PE文件没有重定位表");
getchar();
exit(0);
}
_image_base_relocation = (_IMAGE_BASE_RELOCATION*)(RVA_to_FOA(fileBufferp,_image_data_directory->VirtualAddress) + newFileBufferp);
//复制重定位表到新节中
_IMAGE_BASE_RELOCATION* _image_base_relocation_temp = _image_base_relocation;
char* newSectionp = (_image_section_header + _image_file_header->NumberOfSections - 1)->PointerToRawData + newFileBufferp; //newSectionp指向:向新节中复制的位置
int size = 0; //计算重定位表的大小
for(int num = 0;_image_base_relocation_temp->VirtualAddress != 0;num++){
size += _image_base_relocation_temp->SizeOfBlock;
_image_base_relocation_temp = (_IMAGE_BASE_RELOCATION*)((char*)_image_base_relocation_temp + _image_base_relocation_temp->SizeOfBlock);
}
size += 8; //还要加上重定位表结束标记的8字节
memcpy(newSectionp,(char*)_image_base_relocation,size);
//修正重定位表数据目录的成员
_image_data_directory->VirtualAddress = FOA_to_RVA(newFileBufferp,newSectionp - newFileBufferp);
return newFileBufferp;
}
int main(int argc, char* argv[])
{
char* filePath = "D:/WinHex/zlib1.dll"; //选一个有重定位表的PE文件,选WinHex文件下的zlib1.dll是因为这个DLL的重定位表的块比较少,且原节表后面有空余位置新增节表,方便我们验证是否可以正常解析
//char* filePath = "D:/OllyICE_1.10/OllyDBG.EXE"; //选ollyDBG.EXE是因为不光可以用LordPE解析其导出表验证是否成功;也可以直接双击运行,看移动重定位表后能否正常运行,去验证是否成功
char* fileBufferp = to_FileBuffer(filePath);
char* newFileBufferp = move_RelocationTable(fileBufferp);
//将移动重定位表后的PE文件存盘
DWORD size = compute_NewBuffer_size(newFileBufferp);
char* storagePath = "D:/WinHex/zlib1_add.dll";
//char* storagePath = "D:/OllyICE_1.10/OllyDBG_add.EXE";
int isSucceed = save_to_disk(newFileBufferp,storagePath,size);
if(!isSucceed){
printf("存盘失败");
getchar();
}else
printf("存盘成功");
free(fileBufferp);
free(newFileBufferp);
return 0;
}
验证一下:
先验证
char* filePath = "D:/WinHex/zlib1.dll";
,char* storagePath = "D:/WinHex/zlib1_add.dll";
使用PETool打开
zlib1.dll
和zlib1_add.dll
,查看新增节是否成功,且记录下新节的RVA为0x1F000再使用LordPE打开
zlib1.dll
和zlib1_add.dll
。发现可以正常解析zlib1_add.dll
的重定位表以及当中的块和每块的具体项,且重定位表的RVA为0x1F000就在新节中。故验证成功!再验证一下
char* filePath = "D:/OllyICE_1.10/OllyDBG.EXE";
,char* storagePath = "D:/OllyICE_1.10/OllyDBG_add.EXE";
(注意:由于OllyDBG.EXE的重定位表很大!经过VC6调试可以发现大小为48360字节,即0xBCE8字节,所以我们要将上述代码中add_one_section()
方法中的DWORD expand_value = 0x3000
改成足够存储0x8CE8字节的大小,比如DWORD expand_value = 0xF000;
)
使用PETool打开
OllyDBG.EXE
和OllyDBG_add.EXE
,查看新增节是否成功,且记录下新节的RVA为0x183000再使用LordPE打开
OllyDBG.EXE
和OllyDBG_add.EXE
。发现可以正常解析OllyDBG_add.EXE
的重定位表以及当中的块和每块的具体项,且重定位表的RVA为0x183000,就在新节中。最后再双击运行一下
OllyDBG_add.EXE
,成功打开,故验证成功!
四、修复重定位表
注意:修复重定位表不是真的修复重定位表中的数据,而是根据重定位表的各个具体项,去修改具体项指向的地方的值(这个值可能是DLL中全局变量的绝对地址;或者是DLL中函数的绝对地址等)!!!只是平时我们这样叫方便些
1.应用场景
应用场景已经在day37.1-重定位表的问题一中详细说明过,这里再简单提一下
-
一般情况下一个PE文件自身的.exe的ImageBase很少会和别的PE文件ImageBase发生冲突;但是
.dll
就不一定了:day35写过自己发布的DLL,默认情况下DLL的ImageBase为0x10000000,所以如果一个程序要用的DLL没有合理的修改分配装载起始地址,就可能出现多个DLL的ImageBase都是同一个地址,造成装载冲突 -
所以如果一个DLL在装载时,发现其他DLL已经占用这块空间了,那么这个DLL会依据模块对齐粒度,往后找空余的空间存入,则此时这个DLL的装载起始地址和它的ImageBase就不一致了!这个时候操作系统如果访问DLL当中写死的绝对地址,就会访问出错。所以操作系统就会根据重定位表去修正这些写死的地址
-
要求:现在我们自己故意修改一个DLL文件的ImageBase,再根据其重定位表去修正,最后存盘,看此DLL文件是否可以正常使用(即手动模拟操作系统修复重定位表的过程)
-
修正过程:比如DLL的ImageBase原来为0x400000,现在修改为0x500000;接着找到重定位表,遍历每个块中的具体项,根据具体项去找哪里的地址值需要修改重定位;找到后,将原来的地址值 + (0x500000 - 0x400000)即可
-
注入:平时我们说的注入的本质就是将自己写的DLL复制“贴”到一个程序运行时的虚拟内存中,所以我可以找一个空闲位置,把DLL“贴”进去,接着手动修复重定位表,就实现了注入
比如说:我写了一个游戏的工具软件放到DLL里,首先需要将DLL拉伸放到游戏运行时的虚拟内存中,游戏才能使用这个DLL。但是不能直接使用操作系统提供的函数
LoadLibrary()
把DLL放到游戏虚拟内存中,因为游戏在调用LoadLibrary()
函数时会检查参数DLL是否是游戏自己的DLL,如果不是肯定就禁止调用。但是如果我遍历一下游戏虚拟内存空间,看有哪些地方空闲,接着把DLL贴进去,最后修复DLL的重定位表!这个DLL就可以被游戏使用了
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_BASE_RELOCATION{
DWORD VirtualAddress;
DWORD SizeOfBlock;
//具体项
};
/*计算文件大小函数
参数:文件绝对路径
返回值:返回文件大小(单位字节)
*/
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;
}
/*修改ImageBase后修复重定位表函数
参数:要修复重定位表的PE文件的FileBuffer
返回值:无
*/
void repair_RelocationTable(char* fileBufferp){
_IMAGE_DOS_HEADER* _image_dos_header = NULL;
_IMAGE_FILE_HEADER* _image_file_header = NULL;
_IMAGE_OPTIONAL_HEADER* _image_optional_header = NULL;
_IMAGE_DATA_DIRECTORY* _image_data_directory = NULL;
_IMAGE_BASE_RELOCATION* _image_base_relocation = 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);
//指向数据目录结构体数组的第6个结构体
_image_data_directory = (_IMAGE_DATA_DIRECTORY*)(_image_optional_header->DataDirectory + 5);
//判断是否有重定位表
if(_image_data_directory->VirtualAddress == 0){
printf("此PE文件没有重定位表");
getchar();
exit(0);
}
//找到重定位表
_image_base_relocation = (_IMAGE_BASE_RELOCATION*)(RVA_to_FOA(fileBufferp,_image_data_directory->VirtualAddress) + fileBufferp);
//手动修改ImageBase(这里可以先查一下原ImageBase(0x20000000)是多少,方便修改)
DWORD original_ImageBase = _image_optional_header->ImageBase;
_image_optional_header->ImageBase = 0x30000000;
int offset = (int)(_image_optional_header->ImageBase - original_ImageBase);//现ImageBase比原ImageBase可能大也可能小,所以设置为有符号整型类型
//根据重定位表中每个块的具体项去修复
while(_image_base_relocation->VirtualAddress != 0){
for(DWORD j = 0;j < (_image_base_relocation->SizeOfBlock - 8) / 2;j++){
//这里使用右移12位的方式2字节宽度的高4位
if((*((WORD*)((char*)_image_base_relocation + 8 + j * 2)) >> 12) == 0){
continue;
}
//这里用与运算把高4位变成0后输出2字节宽度的值,即为低12位的值(别忘了还要加该块VirtualAddress)
DWORD* repairAddress = (DWORD*)(fileBufferp + RVA_to_FOA(fileBufferp,_image_base_relocation->VirtualAddress + (*((WORD*)((BYTE*)_image_base_relocation + 8 + j * 2)) & 0x0FFF)));
*repairAddress += offset;
}
_image_base_relocation = (_IMAGE_BASE_RELOCATION*)((char*)_image_base_relocation + _image_base_relocation->SizeOfBlock);
}
}
int main(int argc, char* argv[])
{
char* filePath = "D:/LordPE/PROCS.DLL"; //选一个有重定位表的PE文件,选LordPE文件下的PROCS.DLL是因为这个DLL的重定位表的块比较少,方便我们验证是否可以正常解析
char* fileBufferp = to_FileBuffer(filePath);
repair_RelocationTable(fileBufferp);
//将修复重定位表后的PE文件存盘
DWORD size = compute_NewBuffer_size(fileBufferp);
char* storagePath = "D:/LordPE/PROCS_repair.DLL";
int isSucceed = save_to_disk(fileBufferp,storagePath,size);
if(!isSucceed){
printf("存盘失败");
getchar();
}else
printf("存盘成功");
free(fileBufferp);
return 0;
}
验证一下:
使用LordPE打开
PROCS.DLL
和PROCS_repair.DLL
,先查看ImageBase,修改成功;再查看重定位表的每个块的每个具体项低12位 + 对应块的RVA,所指向的数据(即Far Address),可以看到数据值都加上了0x30000000 - 0x20000000 = 0x10000000,且可以正确解析出Far Address所指向的数据(即Data Interpretation)这里一定要用Far Address和Data Interpretation来验证正确性,不能看重定位表是否能解析出来,因为修复重定位表,没有修改重定位表中每个块的RVA和每个块中每个具体项的值,而是修改了每个具体项低12位 + 对应块的RVA–所指向的数据值(这个数据就是某个全局变量的绝对地址,或者是函数的绝对地址等)
小问题:如果修改一个有重定位表的.exe文件的ImageBaes,比如
OllyDBG.EXE
,双击运行后,系统会认为这个程序不是一个win32可执行文件;如果稍微把ImageBase修改大一点,双击运行后,可以打开,但是会立马弹一个报错的消息框,也无法正常执行
- 可能是因为正常来说我们不会自己主动修改一个PE文件的ImageBase;而是当程序运行时,如果有的DLL不能按ImageBase加载,操作系统会寻找其他空间存放,并且去修复重定位表,所以这些都是操作系统帮忙干的。我们主动修改ImageBase,可能就会导致PE文件无法正常执行(上面之所以改是因为方便我们练习操作系统是怎么修复重定位表的,让我们更好理解重定位表到底有什么用)