天天看点

滴水逆向三期实践4:节空白区注入代码

还记得节与节之间无论是文件中还是内存中都会存在一定的空白区(空隙)。那么可不可以往这里面注入代码呢?

节表 VirtualSize(Misc)是指节的真实大小,暂且相信这个值的真实性。(后面有更可靠的方法)

那么如果注入代码(硬编码)长度 加上 VirtualSize 仍然没有超过(覆盖)下一个节在文件中的位置 ,

即 ( VirtualSize + 即注入代码长度 ) < SizeOfRawData

那么就可以往PE文件中注入代码了。在此之前,还需要知道一点其他的知识

为了效果显著且操作简单,就在程序运行前调用一个MessageBoxA弹窗作为注入的代码。首先在调用函数前,需要push参数,MessageBoxA有四个参数,可以全部写0. push完四个后就可以call 调用函数了,那怎么在PE文件一打开就运行我们注入的代码呢?

在可选头有个AddressOfEntryPoint字段,就是程序开始的入口点,把这个值改了指向我们的注入代码。那么一打开程序就会执行我们的代码了,如果还要保证程序正常运行,执行完我们的代码还得指回去,所以call之后还得 jmp 回原来的AddressOfEntryPoint地址

并且注意上述操作涉及的地址全都是RVA,而且由于我们是直接对文件操作(不用FileBuffer2ImageBuffer 再改再转回来了,那样太麻烦)我们直接用上次写好的FOA2RVA的直接在未拉伸的文件中转换即可。

至于push 和 call  jmp 的硬编码,可以直接查询x86下他们的硬编码或者拖个PE文件进IDA或者OD看。这里直接给出push四个0参数的硬编码 0x6A,0x00,0x6A,0x00,0x6A,0x00,0x6A,0x00,  call的硬编码0xE8,后跟四字节,需要跳转地址进行一定计算,jmp为0xE9,后四字节也一样的方式。具体为:

call X  

X = 要跳转的地址 - call指令下一条指令的地址

由于call指令占5字节,所以上式化为 X = 要跳转的地址 - ( E8所在地址 + 5 ) 

E8为call指令的第一个字节,固定值

那么要跳转的地址即MessageboxA地址,但这个地址有点麻烦,目前先体验一下简单的做法,这里可能需要点逆向基础。打开OD拖入任一exe,Ctrl+G搜索MessageboxA,记录下MessageboxA起始地址,可以写入代码中,但下次开机需要重新这个操作,当然这只是个实例,不一定添加MessageboxA的调用。具体细节在代码具体应用中有更好理解,有详细注释。

jmp同理。 那么附上上述思路实现的简单代码(找个个节表空隙够的exe,这里shellcode弹窗很短一般都够的,这个代码只找了第一个节,可以写个循环一个节不够判断下一个节之间的空隙)

(MemeryToFile 函数已在上一章中贴出)

#include "windows.h"
#include "stdio.h"

#define MESSAGEBOXADDR 0x76AF39A0   //这个值需要将任一exe文件拖入OD打开,搜索 MessageBoxA 记录它的地址到这里(每次开机都不同)

unsigned char ShellCode317[] =
{
	0x6A,0x00,0x6A,0x00,0x6A,0x00,0x6A,0x00,  //这行是push 四个0 作为 MessageBox 的参数
	0xE8,0x00,0x00,0x00,0x00,
	0xE9,0x00,0x00,0x00,0x00
};

void h317()
{
	char FilePath[] = "CrackHead.exe";
	char CopyFilePath[] = "CrackHeadcopy.exe";
	LPVOID pFileBuffer = NULL;				//会被函数改变的 函数输出之一
	LPVOID* ppFileBuffer = &pFileBuffer;	//传进函数的形参
	int SizeOfFileBuffer;
	PIMAGE_DOS_HEADER pDosHeader = NULL;
	PIMAGE_NT_HEADERS pNTHeader = NULL;
	PIMAGE_FILE_HEADER pFileHeader = NULL;
	PIMAGE_OPTIONAL_HEADER32 pOptionalHeader = NULL;
	PIMAGE_SECTION_HEADER pSectionHeader = NULL;
	DWORD CallX = NULL;	//即E8后跟的4字节
	DWORD JmpX = NULL;	//即E9后跟的4字节

	SizeOfFileBuffer = ReadPEFile(FilePath, ppFileBuffer);	//pFileBuffer即指向已装载到内存中的exe首部
										/*pFileBuffer = *ppFileBuffer;*/
	if (!SizeOfFileBuffer)
	{
		printf("文件读取失败\n");
		return;
	}

	//Dos头
	pDosHeader = (PIMAGE_DOS_HEADER)pFileBuffer;	// 强转 DOS_HEADER 结构体指针
	//NT头
	pNTHeader = (PIMAGE_NT_HEADERS)((DWORD)pFileBuffer + pDosHeader->e_lfanew);
	//PE头
	pFileHeader = (PIMAGE_FILE_HEADER)(((DWORD)pNTHeader) + 4);	//NT头地址 + 4 为 FileHeader 首址
	//可选PE头	
	pOptionalHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pFileHeader + IMAGE_SIZEOF_FILE_HEADER);//SIZEOF_FILE_HEADER为固定值且不存在于PE文件字段中
	//首个节表
	pSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)pOptionalHeader + pFileHeader->SizeOfOptionalHeader);
	if ( pSectionHeader->Misc.VirtualSize + sizeof(ShellCode317) > pSectionHeader->SizeOfRawData)
	{
		printf("空间不足");
		free(pFileBuffer);
		return;
	}
	//X即E8后的数 = 要跳转的地址 - (E8所在地址 + 5)            (E8 所在地址+5 即 call指令的下一条指令的地址)
	//那么要跳转的地址即messageboxA地址。E8所在地址即 ImageBase内存运行基址 + VirtualAddress节所在偏移 + VirtualSize 节真正长度即节结束的偏移 再加上8才到E8
	CallX = MESSAGEBOXADDR - (pOptionalHeader->ImageBase + pSectionHeader->VirtualAddress + pSectionHeader->Misc.VirtualSize +8 +5);
	
	//jump 要跳转的地址即OEP程序入口点, X = 程序入口点 - (E9所在地址 + 5)
	//这里程序入口点即ImageBase基址 + AddressOfEntryPoint         E9所在地址计算同上
	JmpX = pOptionalHeader->ImageBase + pOptionalHeader->AddressOfEntryPoint - (pOptionalHeader->ImageBase + pSectionHeader->VirtualAddress + pSectionHeader->Misc.VirtualSize + 13 + 5);
	
	//将上述计算后的值放入ShellCode317
	*(PDWORD)(ShellCode317 + 9) = CallX;
	*(PDWORD)(ShellCode317 + 14) = JmpX;
	for(int i = 0;i<sizeof(ShellCode317);i++)
	{
		printf("%x ", ShellCode317[i]);
	}
	printf("\n");
	
	//拷贝ShellCode317到内存中
	memcpy((void*)((DWORD)pFileBuffer + pSectionHeader->PointerToRawData + pSectionHeader->Misc.VirtualSize), ShellCode317,sizeof(ShellCode317));
	
	//修改OEP的值
	pOptionalHeader->AddressOfEntryPoint = pSectionHeader->VirtualAddress + pSectionHeader->Misc.VirtualSize;
	MemeryToFile(pFileBuffer, SizeOfFileBuffer, CopyFilePath);
}
           

程序运行输出:

6a 0 6a 0 6a 0 6a 0 e8 b9 24 6f 76 e9 14 fb ff ff

success

打开生成的另一份exe 

滴水逆向三期实践4:节空白区注入代码

点击确定后才是原来的程序

继续阅读