可以向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;
}