.386
.model flat, stdcall
option casemap:none
include windows.inc
include kernel32.inc
include user32.inc
includelib kernel32.lib
includelib user32.lib
.data
szCaption db "命令行參數",0
.code
start:
invoke GetCommandLine
invoke MessageBox, NULL, eax, addr szCaption, MB_OK
invoke ExitProcess, NULL
end start
Disassembly of File: 6.exe
Code Offset = 00000400, Code Size = 00000200
Data Offset = 00000800, Data Size = 00000200
Number of Objects = 0003 (dec), Imagebase = 00400000h
Object01: .text RVA: 00001000 Offset: 00000400 Size: 00000200 Flags: 60000020
Object02: .rdata RVA: 00002000 Offset: 00000600 Size: 00000200 Flags: 40000040
Object03: .data RVA: 00003000 Offset: 00000800 Size: 00000200 Flags: C0000040
+++++++++++++++++++ IMPORTED FUNCTIONS ++++++++++++++++++
Number of Imported Modules = 2 (decimal)
Import Module 001: KERNEL32.dll
Import Module 002: USER32.dll
Import Module 001: KERNEL32.dll
Addr:00002072 hint(00B6) Name: GetCommandLineA
Addr:00002064 hint(0075) Name: ExitProcess
Import Module 002: USER32.dll
Addr:00002092 hint(01BB) Name: MessageBoxA
+++++++++++++++++++ ASSEMBLY CODE LISTING ++++++++++++++++++
//********************** Start of Code in Object .text **************
Program Entry Point = 00401000 (6.exe File Offset:00001600)
* Reference To: KERNEL32.GetCommandLineA, Ord:00B6h
//******************** Program Entry Point ********
:00401000 E81D000000 Call 00401022
:00401005 6A00 push 00000000
:00401007 680E304000 push 0040300E ; szCaption 位址
:0040100C 50 push eax ; GetCommandLine 返回值
:0040100D 6A00 push 00000000
* Reference To: USER32.MessageBoxA, Ord:01BBh
:0040100F E814000000 Call 00401028
:00401014 6A00 push 00000000
* Reference To: KERNEL32.ExitProcess, Ord:0075h
:00401016 E801000000 Call 0040101C
:0040101B CC int 3
* Referenced by a CALL at Address: |:00401016
* Reference To: KERNEL32.ExitProcess, Ord:0075h
:0040101C FF2504204000 Jmp dword ptr [00402004] ; **IAT Thunk: ExitProcess**
* Referenced by a CALL at Address: |:00401000
* Reference To: KERNEL32.GetCommandLineA, Ord:00B6h
:00401022 FF2500204000 Jmp dword ptr [00402000] ; **IAT Thunk: GetCommandLineA**
* Reference To: USER32.MessageBoxA, Ord:01BBh
:00401028 FF250C204000 Jmp dword ptr [0040200C] ; **IAT Thunk: MessageBoxA**
觀察入口點:
:00401000 E81D000000 Call 00401022 ; Call 近似相對跳轉到 Thunk
...
:00401022 FF2500204000 Jmp dword ptr [00402000] ; 間接跳轉 IAT
原因:載入器重定位效率 (Relocation Efficiency)。
| 方式 | 機器碼範例 | 載入器修補點數 | 代碼大小 |
|---|---|---|---|
| 直接 CALL [IAT] | FF 15 00 20 40 00 (6 bytes) | 每個呼叫點皆需修補 (若呼叫 100 次,修補 100 處) | 每次 6 bytes |
| CALL Thunk -> JMP [IAT] | E8 xx xx xx xx (5 bytes) + Thunk FF 25 yy yy yy yy (6 bytes) | 僅修補 Thunk 處的 IAT 位址 (1 處) | 共用 Thunk 時極省空間 |
ExitProcess 常只有一個 Thunk)。文中提到使用 DEBUG (或 SoftICE/TRW/IDA/Hiew) 載入進程除錯:
debug /Load Process
; 輸入命令列參數 (可選) -> 進入除錯視窗
; 單步追蹤 (F8), 觀察呼叫流程
在 DOS 年代 DEBUG 是神器;Win32 時代建議使用 x64dbg / x32dbg (開源、現代 UI、支援符號)、WinDbg (微軟官方、核心除錯)、IDA Pro (靜態分析之王)。
[00402000] 等 IAT 項目指向自訂函式,實現 API 截獲 (用戶模式 Hook)。CALL/JMP [reg/mem]) 前插入檢查,驗證目標位址是否在合法靶點表中,防禦 ROP/JOP 攻擊。Thunk 位址需在 CFG 表中。「為什麼
invoke GetCommandLine不直接編譯成Call dword ptr [00402000]?」
答案:**為了讓載入器只修補一個 IAT 表項,而非成百上千個呼叫點,實現代碼區段唯讀共享與 ASLR 友善。** 這是 PE 格式設計的核心智慧之一。