PE文件的基础知识大家可以自行百度。
1、在PE中末尾添加一个节
在PE末尾中添加一个节,需要注意:
(1)RVA和FOA的转换
(2)需要修改SizeofImage的大小
(3)需要修改PE头中节的数量
(4)如果现有的节表后面添加的空间不足,可以移动NT头和节表
(5)新增加节表的属性根据自己的需要修改
(6)新增节的RVA 需要注意计算,需要计算上一个节RVA对齐后的尺寸
计算公式如下:
//计算内存中对齐的大小
DWORD VirtulaSize = NULL;
if (section_header->SizeOfRawData < section_header->Misc.VirtualSize) {
VirtulaSize = ((section_header->Misc.VirtualSize /0x1000)+1) * 0x1000;
}
else
{
VirtulaSize = (section_header->SizeOfRawData/0x1000+1)*0x1000;
}
pAddsection->VirtualAddress = section_header->VirtualAddress + VirtulaSize;
2、添加节区的代码如下
BOOL AddSection(char* FileBuffer, LPCSTR NewFileName) {
PIMAGE_DOS_HEADER dos = NULL;
PIMAGE_NT_HEADERS nt = NULL;
PIMAGE_FILE_HEADER file_header = NULL;
PIMAGE_SECTION_HEADER section_header = NULL;
PIMAGE_OPTIONAL_HEADER option_header = NULL;
dos = (PIMAGE_DOS_HEADER)FileBuffer;
nt = (PIMAGE_NT_HEADERS)(FileBuffer + dos->e_lfanew);
file_header = (PIMAGE_FILE_HEADER)(&(nt->FileHeader));
option_header = (PIMAGE_OPTIONAL_HEADER)(&(nt->OptionalHeader));
section_header = PIMAGE_SECTION_HEADER((char*)option_header + file_header->SizeOfOptionalHeader);
//添加大小为0x1000的节
option_header->SizeOfImage = option_header->SizeOfImage + 0x1000;
//1、计算后续空间能不能增加节表
DWORD dwNumofSection = file_header->NumberOfSections;
DWORD dwLfanew = dos->e_lfanew;
DWORD dwSizeofHeader = option_header->SizeOfHeaders;
printf("%d,%d,", sizeof(*nt), sizeof(*section_header));
DWORD dwSizeNowHeader = dwLfanew + sizeof(*nt) + dwNumofSection * sizeof(*section_header);
//1.1 后续空间无法添加节表,移动头部信息
if (dwSizeofHeader - dwSizeNowHeader < 2 * sizeof(section_header)) {
memcpy(FileBuffer + 0x40, FileBuffer + dos->e_lfanew, sizeof(*nt) + dwNumofSection * sizeof(*section_header));
dos->e_lfanew = 0x40;
nt = (PIMAGE_NT_HEADERS)(FileBuffer + dos->e_lfanew);
file_header = (PIMAGE_FILE_HEADER)(&(nt->FileHeader));
option_header = (PIMAGE_OPTIONAL_HEADER)(&(nt->OptionalHeader));
section_header = PIMAGE_SECTION_HEADER((char*)option_header + file_header->SizeOfOptionalHeader);
DWORD dwNumofSection = file_header->NumberOfSections;
DWORD dwLfanew = dos->e_lfanew;
DWORD dwSizeofHeader = option_header->SizeOfHeaders;
printf("%d,%d,", sizeof(*nt), sizeof(*section_header));
DWORD dwSizeNowHeader = dwLfanew + sizeof(*nt) + dwNumofSection * sizeof(*section_header);
}
//1.2添加两个节表
//(1)移动到最后一个节表处
for (int i = 0; i < dwNumofSection-1; i++) {
section_header = section_header + 1;
}
//(2)添加节表
DWORD dwAddSectionAddress = dwSizeNowHeader;
PIMAGE_SECTION_HEADER pAddsection = (PIMAGE_SECTION_HEADER)malloc(sizeof(IMAGE_SECTION_HEADER));
if (pAddsection == NULL) {
return FALSE;
}
memset(pAddsection, 0, sizeof(*pAddsection));
char newName[] = ".newSec";
memcpy(pAddsection, newName, 8);
//计算内存中对齐的大小
DWORD VirtulaSize = NULL;
if (section_header->SizeOfRawData < section_header->Misc.VirtualSize) {
VirtulaSize = ((section_header->Misc.VirtualSize /0x1000)+1) * 0x1000;
}
else
{
VirtulaSize = (section_header->SizeOfRawData/0x1000+1)*0x1000;
}
//添加节表信息
pAddsection->VirtualAddress = section_header->VirtualAddress + VirtulaSize;
pAddsection->Misc.VirtualSize = 0x600;
pAddsection->SizeOfRawData = 0x1000;
pAddsection->PointerToRawData = section_header->PointerToRawData + section_header->SizeOfRawData;
pAddsection->Characteristics = IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_EXECUTE | IMAGE_SCN_CNT_CODE;
//写入到fileBuffer中
memcpy(FileBuffer + dwAddSectionAddress, pAddsection, 40);
char secode_section[40] = { 0 };
memcpy(FileBuffer + dwAddSectionAddress + 40, secode_section, 40);
// 将节的数量加1
file_header->NumberOfSections = file_header->NumberOfSections + 1;
//申请新的空间
DWORD* AddSectionSpace = (DWORD*)malloc(_msize(FileBuffer) +0x1000);
if (AddSectionSpace == NULL) {
return FALSE;
}
memset(AddSectionSpace, 0, _msize(AddSectionSpace));
memcpy(AddSectionSpace, FileBuffer, _msize(FileBuffer));
AddCode((char*)AddSectionSpace, NewFileName, file_header->NumberOfSections);
return TRUE;
}
3、修改入口点
在增加节之后,可以往增加的节中添加自己的代码,并将程序入口点修改到自己的代码处。其中需要注意点如下:
(1)需要关闭PE的地址随机化功能,可以通过修改OptionHeader头中的DllCharacteristics = 0x8100,来绕过地址随机化。
(2)计算E8和E9的时候,CALL 偏移:跳转地址 = 当前地址+5+x ->x = 跳转地址 -当前地址-5。求出x就是E8(CALL指令的硬编码)后的跳转偏移。
(3)需要进行RVA和FOA的转换,所有的地址都是加载到内存后的地址。
代码如下:其中AddNumSction是需要将代码插入到哪个节中
BOOL AddCode(char* FileBuffer, LPCSTR NewFileName,INT AddNumSction) {
PIMAGE_DOS_HEADER dos = NULL;
PIMAGE_NT_HEADERS nt = NULL;
PIMAGE_FILE_HEADER file_header = NULL;
PIMAGE_SECTION_HEADER section_header = NULL;
PIMAGE_OPTIONAL_HEADER option_header = NULL;
DWORD dwSizeofOptionHeader = NULL;
DWORD dwVirtualAddress = NULL;
DWORD dwPointerRawData = NULL;
DWORD dwEntryCode = NULL;
DWORD dwImageBase = NULL;
DWORD dwUnInitDataSize = NULL;
DWORD dwNumofSection = NULL;
DWORD dwCodeWirteAddress = NULL;
dos = (PIMAGE_DOS_HEADER)FileBuffer;
nt = (PIMAGE_NT_HEADERS)(FileBuffer + dos->e_lfanew);
file_header = (PIMAGE_FILE_HEADER)(&(nt->FileHeader));
option_header = (PIMAGE_OPTIONAL_HEADER)(&(nt->OptionalHeader));
dwSizeofOptionHeader = file_header->SizeOfOptionalHeader;
section_header = (PIMAGE_SECTION_HEADER)((char*)option_header + dwSizeofOptionHeader);
dwNumofSection = file_header->NumberOfSections;
dwImageBase = option_header->ImageBase;
dwEntryCode = option_header->AddressOfEntryPoint + dwImageBase;
DWORD Charac = IMAGE_SCN_CNT_CODE | IMAGE_SCN_MEM_EXECUTE | IMAGE_SCN_MEM_READ;
/* for (int i = 0; i < dwNumofSection; i++) {
if (section_header->SizeOfRawData > 0 && (section_header->Characteristics & Charac)) {
break;
}
section_header = (PIMAGE_SECTION_HEADER)((char*)section_header + 40);
}*/
for (int i = 0; i < AddNumSction-1; i++) {
section_header = (PIMAGE_SECTION_HEADER)((char*)section_header + 40);
}
dwUnInitDataSize = section_header->Misc.VirtualSize;
dwPointerRawData = section_header->PointerToRawData;
dwVirtualAddress = section_header->VirtualAddress;
dwCodeWirteAddress = dwUnInitDataSize + dwPointerRawData;
HMODULE user32 = LoadLibraryA("user32.dll");
if (user32 == NULL) {
return FALSE;
}
DWORD MessageboxAAddress = (DWORD)GetProcAddress(user32, "MessageBoxA");
//printf("%llx ", MessageboxAAddress);
char message[] = "你是一个傻逼";
//MessageBoxA(0, message, 0, 0);
//MessageBoxA(0, message, 0, 0);
char buffer[22] = {0x6A,0x00, //push 0
0x6A,0x00, //push 0
0xB8,0x00,0x00,0x00,0x00, //mov eax,[message]
0x50, //push eax
0x6A,0x00, //push 0
0xE8,0x00,0x00,0x00,0x00,
0xE9,0x00,0x00,0x00,0x00};
//printf("%d",sizeof(message));
//去掉地址随机化
option_header->DllCharacteristics = 0x8100;
//将代码写入到FileBuffer中
memcpy(FileBuffer + dwCodeWirteAddress, buffer, sizeof(buffer));
//将要写入的输出字符写到fileBuffer中
memcpy(FileBuffer + dwCodeWirteAddress + sizeof(buffer), message, sizeof(message));
//计算CALL 偏移:跳转地址 = 当前地址+5+x ->x = 跳转地址 -当前地址-5
DWORD ChangeCodeAddress = dwCodeWirteAddress + 13;
DWORD dwCallShift = MessageboxAAddress - (dwImageBase + dwVirtualAddress + dwUnInitDataSize +12+5);
//改变E8后面的跳转偏移
*(DWORD*)((char*)FileBuffer + ChangeCodeAddress) = dwCallShift;
//改变第三个参数
DWORD dwTextAddress = dwUnInitDataSize + sizeof(buffer) + dwImageBase + dwVirtualAddress;
/*DWORD TextShift = (dwImageBase + dwVirtualAddress + dwCodeWirteAddress + sizeof(buffer) - (dwImageBase +dwVirtualAddress+dwCodeWirteAddress+13));*/
*(DWORD*)((char*)FileBuffer + dwCodeWirteAddress + 5) = dwTextAddress;
//Jmp到原入口地址
DWORD EntryCodeShift = dwEntryCode - (dwImageBase + dwVirtualAddress + dwUnInitDataSize + sizeof(buffer));
*(DWORD*)((char*)FileBuffer + dwCodeWirteAddress + 18) = EntryCodeShift;
//修改入口地址
*(&(option_header->AddressOfEntryPoint)) = dwVirtualAddress+ dwUnInitDataSize;
FILE* fpp = NULL;
fopen_s(&fpp,NewFileName,"wb+");
//printf("%x",_msize(FileBuffer));
if (fpp == NULL) {
return FALSE;
}
fwrite(FileBuffer, sizeof(char), _msize(FileBuffer)/sizeof(char), fpp);
fflush(fpp);
fclose(fpp);
return TRUE;
}
4、结果
修改前:只有9个段,并且EP为112C1
修改后:增加了一个区段,并且入口地址修改成了自己的地址
并且增加了可读可执行的属性