首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 更多程式語言 >> 組合語言

組合語言位置無關程式設計:利用 CALL 取得執行期位址修正值

     編輯:組合語言

前言

文章探討組合語言中如何實現真正的位置無關程式碼 (Position Independent Code, PIC)。一般資料存取使用標號會被編譯為絕對位址,導致程式碼無法任意搬移。作者提出利用 CALL 指令將 EIP 壓入堆疊,再 POP 取得執行期位址,與編譯期位址相減得修正值,後續存取資料時加上此修正值,達到位置無關效果。

問題背景

_asm{
 DATA_LABEL:
 _emit 0x87; _emit 0xa0; _emit 0x49; _emit 0x90
 ...
 mov ebx, dword ptr [DATA_LABEL]  ; 編譯期固定為絕對位址
}

若程式碼被載入不同基址,DATA_LABEL 絕對位址失效。高級語言函數雖也有固定位址,但由載入器/動態連結器處理重定位;組合語言若想做到像 Shellcode 一樣「複製即執行」,需自行解決。

核心技巧:CALL-POP 取得執行期位址

_asm{
 call FUNC_START
FUNC_START:
 pop ebx
 sub ebx, offset FUNC_START
 mov [ebp-xx], ebx       ; 保存修正值 Delta
 ...
 DATA_LABEL:
 _emit ...
 ...
 mov eax, [ebp-xx]       ; 取得 Delta
 mov ebx, dword ptr [DATA_LABEL+eax]  ; 相對定址
}

原理詳細解析

  1. CALL FUNC_START:將**下一條指令位址** (即 FUNC_START: 標號處 POP EBX 的位址) 壓入堆疊。
  2. POP EBX:彈出該位址到 EBX,此刻 EBX = 執行期 FUNC_START 位址。
  3. SUB EBX, OFFSET FUNC_START:OFFSET FUNC_START 為編譯期位址 (組譯器常數)。相減得 Delta = 執行期基址 - 編譯期基址。
  4. 保存 Delta 到區域變數 ([EBP-xx]) 供後續使用。
  5. 存取資料時:[DATA_LABEL + Delta] = 執行期真實位址。

關鍵優勢與應用場景

  • 無需重定位表:純指令流實現位置無關,適合 Shellcode、引導程式碼、病毒/免殺模組、嵌入式固件模組。
  • 不依賴載入器:不需 PE/ELF 重定位區段,可直接注入任意記憶體執行。
  • 相容性:x86/x64 皆適用 (x64 用 RIP 相對定址更簡單,但此法仍可工作)。
  • 資料區也可相對:程式碼與資料區同樣偏移,單一 Delta 修正所有標號。

進階變體與注意事項

  • x64 RIP 相對定址:MOV RAX, [RIP+DATA_LABEL] 硬體直接支援,無需 CALL-POP。
  • Delta 計算時機:務必在程式入口點最先執行,後續所有標號存取皆需加 Delta。
  • 呼叫慣例影響:若在函數中使用,需保存/恢復呼叫者保存暫存器 (EBX 為被呼叫者保存,需 PUSH/POP)。
  • 多模組合併:每個模組需獨立計算 Delta,或共享基址參考點。
  • 反組譯/分析對抗:此模式常被視為 Shellcode 特徵,安全產品會重點掃描 CALL $+5 / POP REG 序列。

結語

CALL-POP 技巧是組合語言實現位置無關程式碼的經典核心手法,揭示了 指令指標 (EIP/RIP) 只能透過控制轉移指令間接讀取 這一 x86 架構基本事實。理解此技巧不僅能編寫可任意搬移的組合語言模組,更是分析 Shellcode、惡意程式碼解殼、引導載入器、動態二進制插桩等進階領域的必修基礎。現代編譯器 (GCC/MSVC) 產生 PIC 時亦使用類似原理 (x86 用 CALL/POP 或 GOT,x64 用 RIP-relative),可見其普世價值。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved