一、添加代码的方式
- 我们现在学过的有:①直接在任意节的空白区添加代码;②新增节添加代码;③扩大最后一个节添加代码
- 今天要学习:④合并节并添加代码
二、合并节思路
-
使用拉伸后的合并更方便计算节合并后的大小,而且可以保证计算的正确性。如果用FileBuffer算,算地址或者大小时还要担心节中未初始化数据在拉伸后导致节变大等等情况。
-
合并节就是:把第一个节开始一直到最后一个节的结尾都当成第一个节
-
但是可能会造成合并后文件变大,因为文件对齐粒度一般都要小于内存对齐粒度,所以虚拟内存中每个节的空白区一般比文件空白区要大一些,现在合并节是把虚拟内存中从第一个节开始到文件结束都当成一个节,最后再变成FileBuffer就会变大。
-
步骤:
-
NumberOfSections改成1
-
修改第一个节表的字段
- VirtualSize:
- 方法一:SizeOfImage - 第一个节VirtualAddress
- 方法二:还可以通过最后一个节的VirtualAddress + 最后一个节的VirtualSize内存对齐后的大小 - SizeOfHeader内存对齐后的大小
- SizeOfRawData:等于VirtualSize即可
- VirtualSize:
-
将第一个节的属性改为包含所有节的属性,即用第一个节的属性与其他的节的属性做或运算
-
三、作业
1.扩大最后一个节,保证程序正常运行(手动)
day33.1中写过用编程实现扩大节,所以今天再试着用手动的方式体会一下扩大节,由于手动的时候是在FileBuffer中操作,所以有些值的计算包括扩大的大小都是需要计算的,没有在ImageBuffer中扩大节那么方便
-
打开notepad.exe程序,如果我们想扩大0x1000字节,则需要修改SizeOfImage,0x13000 + 0x1000内存对齐后 = 0x14000(我们知道notepad.exe的内存对齐为0x1000;文件对齐为0x200)
-
接着找到最后一个节表,修改VirtualSize = 原来的0x8948内存对齐后0x9000 + 0x1000内存对齐后 = 0xA000
-
SizeOfRawData = VirtualSize即可
-
最后将最后一个节的属性0x40000040与0x60000020亦或得到0x60000060
-
最后最关键的一步就是在最后一个节末尾扩大节,我们知道如果在ImageBuffer中扩大节,就直接在末尾加上0x1000字节即可,但是由于现在是在FileBuffer中操作,不能直接扩大0x1000了,不然就跟SizeOfRawData对不上,我们要通过计算来得到:原来的SizeOfRawData为0x8A00,而扩大后的SizeOfRawData = 0xA000,所以只需要扩大0xA000 - 0x8A00 = 0x1600即可,换算成十进制为5632。所以在文件末尾0x101FF处右键–Edit–Paste Zero Bytes–输入5632
-
保存运行,再拖入PEtool中查看是否修改成功
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
#define MessageBox_Address 0x77D36476 //宏定义记事本的MessageBox内存地址
//shellcode
BYTE shellcode[] = {
0x6A,0x00,0x6A,0x00,0x6A,0x00,0x6A,0x00,
0xE8,0x00,0x00,0x00,0x00,
0xE9,0x00,0x00,0x00,0x00
};
//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; //特征值
};
//可选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; //该节特征属性
};
/*计算文件大小函数
参数:文件绝对路径
返回值:返回文件大小(单位字节)
*/
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;
}
/*FileBuffer到ImageBuffer函数
参数:FileBuffer起始地址
返回值:ImageBuffer起始地址
*/
char* fileBuffer_to_ImageBuffer(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;
_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);
DWORD sizeofImage = _image_optional_header->SizeOfImage;
char* imageBufferp = (char*)malloc(sizeofImage);
if(NULL == imageBufferp){
printf("动态申请ImageBuffer内存失败\n");
exit(0);
}
for(DWORD i = 0;i < sizeofImage;i++){
*(imageBufferp + i) = 0x00;
}
//strncpy(imageBufferp,fileBufferp,_image_optional_header->SizeOfHeaders); 因为imageBufferp,fileBufferp值会变,所以这个方法不太好
for(i = 0;i < _image_optional_header->SizeOfHeaders;i++){
*(imageBufferp + i) = *(fileBufferp + i);
}
for(i = 0;i < _image_file_header->NumberOfSections;i++){
for(DWORD j = 0;j < _image_section_header->SizeOfRawData;j++){
*(imageBufferp + _image_section_header->VirtualAddress + j) = *(fileBufferp +
_image_section_header->PointerToRawData + j);
}
_image_section_header++;
}
return imageBufferp;
}
/*计算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;
}
/*ImageBuffer到NewBuffer函数
参数:ImageBuffer起始地址
返回值:NewBuffer起始地址
*/
char* imageBuffer_to_NewBuffer(char* imageBufferp){
_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*)imageBufferp;
_image_file_header = (_IMAGE_FILE_HEADER*)(imageBufferp + _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);
//重新计算newBuffer需要大小
/*方法一:SizeOfHEADER + 所有节的SizeOfRawData之和
int sizeofSections = 0;
_IMAGE_SECTION_HEADER* _image_section_header_temp = _image_section_header;
for(DWORD i = 0;i < _image_file_header->NumberOfSections;i++){
sizeofSections += _image_section_header_temp->SizeOfRawData;
_image_section_header_temp++;
}
char* newBufferp = (char*)malloc(_image_optional_header->SizeOfHeaders + sizeofSections);
*/
//方法二:使用最后一个节的文件偏移地址 + 最后一个节对齐后的大小
DWORD size = compute_NewBuffer_size(imageBufferp);
char* newBufferp = (char*)malloc(size);
if(NULL == newBufferp){
printf("NewBuffer内存分配失败\n");
exit(0);
}
for(DWORD i = 0;i < size;i++){
*(newBufferp + i) = 0x00;
}
for(i = 0;i < _image_optional_header->SizeOfHeaders;i++){
*(newBufferp + i) = *(imageBufferp + i);
}
for(i = 0;i < _image_file_header->NumberOfSections;i++){
for(DWORD j = 0;j < _image_section_header->SizeOfRawData;j++){ //不用VirtualSize因为害怕会多复制覆盖下一个节
*(newBufferp + _image_section_header->PointerToRawData + j) = *(imageBufferp +
_image_section_header->VirtualAddress + j);
}
_image_section_header++;
}
return newBufferp;
}
/*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;
}
/*合并节函数
参数:需要合并节的可执行文件ImageBuffer起始地址
返回值:合并后的ImageBuffer起始地址(因为合并节后并不改变ImageBuffer内存大小,所以返回void也行)
*/
char* merge_section(char* imageBufferp){
_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*)imageBufferp;
_image_file_header = (_IMAGE_FILE_HEADER*)(imageBufferp + _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->Misc.VirtualSize = _image_optional_header->SizeOfImage - _image_section_header->VirtualAddress;
_image_section_header->SizeOfRawData = _image_section_header->Misc.VirtualSize;
for(int i = 1;i < _image_file_header->NumberOfSections;i++){
_image_section_header->Characteristics = _image_section_header->Characteristics | (_image_section_header + i)->Characteristics;
}
/*前面提到过节表末尾应该跟40个0x00,但是如果修改了,文件就打不开了?不知道原因是啥
for(i = 0;i < 40 * _image_file_header->NumberOfSections;i++){
*((char*)(_image_section_header + 1) + i) = 0x00;
}*/
//修改NumberOfSections字段
_image_file_header->NumberOfSections = 1;
return imageBufferp;
}
int main(int argc,char* argv[]){
char* filePath = "D:/C-language/file/notepad.exe"; //你要打开的PE文件绝对路径
char* storagePath = "D:/C-language/file/notepad_merge.exe"; //保存路径
//char* filePath = "D:/IPMsg/ipmsg.exe"; //你要打开的PE文件绝对路径
//char* storagePath = "D:/IPMsg/ipmsg_merge.exe"; //保存路径
char* fileBufferp = to_FileBuffer(filePath);
char* imageBufferp = fileBuffer_to_ImageBuffer(fileBufferp);
imageBufferp = merge_section(imageBufferp);
char* newBufferp = imageBuffer_to_NewBuffer(imageBufferp);
DWORD size = compute_NewBuffer_size(newBufferp);
int isSucceed = save_to_disk(newBufferp,storagePath,size);
if(!isSucceed){
printf("存盘失败");
getchar();
}else
printf("存盘成功");
free(fileBufferp);
free(imageBufferp);
free(newBufferp);
return 0;
}
3.定义函数,能够返回对齐后的大小
-
说明:定义一个函数Align(int x,int y),第一个参数为我们手动输入的字节大小,第二个参数为文件对齐大小或者内存对齐大小,现在功能是如果我们传入0x222字节,文件对齐大小为0x200,那么计算0x222文件对齐后的大小为多少;如果传入0x1222字节,内存对齐大小为0x1000,那么计算0x1222内存对齐后的大小是多少
-
代码如下:
#include "stdafx.h" typedef unsigned int DWORD; //因为一般alignment函数都是用于PE结构字段,都是无符号整形 /*计算一个数按照某对齐粒度对齐后的值函数 参数:一个数,对齐粒度 返回值:此数对齐后的值,返回0表示参数不合法 */ DWORD alignment(DWORD num,DWORD align_num){ //用int也行 if((int)num <= 0 || (int)align_num <= 0){ printf("输入的数据不合法\n"); return 0; } if(num % align_num == 0){ return num; }else{ return align_num * (num / align_num + 1); } } int main(int argc,char* argv[]){ printf("%x",alignment(0x3700,0x1000)); //0x4000 return 0; }