第二十五课 FileBuffer-ImageBuffer
1.PE文件执行的总过程
第二十三课已经说过了,文件先复制一份读入虚拟内存中(FileBuffer),接着要运行时将FileBuffer中的文件数据拉伸,重载到4GB的虚拟内存中(ImageBuffer)
- 但ImageBuffer还不是文件运行时在内存的真正状态,ImageBuffer还没表示文件已经被执行了
- ImageBuffer的文件数据已被拉伸,已经无限接近可被windows执行的文件格式,但是还没被真正执行,操作系统后续还需要做一些事,
2.SizeOfRawData一定大于Misc.VirtualSize吗
- Misc.VirtualSize表示节在内存中还没对齐的大小;SizeOfRawData表示节在硬盘上文件对齐后的大小
- 在我们int arr[100] = {0}定义数组时,这个数组已经被初始化,这100个数据被存放在某个节里并分配好了空间,这块空间无论在文件还是内存都是一样大的,所以这种情况一般SizeOfRawData大于或等于Misc.VirtualSize
- 但是当我们数组只声明不赋值时,int arr[100];这时数据还未初始化,所以文件在硬盘上是不会留出这100的空间的,所以SizeOfRawData不会算上这未初始化的空间,但是Misc.VirtualSize为加载到内存时节未对齐的大小,他是需要计算上未初始化的空间的,所以这种情况下Misc.VirtualSize可能大于SizeOfRawData
3.内存偏移地址和文件偏移地址的转换
现在一个文件加载到虚拟内存中的某个数据地址是0x501234,那么我们应该如何把内存地址转换为文件在硬盘上的地址?
- 先以0x501234-0x500000=0x1234,内存地址减去imagebase得出偏移
- 由VirtualAddress为1000,2000,3000,可以看到0x1234在1000到2000的区间,所以0x1234在第一个节表。
- 0x1234-0x1000 = 0x234得出相对所在节的偏移量
- 由于第一个节的PointerToRawData为0x400,假设FileBuffer的起始地址为0(相对的),则0x501234对应的文件偏移地址为0x400 + 0x234 = 0x634
作业
//函数声明
//**************************************************************************
//ReadPEFile:将文件读取到缓冲区
//参数说明:
//lpszFile 文件路径
//pFileBuffer 缓冲区指针
//返回值说明:
//读取失败返回0 否则返回实际读取的大小
//**************************************************************************
DWORD ReadPEFile(IN LPSTR lpszFile,OUT LPVOID* pFileBuffer);
//**************************************************************************
//CopyFileBufferToImageBuffer:将文件从FileBuffer复制到ImageBuffer
//参数说明:
//pFileBuffer FileBuffer指针
//pImageBuffer ImageBuffer指针
//返回值说明:
//读取失败返回0 否则返回复制的大小
//**************************************************************************
DWORD CopyFileBufferToImageBuffer(IN LPVOID pFileBuffer,OUT LPVOID* pImageBuffer);
//**************************************************************************
//CopyImageBufferToNewBuffer:将ImageBuffer中的数据复制到新的缓冲区
//参数说明:
//pImageBuffer ImageBuffer指针
//pNewBuffer NewBuffer指针
//返回值说明:
//读取失败返回0 否则返回复制的大小
//**************************************************************************
DWORD CopyImageBufferToNewBuffer(IN LPVOID pImageBuffer,OUT LPVOID* pNewBuffer);
//**************************************************************************
//MemeryTOFile:将内存中的数据复制到文件
//参数说明:
//pMemBuffer 内存中数据的指针
//size 要复制的大小
//lpszFile 要存储的文件路径
//返回值说明:
//读取失败返回0 否则返回复制的大小
//**************************************************************************
BOOL MemeryTOFile(IN LPVOID pMemBuffer,IN size_t size,OUT LPSTR lpszFile);
//**************************************************************************
//RvaToFileOffset:将内存偏移转换为文件偏移
//参数说明:
//pFileBuffer FileBuffer指针
//dwRva RVA的值
//返回值说明:
//返回转换后的FOA的值 如果失败返回0
//**************************************************************************
DWORD RvaToFileOffset(IN LPVOID pFileBuffer,IN DWORD dwRva);
硬要用一级指针的错误代码示例
每个单个函数都可以正常运行,但是到了main函数想把几个函数串起来就不行了,memeryToFile存盘函数需要上面的函数完成后的size和pBuffer两个变量,但是上面的函数只能返回一个变量
所以我们选择返回我们函数里算好的size,然后设置个全局变量pBuffer我们选择用函数去帮我们算,如果我们不用指针,函数算完结果只会在函数内,函数外的pBuffer的值还是没改变,所以我们要让函数去改变全局变量pBuffer,就得使用指针,这里是二级指针是因为这个pBuffer本来就是个指针地址。
#pragma warning(disable:4996)
#include <iostream>
#include <windows.h>
#include <winnt.h>
using namespace std;
int F_Size(FILE* fp)
{
fseek(fp,0,2);
int len = ftell(fp);
fseek(fp, 0, 0);
return len;
}
LPVOID Read_PE()
{
FILE* fp;
LPVOID pFileBuffer;
fp = fopen("C:\\Windows\\notepad.exe", "rb");
int F_size = F_Size(fp);
pFileBuffer = malloc(F_size);
if (!pFileBuffer)
{
printf("分配空间失败");
fclose(fp);
return NULL;
}
size_t read = fread(pFileBuffer,F_size,1,fp);
if (!read)
//如果读取失败,返回读取到的元素为0,!read则为!0=1,则会运行下面代码
{
printf("读取文件失败");
fclose(fp);
}
fclose(fp);
return pFileBuffer;
}
LPVOID pFileBuffer_pImageBuffer()
{
LPVOID pFileBuffer = NULL;
LPVOID pImageBuffer = NULL;
PIMAGE_DOS_HEADER pDos_header = NULL;
PIMAGE_NT_HEADERS pNT_header = NULL;
PIMAGE_FILE_HEADER pPE_header = NULL;
PIMAGE_OPTIONAL_HEADER pOption_header = NULL;
PIMAGE_SECTION_HEADER pSection_header = NULL;
//读取文件函数读取
pFileBuffer = Read_PE();
//验证MZ标志
pDos_header = (PIMAGE_DOS_HEADER)pFileBuffer;
if (pDos_header->e_magic != IMAGE_DOS_SIGNATURE)
{
printf("不是有效的MZ标志\n");
free(pFileBuffer);
}
//计算NT头起始地址*
pNT_header = (PIMAGE_NT_HEADERS)((DWORD)pFileBuffer + pDos_header->e_lfanew);
//验证NT头
if (pNT_header->Signature != IMAGE_NT_SIGNATURE)
{
printf("不是有效的PE标志\n");
free(pFileBuffer);
return 0;
}
//标准PE头和可选PE头
pPE_header = (PIMAGE_FILE_HEADER)((DWORD)pNT_header + 4);
pOption_header = (PIMAGE_OPTIONAL_HEADER)((DWORD)pPE_header + IMAGE_SIZEOF_FILE_HEADER);
//节表
pSection_header = (PIMAGE_SECTION_HEADER)((DWORD)pOption_header + pPE_header->SizeOfOptionalHeader);
pImageBuffer = malloc(pOption_header->SizeOfImage);
if (!pImageBuffer)
{
printf("分配空间失败\n");
return 0;
}
printf("SizeOfImage:%x\n", pOption_header->SizeOfImage);
memset(pImageBuffer,0,pOption_header->SizeOfImage);
memcpy(pImageBuffer, pDos_header, pOption_header->SizeOfHeaders);
for (int i = 0; i < pPE_header->NumberOfSections; i++,pSection_header++)
{
memcpy((LPVOID)((DWORD)pImageBuffer + pSection_header->VirtualAddress),(LPVOID)((DWORD)pDos_header + pSection_header->PointerToRawData),pSection_header->SizeOfRawData);
}
pSection_header = NULL;
printf("拷贝完成\n");
return pImageBuffer;
}
DWORD pImageBuffer_pFileBuffer()
{
LPVOID pImageBuffer = pFileBuffer_pImageBuffer();
LPVOID pNFileBuffer = NULL;
PIMAGE_DOS_HEADER pDos_header = NULL;
PIMAGE_NT_HEADERS pNT_header = NULL;
PIMAGE_FILE_HEADER pPE_header = NULL;
PIMAGE_OPTIONAL_HEADER64 pOption_header = NULL;
PIMAGE_SECTION_HEADER pSection_header = NULL;
//算出ImageBuffer中的dos头nt头pe头节表地址
pDos_header = (PIMAGE_DOS_HEADER)pImageBuffer;
pNT_header = (PIMAGE_NT_HEADERS)((DWORD)pDos_header + pDos_header->e_lfanew);
pPE_header = (PIMAGE_FILE_HEADER)((DWORD)pNT_header + 4);
pOption_header = (PIMAGE_OPTIONAL_HEADER64)((DWORD)pPE_header + IMAGE_SIZEOF_FILE_HEADER);
pSection_header = (PIMAGE_SECTION_HEADER)((DWORD)pOption_header + pPE_header->SizeOfOptionalHeader);
//验证
if (!pImageBuffer)
{
printf("ImageBuffer获取失败\n");
return 0;
}
if (pDos_header->e_magic != IMAGE_DOS_SIGNATURE)
{
printf("不是有效的MZ标志\n");
return 0;
}
if (pNT_header->Signature != IMAGE_NT_SIGNATURE)
{
printf("不是有效的PE标志\n");
}
//申请新空间pNFileBuffer,计算空间大小
for (int i = 1; i < pPE_header->NumberOfSections; i++, pSection_header++)
{
}
//循环已经自加到最后一个节,注意这里i是1,现在用最后一个节的地址加上节大小就可以得到文件末尾地址
DWORD File_Size = (DWORD)pSection_header->PointerToRawData + (DWORD)pSection_header->SizeOfRawData;
printf("newFileSize:%x\n", File_Size);
pNFileBuffer = malloc(File_Size);
memset(pNFileBuffer, 0, File_Size);
//先把头和节表都复制到pNFileBuffer
memcpy(pNFileBuffer,pImageBuffer,pOption_header->SizeOfHeaders);
//复制节memcpy(pNFileBuffer+节文件偏移,pImageBuffer的内存节的偏移,节文件大小)
pSection_header = (PIMAGE_SECTION_HEADER)((DWORD)pOption_header + pPE_header->SizeOfOptionalHeader);
for (int i = 0; i < pPE_header->NumberOfSections; i++, pSection_header++)
{
memcpy((LPVOID)((DWORD)pNFileBuffer + pSection_header->PointerToRawData),(LPVOID)((DWORD)pImageBuffer + pSection_header->VirtualAddress),pSection_header->SizeOfRawData);
}
printf("ImageBuffer to FileBuffer 完成");
return File_Size;
}
BOOL MemeryToFile(LPVOID FileBuffer, DWORD Size)
{
FILE* fpw = fopen("E:\\nwenotepad.exe","wb");
if (!fpw)
{
printf("打开文件失败\n");
return 0;
}
if (fwrite(FileBuffer, 1, Size, fpw) == 0)
{
printf("文件写入失败");
return 0;
}
fclose(fpw);
fpw = NULL;
printf("存盘成功");
}
int main()
{
pImageBuffer_pFileBuffer();
//MemeryToFile();
}
注意:
第88行:使用pImageBuffer代替ImageBase,因为我们这是c语言模拟pe加载的过程,内存基址就是我们申请的内存的起始地址,然后再加上内存中的偏移就可以算出我们内存中需要把节从哪开始存,第二个参数就是文件中节的偏移,用前面的pDOS_header加上文件偏移即可
二级指针使用
感觉就是在全局或者main定义一个变量和他的指针变量,变量就是在main里面,指针变量用于在其他函数中对变量的操作,使得函数执行后全局变量也被其改变,所以在函数中把需要用到变量的地方用指针变量*Buffer代替就好了
#pragma warning(disable:4996)
#include <iostream>
#include <windows.h>
#include <winnt.h>
using namespace std;
int F_Size(FILE* fp)
{
fseek(fp,0,2);
int len = ftell(fp);
fseek(fp, 0, 0);
return len;
}
DWORD Read_PE(LPVOID* ppFileBuffer)
{
FILE* fp;
fp = fopen("C:\\Windows\\notepad.exe", "rb");
int F_size = F_Size(fp);
*ppFileBuffer = malloc(F_size);
if (!*ppFileBuffer)
{
printf("分配空间失败");
fclose(fp);
return NULL;
}
size_t read = fread(*ppFileBuffer,F_size,1,fp);
if (!read)
//如果读取失败,返回读取到的元素为0,!read则为!0=1,则会运行下面代码
{
printf("读取文件失败");
fclose(fp);
}
fclose(fp);
return F_size;
}
DWORD pFileBuffer_pImageBuffer(LPVOID pFileBuffer,LPVOID* ppImageBuffer)
{
PIMAGE_DOS_HEADER pDos_header = NULL;
PIMAGE_NT_HEADERS pNT_header = NULL;
PIMAGE_FILE_HEADER pPE_header = NULL;
PIMAGE_OPTIONAL_HEADER pOption_header = NULL;
PIMAGE_SECTION_HEADER pSection_header = NULL;
//验证MZ标志
pDos_header = (PIMAGE_DOS_HEADER)pFileBuffer;
if (pDos_header->e_magic != IMAGE_DOS_SIGNATURE)
{
printf("不是有效的MZ标志\n");
free(pFileBuffer);
}
//计算NT头起始地址*
pNT_header = (PIMAGE_NT_HEADERS)((DWORD)pFileBuffer + pDos_header->e_lfanew);
//验证NT头
if (pNT_header->Signature != IMAGE_NT_SIGNATURE)
{
printf("不是有效的PE标志\n");
free(pFileBuffer);
return 0;
}
//标准PE头和可选PE头
pPE_header = (PIMAGE_FILE_HEADER)((DWORD)pNT_header + 4);
pOption_header = (PIMAGE_OPTIONAL_HEADER)((DWORD)pPE_header + IMAGE_SIZEOF_FILE_HEADER);
//节表
pSection_header = (PIMAGE_SECTION_HEADER)((DWORD)pOption_header + pPE_header->SizeOfOptionalHeader);
*ppImageBuffer = malloc(pOption_header->SizeOfImage);
if (!*ppImageBuffer)
{
printf("分配空间失败\n");
return 0;
}
printf("SizeOfImage:%x\n", pOption_header->SizeOfImage);
memset(*ppImageBuffer,0,pOption_header->SizeOfImage);
memcpy(*ppImageBuffer, pDos_header, pOption_header->SizeOfHeaders);
for (int i = 0; i < pPE_header->NumberOfSections; i++,pSection_header++)
{
memcpy((LPVOID)((DWORD)*ppImageBuffer + pSection_header->VirtualAddress),(LPVOID)((DWORD)pDos_header + pSection_header->PointerToRawData),pSection_header->SizeOfRawData);
}
pSection_header = NULL;
printf("拷贝完成\n");
return pOption_header->SizeOfImage;
}
DWORD pImageBuffer_pFileBuffer(LPVOID pImageBuffer,LPVOID* ppNFileBuffer)
{
PIMAGE_DOS_HEADER pDos_header = NULL;
PIMAGE_NT_HEADERS pNT_header = NULL;
PIMAGE_FILE_HEADER pPE_header = NULL;
PIMAGE_OPTIONAL_HEADER64 pOption_header = NULL;
PIMAGE_SECTION_HEADER pSection_header = NULL;
//算出ImageBuffer中的dos头nt头pe头节表地址
pDos_header = (PIMAGE_DOS_HEADER)pImageBuffer;
pNT_header = (PIMAGE_NT_HEADERS)((DWORD)pDos_header + pDos_header->e_lfanew);
pPE_header = (PIMAGE_FILE_HEADER)((DWORD)pNT_header + 4);
pOption_header = (PIMAGE_OPTIONAL_HEADER64)((DWORD)pPE_header + IMAGE_SIZEOF_FILE_HEADER);
pSection_header = (PIMAGE_SECTION_HEADER)((DWORD)pOption_header + pPE_header->SizeOfOptionalHeader);
//验证
if (!pImageBuffer)
{
printf("ImageBuffer获取失败\n");
return 0;
}
if (pDos_header->e_magic != IMAGE_DOS_SIGNATURE)
{
printf("不是有效的MZ标志\n");
return 0;
}
if (pNT_header->Signature != IMAGE_NT_SIGNATURE)
{
printf("不是有效的PE标志\n");
}
//申请新空间pNFileBuffer,计算空间大小
for (int i = 1; i < pPE_header->NumberOfSections; i++, pSection_header++)
{
}
//循环已经自加到最后一个节,注意这里i是1,现在用最后一个节的地址加上节大小就可以得到文件末尾地址
DWORD File_Size = (DWORD)pSection_header->PointerToRawData + (DWORD)pSection_header->SizeOfRawData;
printf("newFileSize:%x\n", File_Size);
*ppNFileBuffer = malloc(File_Size);
memset(*ppNFileBuffer, 0, File_Size);
//先把头和节表都复制到pNFileBuffer
memcpy(*ppNFileBuffer,pImageBuffer,pOption_header->SizeOfHeaders);
//复制节memcpy(pNFileBuffer+节文件偏移,pImageBuffer的内存节的偏移,节文件大小)
pSection_header = (PIMAGE_SECTION_HEADER)((DWORD)pOption_header + pPE_header->SizeOfOptionalHeader);
for (int i = 0; i < pPE_header->NumberOfSections; i++, pSection_header++)
{
memcpy((LPVOID)((DWORD)*ppNFileBuffer + pSection_header->PointerToRawData),(LPVOID)((DWORD)pImageBuffer + pSection_header->VirtualAddress),pSection_header->SizeOfRawData);
}
printf("ImageBuffer to FileBuffer 完成\n");
return File_Size;
}
BOOL MemeryToFile(LPVOID FileBuffer, DWORD Size)
{
FILE* fpw = fopen("E:\\nwenotepad.exe","wb");
if (!fpw)
{
printf("打开文件失败\n");
return 0;
}
if (fwrite(FileBuffer, 1, Size, fpw) == 0)
{
printf("文件写入失败");
return 0;
}
fclose(fpw);
fpw = NULL;
printf("存盘成功");
}
int main()
{
LPVOID pFileBuffer = NULL;
LPVOID* ppFileBuffer = &pFileBuffer;
LPVOID pImageBuffer = NULL;
LPVOID* ppImageBuffer = &pImageBuffer;
LPVOID pNFileBuffer = NULL;
LPVOID* ppNFileBuffer = &pNFileBuffer;
DWORD Size_FileBuffer = Read_PE(ppFileBuffer);
if (!Size_FileBuffer)
{
printf("FileBuffer函数调用失败 \n");
return 0;
}
//pFileBuffer = *ppFileBuffer;
DWORD Size_ImageBuffer = pFileBuffer_pImageBuffer(pFileBuffer,ppImageBuffer);
if (!Size_ImageBuffer)
{
printf("ImageBuffer函数调用失败 \n");
return 0;
}
DWORD Size_NFileBuffer = pImageBuffer_pFileBuffer(pImageBuffer,ppNFileBuffer);
if (!Size_NFileBuffer)
{
printf("NFileBuffer函数调用失败 \n");
return 0;
}
MemeryToFile(pNFileBuffer,Size_NFileBuffer);
}
第二十六课 PE文件 代码节空白区添加代码
1.call、jmp与push指令的硬编码
这里简单看看硬编码,进入vs反汇编看看编译器怎么写的
- call指令的硬编码:E8 后面跟了4个字节(转换后的地址)
注意:硬编码为E8的call,后面跟的地址是相对地址;还有硬编码为FF15的call,后面跟的是绝对地址,即ImageBase + RVA后的地址值
- jmp指令的硬编码:E9 后面跟了4个字节(转换后的地址)
- push指令的硬编码:6A 参数的值(传入函数参数的值是什么对应的参数硬编码就是什么)
但是我们可以看到指令后面跟的地址不是我们直接想跳转的地址,
E8 和 E9 后面4字节地址X的转换公式:X = 真正要跳转的地址 - E8这条指令的下一行地址
公式中所用到的地址值全部是可执行文件在4GB虚拟内存中的地址值,不是文件地址
2.添加代码
效果
修改思路
具体操作思路
- 先确定我们需要插入的代码,先push函数的四个参数,在call MessageBoxa函数的地址,最后jmp回原来的程序入口,大概
6A 00 6A 00 6A 00 6A 00 E8 函数地址 E9 程序入口地址
所以我们需要找出两个东西,MessageBoxa函数地址和程序入口 - MessageBoxA函数地址:需要打开OD文件,命令行输入:
bp MessageBoxA
,相当于打个断点,我们在上方工具栏中的B按钮查看断点,显示的断点所在地址就是MessageBoxA函数的起始地址 - 程序入口地址:通过可选PE头的AddressOfEntryPoint字段可以获取程序入口地址偏移量,+加上ImageBase字段就可以获取内存中的程序入口地址
- 最后找一个节最后的空白区,只要能放下18个字节的硬编码就可以
注:
E8后面地址计算:E8下一条指令地址在需要从FileBuffer转换成ImageBuffer,用现在文件偏移-节初始地址=文件中对于节的偏移是775D,内存中节的初始位置(imagebase1000000
+virtualAddress1000
)+775D
=内存中E8的下一条指令内存地址100875D
,真正要跳转的地址76120F40 - E8这条指令的下一行地址=7511 87E3
E9后面地址转换:文件中指令地址对于节的偏移7762
,1001000+7762=E9下一条指令内存地址1008762
真正要跳转的地址100739D - E9这条指令的下一行地址=FFFFEC3B
3.c++模拟添加shellcode
参考链接:https://www.cnblogs.com/cspecialr/p/17357461.html
LPVOID ShellCode(LPVOID pImageBuffer)
{
PIMAGE_DOS_HEADER pDos_header = NULL;
PIMAGE_NT_HEADERS pNT_header = NULL;
PIMAGE_FILE_HEADER pPE_header = NULL;
PIMAGE_OPTIONAL_HEADER32 pOption_header = NULL;
PIMAGE_SECTION_HEADER pSection_header = NULL;
PBYTE ShellCodeBegin = NULL;
//算出ImageBuffer中的dos头nt头pe头节表地址
pDos_header = (PIMAGE_DOS_HEADER)pImageBuffer;
pNT_header = (PIMAGE_NT_HEADERS)((DWORD)pDos_header + pDos_header->e_lfanew);
pPE_header = (PIMAGE_FILE_HEADER)((DWORD)pNT_header + 4);
pOption_header = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pPE_header + IMAGE_SIZEOF_FILE_HEADER);
pSection_header = (PIMAGE_SECTION_HEADER)((DWORD)pOption_header + pPE_header->SizeOfOptionalHeader);
//检查节空间
if (pSection_header->SizeOfRawData - pSection_header->Misc.VirtualSize < ShellCodeLen)
{
printf("节空间不足\n");
free(pImageBuffer);
}
printf("SizeOfRawData:%x\nMisc.VirtualSize:%x\n", pSection_header->SizeOfRawData, pSection_header->Misc.VirtualSize);
//加入初始shellcode
ShellCodeBegin = PBYTE((DWORD)pImageBuffer + pSection_header->VirtualAddress + pSection_header->Misc.VirtualSize);
if (!memcpy(ShellCodeBegin, Shellcode, ShellCodeLen))
{
printf("加入初始shellcode失败\n");
free(pImageBuffer);
}
//E8
DWORD CallAddr = (DWORD)MessageBoxA - ((DWORD)ShellCodeBegin + 0xD - (DWORD)pImageBuffer + (DWORD)pOption_header->ImageBase);
if (!CallAddr)
{
printf("E8 Error\n");
return 0;
}
*(PDWORD)(ShellCodeBegin + 0x9) = CallAddr;
printf("E8添加成功\n");
//E9
DWORD JmpAddr = (pOption_header->ImageBase + pOption_header->AddressOfEntryPoint) - (pOption_header->ImageBase + (DWORD)ShellCodeBegin + ShellCodeLen - (DWORD)pImageBuffer);
if (!CallAddr)
{
printf("E9 Error\n");
return 0;
}
*(PDWORD)(ShellCodeBegin + 0xE) = JmpAddr;
printf("E9添加成功\n");
//OEP
pOption_header->AddressOfEntryPoint = pSection_header->VirtualAddress + pSection_header->Misc.VirtualSize;
printf("OEP:%x\n", pOption_header->AddressOfEntryPoint);
printf("OEP修改成功\n");
return pImageBuffer;
}
int main()
{
LPVOID pFileBuffer = NULL;
LPVOID* ppFileBuffer = &pFileBuffer;
LPVOID pImageBuffer = NULL;
LPVOID* ppImageBuffer = &pImageBuffer;
LPVOID pNFileBuffer = NULL;
LPVOID* ppNFileBuffer = &pNFileBuffer;
DWORD Size_FileBuffer = Read_PE(ppFileBuffer);
if (!Size_FileBuffer)
{
printf("FileBuffer函数调用失败 \n");
return 0;
}
//pFileBuffer = *ppFileBuffer;
DWORD Size_ImageBuffer = pFileBuffer_pImageBuffer(pFileBuffer,ppImageBuffer);
if (!Size_ImageBuffer)
{
printf("ImageBuffer函数调用失败 \n");
return 0;
}
pImageBuffer = ShellCode(pImageBuffer);
DWORD Size_NFileBuffer = pImageBuffer_pFileBuffer(pImageBuffer,ppNFileBuffer);
if (!Size_NFileBuffer)
{
printf("NFileBuffer函数调用失败 \n");
return 0;
}
MemeryToFile(pNFileBuffer,Size_NFileBuffer);
}
注意:
- 我们在添加shellcode的时候都是在ImageBuffer,比如计算ShellCodeBegin都是以ImageBuffer为基址,但是我们在计算E8E9的下一个命令的地址时我们需要以ImageBase为基址,因为他需要真正在内存中的地址去计算
- 在排查异常的时候,文件中和内存中的地址别搞混,ImageBuffer和ImageBase
第二十七课 新增节 扩大节-添加代码
参考链接:https://blog.csdn.net/Edimade/article/details/124540973
新增节后添加数据时,应看看原本该内存申请的空间是否足够
1.新增节的条件
判断空间是否足够添加一个节表;判断计算方法:SizeOfHeader - (DOS + 垃圾数据 + PE标记 + 标志PE头 + 可选PE头 + 已存在节表) >=2个节表大小(一个节表40字节)
- 为什么需要2个节表大小:有时windows会根据一个结构后面是否有多个0x00来判断这个结构是否结束。所以算是windows规定的格式吧:故最后一个节表后面还要补跟节表宽度一样的0;但是其实最后一个节表后面不是40个0或者跟了一些其他数据也是可行的,但由于没有满足规定的格式要求,不知道什么时候就不好使了,所以我们新增节表时还是按照标准规定来
如果空间不够添加新节怎么办?
把DOS stub的数据全部覆盖掉,DOS_HEADER 的 lfanew 字段直接指向它的下一个字节,把剩下的整个头(整个NT头)包括节表全部抬升上来,那么新节表最后一个节表的位置和第一个节之间极大概率会有足够空间添加新节表的。
2.新增节的思路
修改的数据
- 添加一个新的节(可以copy一份),在新增节后面 填充一个节大小的000
2) 修改PE头中节的数量(NumberOfSections字段)
3) 修改sizeOfImage的大小(原有值 + 新增节的大小,还要内存对齐)
4) 再原有数据的最后,新增一个节的数据(内存对齐的整数倍,添加时节的大小如果设置为内存对齐的整数倍,后面就不用再担心对齐的事).
5)修正新增节表的属性
- Name,这个按照ASCII码随便取,最好长度限制在8字节内
- Misc.VirtualSize,如果此时我们还不知道添加的数据长度(没对齐前),那么直接就按照对齐后的大小写即可,比如我们要新增节大小为0x1000(考虑了内存对齐),那么不管里面要用多少字节数据,VirtualSize的值就按照0x1000写
- VirtualAddress,内存起始偏移地址,上一个节表的VirtualAddress + VirtualSize内存对齐后,得到上一个节内存对齐后的结尾位置,所以新增节表的VirtualAddress就是这个值。
- SizeOfRawData,根据文件对齐粒度来修正这个值,比如上面如果设定了VirtualSize的值为0x1000,说明这个节的这0x1000字节都是有用数据,那么此节在硬盘上时也应该有这0x1000字节的数据,所以此时根据文件对齐粒度来修正这个值,如果为0x200或者0x1000,就不用修改0x1000,因为已经满足是文件对齐的整数倍了
- PointerToRawData,文件对齐后的文件地址,通过上一个节表的PointerToRawData + SizeOfRawData
- Characteristics,按照我们想要的属性来修改即可(可读、可写、可执行等)
3.代码实现新增节
这边的代码实现有点乱,后面加壳项目的加密壳程序也是类似的,可以参考
LPVOID AddSection(LPVOID pImageBuffer)
{
PIMAGE_DOS_HEADER pDos_header = NULL;
PIMAGE_NT_HEADERS pNT_header = NULL;
PIMAGE_FILE_HEADER pPE_header = NULL;
PIMAGE_OPTIONAL_HEADER32 pOption_header = NULL;
PIMAGE_SECTION_HEADER pSection_header = NULL;
PIMAGE_SECTION_HEADER pLastSection_header = NULL;
PIMAGE_SECTION_HEADER pNewSection_header = NULL;
LPVOID pFirstSection_header = NULL;
BOOL flag = false;
//算出ImageBuffer中的dos头nt头pe头节表地址
pDos_header = (PIMAGE_DOS_HEADER)pImageBuffer;
pNT_header = (PIMAGE_NT_HEADERS)((DWORD)pDos_header + pDos_header->e_lfanew);
pPE_header = (PIMAGE_FILE_HEADER)((DWORD)pNT_header + 4);
pOption_header = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pPE_header + IMAGE_SIZEOF_FILE_HEADER);
pFirstSection_header = (PIMAGE_SECTION_HEADER)((DWORD)pOption_header + pPE_header->SizeOfOptionalHeader);
pSection_header = (PIMAGE_SECTION_HEADER)((DWORD)pOption_header + pPE_header->SizeOfOptionalHeader);
LPVOID pLastDos = LPVOID((DWORD)pImageBuffer + sizeof(IMAGE_DOS_HEADER));
//判断头抬升
for(int i = 0;i<pPE_header->NumberOfSections;i++,pSection_header++)
{ }
pLastSection_header = pSection_header; //指向老节表的最后一个节表
pNewSection_header = pSection_header + 1; //指向新节表
PBYTE pTemp = (PBYTE)pNewSection_header;
if ((DWORD)pNewSection_header + IMAGE_SIZEOF_FILE_HEADER * 2 <= (DWORD)pImageBuffer + pOption_header->SizeOfHeaders)
{
for (int y = 0; y < 80; y++, pTemp++)
{
if (*pTemp)
{
printf("空间足够插入新节表,需要头抬升\n");
flag = TRUE;
break;
}
else
{
printf("空间足够插入新节表不需要头抬升\n");
}
}
}
else
{
printf("空间不足以插入新节表,需要头抬升\n");
flag = TRUE;
}
//头抬升操作
if (flag)
{
//memcpy拷贝数据
memcpy(pLastDos, pNT_header, (DWORD)pNewSection_header - (DWORD)pNT_header);
//更新e_lfanew
pDos_header->e_lfanew = sizeof(IMAGE_DOS_HEADER);
//更新头抬升后的信息
pNT_header = (PIMAGE_NT_HEADERS)((DWORD)pDos_header + pDos_header->e_lfanew);
pPE_header = (PIMAGE_FILE_HEADER)((DWORD)pNT_header + 4);
pOption_header = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pPE_header + IMAGE_SIZEOF_FILE_HEADER);
pFirstSection_header = (PIMAGE_SECTION_HEADER)((DWORD)pOption_header + pPE_header->SizeOfOptionalHeader);
pSection_header = (PIMAGE_SECTION_HEADER)((DWORD)pOption_header + pPE_header->SizeOfOptionalHeader);
for (int i = 0; i < pPE_header->NumberOfSections; i++, pSection_header++)
{
}
pLastSection_header = pSection_header;
pNewSection_header = pSection_header + 1;
//给节表后空白区填充0
LPVOID pTemp = (LPVOID)((DWORD)pLastSection_header);
memset(pTemp, 0, IMAGE_SIZEOF_SECTION_HEADER * 3);
}
//修改NumberOfSection
++pPE_header->NumberOfSections;
//修改SizeOfImage
pOption_header->SizeOfImage = (DWORD)pOption_header->SizeOfImage + 0x1000;
//复制.text
memcpy(pLastSection_header,pFirstSection_header,IMAGE_SIZEOF_SECTION_HEADER);
//修改新节表的属性
strcpy((char*)pLastSection_header->Name, (char*)".NewT");
pLastSection_header->Misc.VirtualSize = pOption_header->SectionAlignment;
DWORD RawSize = pSection_header->SizeOfRawData;
for(int i = 1; RawSize % pOption_header->SectionAlignment !=0 ;i++,RawSize++)
{ }
pSection_header--;
pLastSection_header->VirtualAddress = pSection_header->VirtualAddress + RawSize;
pLastSection_header->SizeOfRawData = pOption_header->SectionAlignment;
pLastSection_header->PointerToRawData = pSection_header->PointerToRawData + pSection_header->SizeOfRawData;
printf("新节表添加成功\n");
return pImageBuffer;
}
注意:
- 文件对齐和内存对齐相等的情况下,pNewSectionTable->VirtualAddress修改成上一个节表的VirtualAddress+上一个节表的SizeOfRawData
- 文件对齐和内存对齐不相等的情况下,pNewSectionTable->VirtualAddress应该修改成上一个节表的VirtualAddress+上一个节表的SizeOfRawData去对齐内存
pSectionHeader->SizeOfRawData%pOptionalHeader->SectionAlignment!=0
4.扩大节思路
1、拉伸到内存
2、分配一块新的空间:SizeOfImage + Ex
3、将最后一个节的SizeOfRawData和VirtualSize改成N
SizeOfRawData = VirtualSize = N
N = (SizeOfRawData或者VirtualSize 内存对齐后的值) + Ex
4、修改SizeOfImage大小
SizeOfImage = SizeOfImage + Ex