在組合語言中,有時需要在執行期動態取得當前程式碼的位址 (如位置無關程式碼、Shellcode、動態解密)。x86 沒有直接讀取 EIP 的指令,但巧用 CALL 將返回位址壓入堆疊,再 POP 取出,即可輕鬆獲得。
call @f ; 呼叫下一個標號
@@: ; 匿名標號 (MASM/TASM/JWasm 支援)
pop ebx ; EBX = @@ 的位址 (即 CALL 後的指令位址)
CALL rel32 執行時:將下一條指令的線性位址 (EIP) 壓入堆疊,然後跳轉。@@: 正是 CALL 後的下一條指令位置。POP EBX 彈出堆疊頂部的該位址 → EBX 現在持有 當前程式碼的執行位址。call get_addr
get_addr:
pop ebx
; EBX = get_addr 位址
; 假設資料在程式碼後 100h 位元組
mov esi, [ebx + 100h] ; ESI 指向資料
start:
call $+5 ; 呼叫下一條指令 (相對位移 0)
pop ebp ; EBP = 基址
; 後續所有記憶體存取以 EBP 為基準
mov eax, [ebp + offset MyData - offset start]
call [ebp + offset MyFunc - offset start]
decode_loop:
call get_key
get_key:
pop esi ; ESI = 密鑰位址
lodsb ; AL = 密鑰位元組
xor [edi], al ; 解密目的緩衝區
inc edi
loop decode_loop
| 組譯器 | 匿名標號 | 具名標號 |
|---|---|---|
| MASM / JWasm / UAsm | @@: | MyLabel: |
| TASM | @@: | MyLabel: |
| NASM / YASM | $: 或 $$: | MyLabel: |
| GAS (AT&T) | 1f: (前向) / 1b: (後向) | MyLabel: |
CALL/POP 不改變堆疊深度,但若在已有參數的堆疊框架中使用,需注意 EBP 相對位址偏移。CALL 壓入 RIP,POP RAX 取得。VirtualProtect 變更頁面屬性。CALL,建議標註 NOP 或關閉該區塊最佳化。CALL @F / POP REG 是組合語言取得執行期位址的黃金標準技巧,簡潔、高效、跨平台。掌握此技巧,即可編寫位置無關程式碼、動態解密器、Shellcode 等進階底層程式,是逆向工程、殼開發、嵌入式韌體開發的必備內功。