還記得節與節之間無論是檔案中還是記憶體中都會存在一定的空白區(空隙)。那麼可不可以往這裡面注入代碼呢?
節表 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
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsIyZuBnLyUjM2QDN0EjMyITOwEjMwIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
點選确定後才是原來的程式