首页 > 其他分享 >滴水逆向笔记系列-PE总结2-25.FileBuffer-ImageBuffer-26.代码节空白区添加代码-27.新增节_扩大节_添加代码

滴水逆向笔记系列-PE总结2-25.FileBuffer-ImageBuffer-26.代码节空白区添加代码-27.新增节_扩大节_添加代码

时间:2024-03-16 10:44:36浏览次数:33  
标签:26 HEADER 代码 pImageBuffer header 添加 pSection DWORD PIMAGE

第二十五课 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


作业

image.png

//函数声明						
//**************************************************************************						
//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.添加代码

效果

  • 我们需要将一段执行MessageBox函数的硬编码添加到pe文件中,添加后我们双击文件时先弹窗,在继续正常运行

修改思路

  • 把PE文件的入口(OEP)改成我们添加的代码的位置,执行完我们的代码后再jmp回原来文件的入口地址

具体操作思路

  1. 先确定我们需要插入的代码,先push函数的四个参数,在call MessageBoxa函数的地址,最后jmp回原来的程序入口,大概6A 00 6A 00 6A 00 6A 00 E8 函数地址 E9 程序入口地址所以我们需要找出两个东西,MessageBoxa函数地址和程序入口
  2. MessageBoxA函数地址:需要打开OD文件,命令行输入:bp MessageBoxA,相当于打个断点,我们在上方工具栏中的B按钮查看断点,显示的断点所在地址就是MessageBoxA函数的起始地址
  3. 程序入口地址:通过可选PE头的AddressOfEntryPoint字段可以获取程序入口地址偏移量,+加上ImageBase字段就可以获取内存中的程序入口地址
  4. 最后找一个节最后的空白区,只要能放下18个字节的硬编码就可以

注:
E8后面地址计算:E8下一条指令地址在需要从FileBuffer转换成ImageBuffer,用现在文件偏移-节初始地址=文件中对于节的偏移是775D,内存中节的初始位置(imagebase1000000+virtualAddress1000)+775D=内存中E8的下一条指令内存地址100875D真正要跳转的地址76120F40 - E8这条指令的下一行地址=7511 87E3
E9后面地址转换:文件中指令地址对于节的偏移77621001000+7762=E9下一条指令内存地址1008762
真正要跳转的地址100739D - E9这条指令的下一行地址=FFFFEC3B
image.png

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.新增节的思路

  • 复制一个已存在的节表(最好节的属性一样等一下就不用改),复制到紧挨着最后一个节表的末尾,然后修改节表数据,最后再补40个字节的0即可

修改的数据

  1. 添加一个新的节(可以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.扩大节思路

image.png
1、拉伸到内存
2、分配一块新的空间:SizeOfImage + Ex
3、将最后一个节的SizeOfRawData和VirtualSize改成N
SizeOfRawData = VirtualSize = N
N = (SizeOfRawData或者VirtualSize 内存对齐后的值) + Ex
4、修改SizeOfImage大小
SizeOfImage = SizeOfImage + Ex

标签:26,HEADER,代码,pImageBuffer,header,添加,pSection,DWORD,PIMAGE
From: https://www.cnblogs.com/xiaoxin07/p/18076794

相关文章

  • 访问Webapp目录下面的html文件变为代码
    一、问题由来一位朋友最近在学习JavaWeb开发,使用Servlet做练习的时候,突然出现一个问题。他去访问自己创建的html文件时,发现返回的数据是html代码,而不是解析后的页面。很是疑惑,自己尝试着解决这个问题,很久都没有解决问题,然后就找到我。问题复现情况如下,正常来说,访问html页......
  • html中如何让网页禁用右键禁止查看源代码
    在网页中,辛辛苦苦写的文章,被别人复制粘贴给盗用去另很多站长感到非常无奈,通常大家复制都会使用选取右键复制,或CTRL+C等方式,下面介绍几种禁止鼠标右键代码,可减少网页上文章被抄袭的几率,当然对高手来说,破解也很简单,不管怎么样,我们还是一起试试吧。使用方法:以下代码加入到<scrip......
  • 视频直播系统源码,异步处理实现代码分析
    视频直播系统源码,异步处理实现代码分析@OverrideprotectedvoiddoGet(HttpServletRequestrequest,HttpServletResponseresponse)throwsServletException,IOException{System.out.println("doget");method3(request,response);}/***使用asyncConte......
  • 代码随想录 第21天 | ● 530.二叉搜索树的最小绝对差 ● 501.二叉搜索树中的众数 ●
    leetcode:530.二叉搜索树的最小绝对差-力扣(LeetCode)思路:判断最小绝对差,肯定用中序遍历,双指针一前一后依次判断。classSolution{intresult=Integer.MAX_VALUE;TreeNodepre=null;publicintgetMinimumDifference(TreeNoderoot){if(root==......
  • 【机器学习】机器学习创建算法第2篇:K-近邻算法【附代码文档】
    机器学习(算法篇)完整教程(附代码资料)主要内容讲述:机器学习算法课程定位、目标,K-近邻算法,1.1K-近邻算法简介,1.2k近邻算法api初步使用定位,目标,学习目标,1什么是K-近邻算法,1Scikit-learn工具介绍,2K-近邻算法API,3案例,4小结。K-近邻算法,1.3距离度量学习目标,1欧式距离,2......
  • 基于YOLOv8/YOLOv7/YOLOv6/YOLOv5的花卉检测与识别系统(附完整资源+PySide6界面+训练代
    摘要:本篇博客介绍了一种基于深度学习的花卉检测与识别系统,并详细展示了其实现代码。系统采取先进的YOLOv8算法,并与YOLOv7、YOLOv6、YOLOv5等早期版本进行了比较,展示了其在图像、视频、实时视频流及批量文件中识别花卉的高准确度。文章深入阐释了YOLOv8的工作机制,并配备了相应的Pyt......
  • JS代码——统计字符串中每个字符出现的次数
    要求:输入一个字符串,输出每个字符各自出现的次数一、代码区域二、效果截图注: 博主每天记录自己所学,如有写的不好之处,希望您能不吝赐教,给我一些关于这个项目的意见和建议。各位的宝贵意见将对我产生深远的影响,我将认真倾听并尽力改进。谢谢各位~~......
  • 基于YOLOv8/YOLOv7/YOLOv6/YOLOv5的火焰检测系统(Python+PySide6界面+训练代码)
    摘要:本研究详述了一种采用深度学习技术的火焰检测系统,该系统集成了最新的YOLOv8算法,并与YOLOv7、YOLOv6、YOLOv5等早期算法进行了性能评估对比。该系统能够在各种媒介——包括图像、视频文件、实时视频流及批量文件中——准确地识别火焰目标或着火点等。文章深入阐述了YOLOv8算法......
  • 基于YOLOv8/YOLOv7/YOLOv6/YOLOv5的番茄成熟度检测系统(Python+PySide6界面+训练代码)
    摘要:开发番茄成熟度检测系统对于提高农业产量和食品加工效率具有重大意义。本篇博客详细介绍了如何利用深度学习构建一个番茄成熟度检测系统,并提供了完整的实现代码。该系统基于强大的YOLOv8算法,并结合了YOLOv7、YOLOv6、YOLOv5的对比,展示了不同模型间的性能指标如mAP、F1Score等......
  • 基于YOLOv8/YOLOv7/YOLOv6/YOLOv5的癌症图像检测系统(深度学习模型+UI界面代码+训练数
    摘要:本文介绍了一种基于深度学习的癌症图像检测系统的代码,采用最先进的YOLOv8算法并对比YOLOv7、YOLOv6、YOLOv5等算法的结果,能够准确识别图像、视频、实时视频流以及批量文件中的摘要:本篇博客深入介绍了如何借助深度学习技术开发癌症图像检测系统,以提高医疗诊断的精度和速度。系......