Black Hat EU 2001 - injectso: Modifying and Spying on Running Processes Under Linux pt2
三句話摘要
Injector 工具如何在 Unix 系統下動態注入共享函式庫到執行中的進程,及其在執行時修複漏洞或進行惡意操作的應用。 Injector 揭示了執行時記憶體操縱的深層威力:一旦進程被附加,再精密的應用層防禦都形同虛設,真正的防線終歸是系統層級的存取控制。 Unix 與 Windows 注入機制差異:Windows 提供現成 API(OpenProcess、LoadLibraryA、CreateRemoteThread 等),但 Unix 缺乏遠端記憶體分配和線程建立的對應物。講者將注入分解為四個概念步驟:(1)用 Ptrace 或 procfs 附加到進程,(2)遍歷動態連結器的連結映射表找到 dlopen 函數的實際位址,(3)以偵錯器身份執行 dlopen 來載入共享庫,(4)觸發庫的初始化函數。
重點整理
重點- 1
Unix 與 Windows 注入機制差異:Windows 提供現成 API(OpenProcess、LoadLibraryA、CreateRemoteThread 等),但 Unix 缺乏遠端記憶體分配和線程建立的對應物。講者將注入分解為四個概念步驟:(1)用 Ptrace 或 procfs 附加到進程,(2)遍歷動態連結器的連結映射表找到 dlopen 函數的實際位址,(3)以偵錯器身份執行 dlopen 來載入共享庫,(4)觸發庫的初始化函數。
- 2
記憶體位址隨機化(ASLR)的克服方案:因為 ASLR 補丁隨機化共享庫位址,不能假設函數在目標進程與自身進程位於相同位址。解決方法是在目標進程中讀取連結映射表、找到 libc 的載入基位址、遍歷其符號表查詢 dlopen 的相對偏移,再計算絕對位址——這個過程模擬了動態連結器本身的行為。
- 3
系統呼叫中斷問題的複雜處理:修改執行流會中斷阻塞中的系統呼叫(如 read、select),導致作業系統寫入 EINTR 等錯誤號到暫存器,進程通常無法處理而崩潰。講者先保存暫存器狀態,檢測進程是否在系統呼叫中,若是則修復第一個暫存器(eax/o1/g1,儲存系統呼叫號碼),並從原系統呼叫指令重新啟動,讓作業系統正確恢復呼叫。
- 4
創意的遠端程式碼執行方法:無法在遠端進程分配可執行記憶體(頁面保護無法修改,ASLR 補丁禁用堆疊執行)。改採「跳板」技巧:將參數複製到堆疊、修改 CPU 暫存器設定參數(遵循呼叫約定)、將指令指標設為 dlopen 位址讓其執行。完成後設返回地址為無效值(如 0x41414140),故意觸發 Segfault,Injector 偵測到段錯誤即知函數已完成,恢復進程原狀。
實用技巧與重點
乾貨- 工具與 API
- Injector(未正式發布,計畫數月內於網站發布)
- 核心系統呼叫:Ptrace(Linux)、procfs(Solaris)
- 關鍵函數:dlopen(libc)、ld.so.do1(Solaris 動態連結器)、GBC 庫(Linux 動態連結器)
- 技術細節
- 連結映射表(Link Map)結構:包含已載入庫的基位址、動態段指標、符號表指標、字串表指標
- Intercept 物件:定義要攔截的函數清單,編譯入注入庫中
- PLT(程序連結表)修補原理:將 PLT 條目從原函數指標改為注入庫中替代版本,保存原函數指標供庫內呼叫
- 延遲綁定問題:Linux/Solaris 函數通常第一次呼叫時才解析,若從已攔截函數內呼叫原函數,動態連結器會重新解析覆蓋補丁,需每次重新應用
- 防禦機制
- 禁用 Ptrace(影響 strace、ltrace 等除錯工具)
- 隱藏動態連結映射資訊
- 移除堆疊執行權限
- 限制進程附加權限
- 實際案例數據
- 案例 1(防禦 WhoIsDaemon):注入約 20 行 C 程式碼的修復庫,攔截 fgets 函數,移除 ANA SOA 查詢中的所有百分號(%),完全阻止公開漏洞利用
- 案例 2(SSH 後門):注入攔截 read、getuid、setuid、PAM 相關函數的惡意庫,實現 (1)記錄用戶名密碼,(2)設定魔法密碼(如「讓我進去」)自動授予 root 權限,(3)記錄所有終端輸入輸出(即使 SSL 加密通訊)
結論
結論“Injector 揭示了執行時記憶體操縱的深層威力:一旦進程被附加,再精密的應用層防禦都形同虛設,真正的防線終歸是系統層級的存取控制。”
完整解析
詳細Injector 是講者針對 Unix 系統開發的動態進程注入工具,能在執行中的程式內部載入任意共享函式庫。與 Windows 依賴 OpenProcess、LoadLibraryA、VirtualAllocEx、CreateRemoteThread 等現成 API 不同,Unix 系統缺乏遠端記憶體分配和線程建立的對應 API,迫使講者採取多個創意解決方案。
首先是附加到目標進程。講者利用 Ptrace(Linux 傳統偵錯機制)或 procfs(Solaris 更功能豐富的選擇)以偵錯器身份連接到遠端進程。這些機制雖然允許讀寫目標進程的記憶體和暫存器,但無法分配遠端記憶體或建立遠端線程,正是 Unix 的兩大限制。
第二是定位 dlopen 函數的真實位址。由於 ASLR 補丁的存在,記憶體位址被隨機化,講者不能假設 dlopen 在目標進程與自身進程位於相同位址。解決方案是在目標進程中遍歷動態連結器維護的連結映射表(Link Map),找到已載入的 libc 庫的基位址,然後透過其符號表和字串表查詢 dlopen 函數的相對偏移量,最後計算其在目標進程中的絕對位址。這個過程完全模擬了動態連結器本身的操作方式。
第三是執行遠端函數呼叫。講者採用了一個膽識過人的手段:先將函數參數(包括指向共享庫名稱的字串指標)複製到目標進程的堆疊中,然後使用偵錯器修改 CPU 暫存器(根據 x86 或 SPARC 的呼叫約定設定參數暫存器),最後將指令指標設為 dlopen 的位址,強制目標進程跳轉到 dlopen 執行。函數執行完成後,講者故意設定返回地址為無效值(如 0x41414140),使進程在返回時觸發段錯誤。Injector 以偵錯器身份偵測到這個段錯誤後,立即確認 dlopen 已完成執行,隨後恢復進程的所有暫存器和記憶體狀態,讓進程繼續正常運行。這種方法的妙處在於完全避免了在遠端進程中注入可執行程式碼,規避了記憶體保護的限制。
第四個挑戰是系統呼叫中斷。當講者修改執行流強制進程跳轉時,若進程原本正在阻塞某個系統呼叫(如 read 等待套接字連接、select 等待 I/O),中斷會導致作業系統在第一個暫存器(eax/o1/g1)寫入 EINTR 等錯誤號。大多數程式碼無法正確處理這個異常,進程通常會因此崩潰。講者的解決方案是先保存進程的所有暫存器狀態,偵測進程是否在系統呼叫中,若是則讀取並保存原系統呼叫號碼,執行注入操作後,恢復暫存器並從原系統呼叫指令重新啟動,讓作業系統能正確恢復呼叫上下文。
注入庫載入後,講者介紹了強大的 Intercept 動態函數攔截機制。庫可以定義一份要攔截的函數清單,Intercept 會遍歷目標進程的 PLT(程序連結表)、找到相應的函數條目、將其修補為指向注入庫中的替代版本,同時保存指向原函數的指標供庫內呼叫。這允許在執行時即時改變函數的行為。但由於 Linux/Solaris 採用延遲綁定策略(函數在第一次呼叫時才由動態連結器解析),從已攔截的函數內呼叫原函數時,動態連結器會重新解析並覆蓋補丁。講者因此需要每次執行後重新應用補丁,以防補丁失效。
講者透過兩個實際演示展示了技術的雙重用途。第一個案例展示防禦用途:WhoIsDaemon 存在著眾所周知的格式化字串漏洞。講者編寫了僅約 20 行 C 程式碼的修復庫,攔截 fgets 函數,檢查讀取的字串是否為 ANA SOA 查詢,若是則刪除其中的百分號(%),徹底防止格式化字串被利用。演示中原本可被公開漏洞利用程式攻破的守護進程在注入修復庫後變得完全無懈可擊。第二個案例展示了極為危險的惡意用途:向執行中的 SSH 守護進程注入一個複雜的後門庫。後門攔截 read、getuid、setuid 以及 PAM 相關函數,實現三個目標:(1)記錄每個登入連接的用戶名和密碼,(2)設定一個「魔法密碼」(如「讓我進去」),任何用戶使用此密碼登入時都自動獲取 root shell,(3)記錄所有終端輸入輸出。這些演示深刻揭示了該技術既可用於執行時修複生產環境中無法停機服務的漏洞,也可被用於輕易植入後門和竊取敏感資訊。
防禦方面,講者列舉了禁用 Ptrace、隱藏動態連結映射、移除堆疊執行權限等措施,但強調這些都只是增加攻擊難度而非完全阻止。他指出,一旦機器被駭客入侵且攻擊者獲得進程附加權限,就沒有任何執行中的進程是安全的。未來工作包括採用重定位補丁替代 PLT 修補以避免重複應用的開銷,以及移植到 BSD 等其他 Unix 變體。
關鍵時刻
Pipeline v2帶時間戳的重點,會在逐字稿層級分析上線後產生。目前請先透過原始影片觀看。
事實查核
Pipeline v2說法查證是下一次管線升級的一部分。KeyFrame 只會顯示它真正能驗證的內容。

