会不会有极端情况,DOS stub 的空间也不足,NT头和节表不能抬升,节表和第一节之间也没有空隙插入新节,这种情况如何加入自己的代码呢?
就需要到合并节,将全部的节(一般文件都有两个以上的节)合并成一个节,那就可以有空隙增加自己的节表了。这里不搞那么复杂了,只做合并节,将全部节合并成一个后文件仍然可用即可。
合并节:
1、拉伸到内存 (这里是真的拉伸到一块内存空间)
2、将第一个节的内存大小、文件大小改成一样
VirtualSize = SizeOfRawData = 整个文件拉伸后的大小 - 第一节VirtualAddress
即相当于在拉伸后的文件中 从第一个节表开始到 最后 都作为第一个节的内存长度与文件长度
3、将第一个节的属性改为包含所有节的属性 (所有节的Characteristics 全部按位或在一起)
4、修改节的数量为1
5、其他节表的空间全部置0
最后将这个拉伸的空间直接拷贝到文件
至于为什么要把拉伸后的空间作为文件空间存储呢,因为操作系统只会按节来拉伸文件,节内部的地址怎么样是管不到的,而运行起来地址都是按内存偏移来寻址的。
比如第二节在内存中就应该放到2000偏移的位置,但它在未拉伸前只在600偏移。不拉伸就合并为一个节,那其他节在内存中起始位置就会丢失。操作系统不知道 600偏移的那段数据需要映射到2000偏移才能正常工作,原代码中就是找2000偏移的位置,而找没有拉伸的2000偏移肯定是错的了。并且我们不可能溯源整个文件的代码逐个改这些相对地址的引用。所以内存中拉伸后是什么样,存到一个节中就应该是什么样。这样合并节文件,运行后,找原本2000的相对偏移,仍然是对的。
上代码附详细注释:这次不涉及ShellCode添加了,相对简单
用到的所有自定义函数均可在前面的章节找到
#include "windows.h"
#include "stdio.h"
VOID h3202()
{
char FilePath[] = "CrackHead.exe"; //CRACKME.EXE CrackHead.exe
char CopyFilePath[] = "CrackHeadcopy.exe"; //CRACKMEcopy.EXE CrackHeadcopy.exe
LPVOID pFileBuffer = NULL; //会被函数改变的 函数输出之一
LPVOID* ppFileBuffer = &pFileBuffer; //传进函数的形参
LPVOID pImageBuffer = NULL;; //会被函数改变的 函数输出之一
LPVOID* ppImageBuffer = &pImageBuffer; //传进函数的形参
int SizeOfFileBuffer;
int SizeOfImageBuffer;
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;
PIMAGE_SECTION_HEADER pFirstSectionHeader = NULL;
PIMAGE_SECTION_HEADER pEndOfSectionHeader = NULL;
DWORD CallX = NULL; //即E8后跟的4字节
DWORD JmpX = NULL; //即E9后跟的4字节
//pFileBuffer即指向已装载到内存中的exe首部
if (!ReadPEFile(FilePath, ppFileBuffer))
{
printf("文件读取失败\n");
return;
}
SizeOfImageBuffer = CopyFileBufferToImageBuffer(pFileBuffer, ppImageBuffer);
//VerifyFileBufferAndImageBuffer(pFileBuffer, pImageBuffer);
//Dos头
pDosHeader = (PIMAGE_DOS_HEADER)pImageBuffer; // 强转 DOS_HEADER 结构体指针
//NT头
pNTHeader = (PIMAGE_NT_HEADERS)((DWORD)pImageBuffer + 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);
pFirstSectionHeader = pSectionHeader;
//修改首个节表长度信息
//printf("SizeOfImageBuffer:%x\nVirtualAddress:%x\n", SizeOfImageBuffer, pFirstSectionHeader->VirtualAddress);
pFirstSectionHeader->SizeOfRawData = Align(SizeOfImageBuffer - pFirstSectionHeader->VirtualAddress,pOptionalHeader->FileAlignment);
//printf("SizeOfRawData:%x\n", pFirstSectionHeader->SizeOfRawData);
pFirstSectionHeader->Misc.VirtualSize = pFirstSectionHeader->SizeOfRawData;
//printf("VirtualSize:%x\n", pFirstSectionHeader->Misc.VirtualSize);
//修改首节表Characteristics
DWORD Characteristics = pSectionHeader->Characteristics;
pSectionHeader++;
for (int i = 2; i <= pFileHeader->NumberOfSections; i++, pSectionHeader++) //注意从第二个开始
{
Characteristics = Characteristics | pSectionHeader->Characteristics;
memset(pSectionHeader, 0, IMAGE_SIZEOF_SECTION_HEADER); //抹去其他节表信息
}
//出循环后pSectionHeader指向节表末尾
pFirstSectionHeader->Characteristics = Characteristics;
pEndOfSectionHeader = pSectionHeader;
printf("Characteristics:%x\n", pFirstSectionHeader->Characteristics);
//修改节数量
pFileHeader->NumberOfSections = 1;
//拷贝到文件
free(pFileBuffer);
SizeOfFileBuffer = CopyImageBufferToFileBuffer(pImageBuffer, ppFileBuffer);
MemeryToFile(pFileBuffer, SizeOfFileBuffer, CopyFilePath);
}
运行程序打印内容如下:
SizeOfImage:5000
SizeOfHeaders:400
拷贝完成
Characteristics:e0000060
大小:4400
SizeOfFile:4400
SizeOfHeaders:400
1次拷贝完成,大小:4400
success
合并节前原文件:
合并节后:
合并节的文件因为把拉伸后的空间作为整个节存储,所以变大了很多,6kb变成17kb