一、编码实现向代码节添加代码
1.不同软件的地址问题
-
我们用OD打开一个文件,模拟的是文件真正运行时的状态,所以此时左侧栏显示的所有地址都是内存地址,文件的起始地址就是ImageBase
-
我们使用UE和winhex打开一个文件,起始就是一个将文件在硬盘上时的数据复制一份存到FileBuffer内存中,所以此时左侧栏显示的地址是文件地址,文件的起始地址是逻辑地址地址为0x0
-
我们使用winhex–tools–open ram打开一个正在运行时的文件,此时左侧栏显示的地址就是文件真正运行时的内存地址,文件的起始地址就是ImageBase
-
我们如果使用C语言来模拟FileBuffer->ImageBuffer->NewBuffer的过程:
-
由于我们使用malloc开辟一块内存空间模拟FileBuffer,这块内存的起始地址就不在是我们说的文件在硬盘上的起始逻辑地址0x0,而是随机动态分配的一个起始地址,所以我们在计算FileBuffer中的地址时,就要注意偏移量加的不是0x0了,而是要用偏移量加上动态分配的起始地址才是在FileBuffer中地址
-
接着如果我们再模拟拉伸的过程,即FileBuffer到ImageBuffer的过程时,由于也使用malloc动态给ImageBuffer分配内存,所以此时ImageBuffer中文件起始地址不再是ImageBase,而也是一个随机分配的地址,所以我们使用动态分配的起始地址加偏移量的方式计算ImageBuffer中的地址时,就不是加ImageBase!而且我们计算call和jmp等硬编码E8、E9后面的4字节值时,需要用到的E8、E9当前的内存地址不是现在在ImageBuffer中的值,因为这也只是我们模拟出来的文件运行状态的ImageBuffer,而不是最后程序在操作系统上真正装入内存时的状态,所以我们此时计算E8当前的内存地址还要想象一下文件真正被执行的状态,那么起始地址就要用ImageBase了,用这个值来计算E8、E9后面要跟的4字节值
-
包括最后ImageBuffer到NewBuffer的过程也是同样如此
-
-
文件双击真正运行时的起始地址就为ImageBase的值了
二、编写代码
1.向代码区空白区添加代码
-
代码如下:
#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:定义要添加的硬编码全局变量,确定的写好,不确定的先用0x00填充,后面计算完再改 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; } /*向第一个节.text中注入shellcode函数 参数:要注入的可执行文件的ImageBuffer起始地址 返回值:0则注入失败,1则注入成功 */ int add_shellcode(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); //用SizeofRawData - VirtualSize判断空白区是否存的下shellcode:这里要考虑到一个问题:我们使用的是secp->VirtualAddress + secp->Misc.VirtualSize来确定内存中此节的空白区的偏移起始地址,这个算法是没错的,但是还记得前面将FileBuffer装载到ImageBuffer时我们复制每个节的长度是按照SizeOfRawData复制的,而且ImageBuffer到NewBuffer也是每个节按照SizeOfRawData复制的。那么如果某一个节的VirtualSize>SizeOfRawData,就会出现我们添加代码的地方确实在节装入内存时的空白区,但是最后ImageBuffer到NewBuffer时,复制的时候只复制了这个节开始的SizeOfRawData个字节,我们加的代码并没有复制进去,就可能出现问题,所以干脆直接要求SizeOfRawData > VirtualSize才能注入 if((int)(_image_section_header->SizeOfRawData - _image_section_header->Misc.VirtualSize) < sizeof(shellcode) / sizeof(shellcode[0])){ printf("此节空白区大小不够,添加失败"); return 0; } //将shellcode注入到节空白区 char* shellcodep = imageBufferp + _image_section_header->VirtualAddress + _image_section_header->Misc.VirtualSize; char* newOEP = shellcodep; //此shellcode注入起始地址即为新的OEP地址 memcpy(shellcodep,shellcode,sizeof(shellcode) / sizeof(shellcode[0])); //注意memcpy函数不改变传入的指针的值!! //修改call MessageBox的硬编码E8后面的4字节 DWORD E8_fill_value = MessageBox_Address - ((shellcodep - imageBufferp + _image_optional_header->ImageBase) + 0xD); *(DWORD*)(shellcodep + 9) = E8_fill_value; //修改jmp 原OEP的硬编码E9后面的4字节 DWORD E9_fill_value = _image_optional_header->AddressOfEntryPoint + _image_optional_header->ImageBase - ((shellcodep - imageBufferp + _image_optional_header->ImageBase) + 0x12); *(DWORD*)(shellcodep + 14) = E9_fill_value; //修改OEP指向shellcode _image_optional_header->AddressOfEntryPoint = (DWORD)(newOEP - imageBufferp); return 1; } int main(int argc,char* argv[]){ char* filePath = "D:/C-language/file/notepad.exe"; //你要打开的PE文件绝对路径 char* storagePath = "D:/C-language/file/notepad_add.exe"; //保存路径 char* fileBufferp = to_FileBuffer(filePath); char* imageBufferp = fileBuffer_to_ImageBuffer(fileBufferp); if(!add_shellcode(imageBufferp)){ printf("shellcode注入失败"); return 0; } char* newBufferp = imageBuffer_to_NewBuffer(imageBufferp); int isSucceed = save_to_disk(newBufferp,storagePath,compute_NewBuffer_size(newBufferp)); if(!isSucceed) printf("存盘失败"); else printf("存盘成功"); free(fileBufferp); free(imageBufferp); free(newBufferp); return 0; }
2.向任何区空白区添加代码
-
思路:比如我想在第一个节空白区加数据,就在第一个加;想在第二个节空白区加就在第二个加…但是要注意一个问题,添加的节的characteristic必须满足可执行才能被执行到,即0x20000000(从低到高第29位为1),所以还要修改一下所在节的characteristic字段的值。这里我们采用将此节现在的characteristic字段的值与0x60000020做或运算,即让属性求并集,既不改变节的原来的属性,而且还可以保证有0x600000020的属性
-
代码如下:
#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; } /*向第一个节.text中注入shellcode函数 参数:要注入的可执行文件的ImageBuffer起始地址 返回值:0则注入失败,1则注入成功 */ int add_shellcode(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); //用SizeofRawData - VirtualSize判断空白区是否存的下shellcode if((int)(_image_section_header->SizeOfRawData - _image_section_header->Misc.VirtualSize) < sizeof(shellcode) / sizeof(shellcode[0])){ printf("此节空白区大小不够,添加失败"); return 0; } //将shellcode注入到节空白区 char* shellcodep = imageBufferp + _image_section_header->VirtualAddress + _image_section_header->Misc.VirtualSize; char* newOEP = shellcodep; //此shellcode注入起始地址即为新的OEP地址 memcpy(shellcodep,shellcode,sizeof(shellcode) / sizeof(shellcode[0])); //注意memcpy函数不改变传入的指针的值!! //修改call MessageBox的硬编码E8后面的4字节 DWORD E8_fill_value = MessageBox_Address - ((shellcodep - imageBufferp + _image_optional_header->ImageBase) + 0xD); *(DWORD*)(shellcodep + 9) = E8_fill_value; //修改jmp 原OEP的硬编码E9后面的4字节 DWORD E9_fill_value = _image_optional_header->AddressOfEntryPoint + _image_optional_header->ImageBase - ((shellcodep - imageBufferp + _image_optional_header->ImageBase) + 0x12); *(DWORD*)(shellcodep + 14) = E9_fill_value; //修改OEP指向shellcode _image_optional_header->AddressOfEntryPoint = (DWORD)(newOEP - imageBufferp); return 1; } /*向任意节的空白区中注入shellcode函数 参数:要注入的可执行文件的ImageBuffer起始地址,要注入第几个节 返回值:0为注入失败、1为注入成功 */ int add_shellcode_to_specific_section(char* imageBufferp,int section_num){ _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); //判断输入的节是否合法,并定位到指定节的节表起始地址 if(section_num <= 0 || section_num > _image_file_header->NumberOfSections){ printf("输入的节不存在"); return 0; } _IMAGE_SECTION_HEADER* target_image_section_header = _image_section_header + section_num - 1; //用SizeofRawData - VirtualSize判断空白区是否存的下shellcode(当SizeofRawData < VirtualSize时,这种特殊情况,我们就当做空白区大小不够处理) if((int)(target_image_section_header->SizeOfRawData - target_image_section_header->Misc.VirtualSize) < (int)(sizeof(shellcode) / sizeof(shellcode[0]))){ printf("此节空白区大小不够,添加失败"); return 0; } //将shellcode注入到节空白区 char* shellcodep = imageBufferp + target_image_section_header->VirtualAddress + target_image_section_header->Misc.VirtualSize; char* newOEP = shellcodep; //此shellcode注入起始地址即为新的OEP地址 memcpy(shellcodep,shellcode,sizeof(shellcode) / sizeof(shellcode[0])); //注意memcpy函数不改变传入的指针的值!! //修改call MessageBox的硬编码E8后面的4字节 DWORD E8_fill_value = MessageBox_Address - ((shellcodep - imageBufferp + _image_optional_header->ImageBase) + 0xD); *(DWORD*)(shellcodep + 9) = E8_fill_value; //修改jmp 原OEP的硬编码E9后面的4字节 DWORD E9_fill_value = _image_optional_header->AddressOfEntryPoint + _image_optional_header->ImageBase - ((shellcodep - imageBufferp + _image_optional_header->ImageBase) + 0x12); *(DWORD*)(shellcodep + 14) = E9_fill_value; //修改OEP指向shellcode _image_optional_header->AddressOfEntryPoint = (DWORD)(newOEP - imageBufferp); //修改该节的特征值使之可读、可写、可执行 target_image_section_header->Characteristics = target_image_section_header->Characteristics | 0x60000020; return 1; } int main(int argc,char* argv[]){ char* filePath = "D:/C-language/file/notepad.exe"; //你要打开的PE文件绝对路径 char* storagePath = "D:/C-language/file/notepad_add.exe"; //保存路径 char* fileBufferp = to_FileBuffer(filePath); char* imageBufferp = fileBuffer_to_ImageBuffer(fileBufferp); /*if(!add_shellcode(imageBufferp)){ printf("shellcode注入失败"); return 0; }*/ if(!add_shellcode_to_specific_section(imageBufferp,3)){ //这这里修改在第几节注入 printf("shellcode注入失败"); getchar(); return 0; } char* newBufferp = imageBuffer_to_NewBuffer(imageBufferp); int isSucceed = save_to_disk(newBufferp,storagePath,compute_NewBuffer_size(newBufferp)); if(!isSucceed) printf("存盘失败"); else printf("存盘成功"); free(fileBufferp); free(imageBufferp); free(newBufferp); return 0; }
-
我们发现有时候文件内存中未对齐的大小确实比文件中对齐的大小大(由于节中包含初始化数据的缘故),比如notepad.exe文件的第二个节.data中,内存中的大小为0x1BA8,但是文件中对齐后的大小为0x600。此时我们可以发现,第三个节在文件中就按照上一个节在文件中对齐后的大小往后接着存放,即0x7200 + 0x600 = 0x7800,所以第三个节的PointerToRawData为0x7800,而第三个节在内存中就按照上一个节在内存中对齐后的大小往后接着存放(就不是再按照上一个节的文件中对齐大小挨着存放了,因为此时VirtualSize比SizeOfRawData大),即0x8000 + 0x1BA8 = 0x9BA8,对齐后为0xA000,所以第三个节内存偏移为0xA000
-