最小PE文件
- 用
1或9填充DOS頭、PE頭和節表中無用的部分 - 可以在這些地方見縫插針地填入所需的東西,如程序主要的代碼、Dll名稱和函數名稱等

DosStub可以整段刪掉,如下圖- 暫時先不修改各個RVA,最後刪完再一次過重設所有需要修改的RVA

- 接下來修改可選PE頭的數據目錄表,因為只需要用到數據目錄表第
2項的導入表,因此後面的14項都可以直接刪除 - 並將數據目錄表的項數設為
2

- 節表只需留下
.text節表,.rdata可以直接刪除 - 可以直接刪除的原因是,數據目錄表能直接定位到導入表,因此不需要依靠
.rdata節表也行

- 分析一下
.rdata對應的節區 - 可以先從原始文件中,找到
.rdata對應節區的的起始位置和大小,從而在當前文件中找到.rdata對應節區 - 整個藍色部分就是
.rdata對應的節區,紅框部分是IAT,這部分不重要,暫時以9填充,重要的是綠框部分,這是一個IDT結構的數組,以20個字節的0作為IDT數組的結尾 - IDT結構體最重要的是
OriginalFirstThunk、FirstThunk和Name這3個屬性,後續要手動將這3個屬性指向正確的地址 - 由於只需要
MessageBoxA這個函數,因此綠框部分只需留下28個字節的空間,前20個字節表示一個IDT結構,後8個字節表示IDT數組的結束 - 然後將修改後的紅框、綠框這兩部分移到文件最下方

- 修改後如下圖
- 紅框部分算是一個備用空間,之後可作刪減
- 綠框部分中,上述提到的
3個屬性要在之後手動修改

- 藍框部分是程序的執行代碼 + 一些無用的字符串,可以直接刪
- 代碼部分要自己重構,然後見縫插針插入PE頭中,以節省空間

- 藍框部分是PE頭
- 將其整段剪下,覆蓋到紅框

- 修改後如下圖

- 在PE頭裡插入
user32.dll和MessageBoxA這2個字符串,插入的位置是從填充了9的字段中選中 - 下方保存的
user32.dll和MessageBoxA現在可以刪了

- 藍框是
.text節表後幾個屬性,無用可以直接刪

- 為了重構代碼,要借助OD來看

- 代碼大概可重構成以下匯編結構,只保留了調用MessageBoxA的部分
E8後面跟的地址由這個公式得出:目標地址 - 當前指令地址 - 5
1 | push 0x1040 |
- 在PE頭中合適的位置插入代碼
- 部分指令解析:
68 40 10 00 00:push參數,可與原程序一致EB 0D:EB是jmp指令的其中一個操作碼,操作數0D是一個偏移(當前指令的下一條指令的起始地址 + 偏移 = 要跳轉到的地址),用EB的目的是為了在一片不連續的空間實現連續的代碼執行流,相當於是一個橋樑68 A4 00 40 00: push參數,A4 00 40 00是標題字符串在內存的絕對地址FF 25 E8 00 40 00:FF 25是jmp指令另一個操作碼,E8 00 40 00是內存中的地址,它指向MessageBoxA的真實地址( 如何讓0x4000E8指向MessageBoxA的真實地址?將PE文件IDT表的FirstThunk設為0xE8即可 )

IAT那部分只保留前8個字節就夠用,後12個字節可以刪

- 之前在PE頭中插入的
MessageBoxA字符串的前2個字節9D 01要刪,然後在後方補上2個字節0 - 若不這樣做會導致PE頭中的
SizeOfUninitializedData過大,會使程序崩潰

- 各種RVA、Size的修改,大致有以下這幾項要修改
NumberOfSections:節區數,改為1SizeOfOptionalHeader:可選PE頭的大小,改為70hAddressOfEntryPoint:程序起始位置,改為30hSizeOfHeaders:這個相當於是第一個節區的起始位置,即代碼節的位置,需與AddressOfEntryPoint一致- 導入表起始地址改為
F0h,大小改為28h - INT和IAT都指向
E8h,E8h指向1Ch(1Ch指向PE頭中的MessageBoxA ),後面跟4個字節的0代表結束,文件加載到內存時 (MessageBoxA的真實地址會放到IAT中,即E8h指向的就是MessageBoxA的真實地址 ) - Dll的Name為
0Ch

- 最後,將彈框信息改成自己的姓名和學號,這裡用的是gbk編碼

結果如下:

本部落格所有文章除特別聲明外,均採用 CC BY-NC-SA 4.0 許可協議。轉載請註明來自 NgIokWeng's Blog!
評論
/image3.png)

/image.png)

/image.png)