可以向pe程序的空白区添加代码;
但是,如果想要添加一段复杂功能的代码,空白区可能空间不够;
一种解决办法是新增一个节,把自己的代码加到这个节中;
1.添加节需要做的事情
添加节需要做两件事:
1】判断是否有足够的空间
2】修改的数据
pe文件的结构:

可以看到:
dos头紧接着一堆垃圾数据;
后面是pe标记、pe头、可选pe头;
然后一堆节表用来描述各个节的信息;
后面是一堆节;
其中SizeOfHeader最好不要随便变动,因为代价太大;
例如:如果该值增大10个字节,后面的节必须向后偏移10个字节,然后一大堆数据要变;
关于节表:
一个节表有40个字节;
一个完整的结构,在所有节表后面必须跟一个全0的数;
也就是必须要有一个40个字节的0;
如果想在后面新增一个节表,需要保证在新增节表后面有40个字节的空间用来放0;
也就是说要计算剩下的空间是否够80个(新节表40+补0的40个);
1)判断空间是否足够
SizeOfHeader - (DOS头 + 垃圾数据 + PE标记 + 标准PE头 + 可选PE头 + 已存在节表)>= 2个节表的大小
2)需要修改的数据
1】 添加一个新的节(可以copy一份)
2】 在新增节后面 填充一个节大小的000
3】 修改PE头中节的数量
->pe头里面的NumberOfSections
4】修改sizeOfImage的大小
->在可选pe头里面;表示内存镜像中pe文件的大小,因为新增了一个节所以要相应的增加,注意对齐
5】 在原有数据的最后,新增一个节的数据(内存对齐的整数倍).
6】修正新增节表的属性
->内存对齐前的大小misc
->内存镜像中的偏移VirtualAddress
->文件对齐后的尺寸SizeOfRawData
->节从哪里开始PointerToRawData
2.手动添加一个节
用二进制工具如winhex打开pe文件:

可以看到:从278-1000之间有足够的空间来放一个新节表和全0的40个字节;
为了方便,可以将第一个节的数据复制一份,放到最后一个节的后面;
因为第一个节一般是代码节,可以直接运行;
后面再修改数据;

修改节的数量以及内存镜像的总大小:
节的数量+1;在pe头中;这里有4个节改为5;
修改SizeOfImage;
如下图,原来的值为7000;考虑到内存对齐,将新节设为1000;要改为8000;

修改:

然后修改新加的节表,可以参照最后一个节表的数据来改

节名随便改,只要不重名即可;
VirtualSize ->对齐前的长度,设为1000够用了;
VirtualAddress ->内存中的偏移,相当于在最后加了一个节;因此偏移量为内存镜像的大小,也就是SizeOfImage,即7000;
SizeOfRawData ->文件中对齐后的大小,需要考虑文件对齐,文件对齐为1000,这里要为1000的倍数,就给1000;
PointToRawData ->文件中的偏移,这个节紧跟最后一个节,这个值为上一个节的偏移6000+上一个节的文件镜像大小1000=7000;
其它的值不重要就先不管,最后一个标志块和是复制的代码节,和那个一样即可,不用管;

然后在文件最后面添加一段空间作为新节的数据;
从7000~8000;先不插代码,全补0即可;

添加新节完成;接下来只要在新节中添加代码即可;
3.极端情况
1)可能最后一个节表后面不是0,而是编译器插入的代码;
因为无法确定这些代码是否有用,不好直接修改;
但没地方添加新节表了;
在dos头和nt头之间有一段无用区域,用来写描述信息,该地方修改不影响程序;
可以把nt头和节表整体向上提,足够80个字节的空间即可;
然后修改dos头存有nt头地址的字段e_flanew即可;
2)nt头和dos头之间的空间也不够用时
可以考虑扩充最后一个节;
因为扩充中间的节会造成后面的节整体偏移而定位不准确;
但扩充最后一个节不会有这个问题;
4.代码实现新增节
#include "stdafx.h"
#include "petool.h"
#include "string.h"
#define SHELLCODELEN 18
#define SRC "C:\\Users\\Administrator\\Desktop\\TraceMe.exe"
#define DEST "C:\\Users\\Administrator\\Desktop\\copy1.exe"
//新增一个节并插入代码
void newSec(){
//定义头结构指针
PIMAGE_DOS_HEADER dosHeader = NULL; //dos头指针
PIMAGE_FILE_HEADER peHeader = NULL; //pe头指针
PIMAGE_OPTIONAL_HEADER32 opHeader = NULL; //可选pe头指针
PIMAGE_SECTION_HEADER seHeader = NULL; //节表指针
PIMAGE_SECTION_HEADER newSeHeader = NULL; //新节表指针
LPVOID pFileBuffer = NULL;
//1.加载文件到内存
DWORD size = ReadPEFile(SRC, &pFileBuffer);
if(!pFileBuffer){
printf("读取文件失败\n");
return;
}
//2.初始化头指针
dosHeader = (PIMAGE_DOS_HEADER) pFileBuffer;
peHeader = (PIMAGE_FILE_HEADER)((DWORD)pFileBuffer + dosHeader->e_lfanew + 4);
opHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)peHeader + IMAGE_SIZEOF_FILE_HEADER);
seHeader = (PIMAGE_SECTION_HEADER)((DWORD)opHeader + peHeader->SizeOfOptionalHeader);
//3.判断空间是否够插入节表
newSeHeader = seHeader + peHeader->NumberOfSections;
if((opHeader->SizeOfHeaders - ((DWORD)newSeHeader - (DWORD)pFileBuffer)) < 80){
printf("空间不够插入节表\n");
free(pFileBuffer);
return;
}
//4.设置节表
strcpy((char*)(newSeHeader->Name), ".NewSec"); //节名
newSeHeader->Misc.VirtualSize = 0x1000; //对齐前大小设为1000
newSeHeader->VirtualAddress = 0x7000; //节的内存偏移为sizeofimage
newSeHeader->SizeOfRawData = 0x1000; //节的文件对齐大小设为1000
newSeHeader->PointerToRawData = 0x7000; //节的偏移紧接着上一个节的尾部即可;
newSeHeader->Characteristics = seHeader->Characteristics; //属性和代码节一样
//5.插入一个全0的节表
LPVOID seEnd = (LPVOID) (newSeHeader + 1);
memset(seEnd, 0, 40);
//6.修头信息
peHeader->NumberOfSections = peHeader->NumberOfSections + 1;
opHeader->SizeOfImage = opHeader->SizeOfImage + 0x1000;
//7.写出到新文件
FILE* copy = fopen(DEST, "a+b");
if(!copy){
printf("打开写出文件失败\n");
free(pFileBuffer);
return;
}
size_t m = fwrite(pFileBuffer, size, 1, copy);
if(!m){
printf("写出文件第一部分失败\n");
fclose(copy);
free(pFileBuffer);
return;
}
//8.追加写出新节表
LPVOID newBuf = malloc(0x1000);
if(!newBuf){
printf("给新节申请内存失败\n");
free(pFileBuffer);
return;
}
memset(newBuf, 0, 0x1000);
size_t n = fwrite(newBuf, 0x1000, 1, copy);
if(!n){
printf("写出第二部分失败\n");
free(pFileBuffer);
free(newBuf);
newBuf = NULL;
fclose(copy);
return;
}
//9.释放内存返回
fclose(copy);
free(pFileBuffer);
free(newBuf);
printf("存盘成功\n");
return;
}
int main(int argc, char* argv[])
{
newSec();
getchar();
return 0;
}