無需Ptrace就能實現Linux進程間代碼注入
ptrace系統調用
ptrace系統調從名字上看是用於進程跟蹤的,它提供了父進程可以觀察和控制其子進程執行的能力,並允許父進程檢查和替換子進程的內核鏡像(包括寄存器)的值。其基本原理是: 當使用了ptrace跟蹤後,所有發送給被跟蹤的子進程的信號(除了SIGKILL),都會被轉發給父進程,而子進程則會被阻塞,這時子進程的狀態就會被系統標註為TASK_TRACED。而父進程收到信號後,就可以對停止下來的子進程進行檢查和修改,然後讓子進程繼續運行。
ptrace是如此的強大,以至於有很多大家所常用的工具都基於ptrace來實現。
ptrace可以實時監測和修改另一個進程的運行,它是如此的強大以至於曾經因為它在unix-like平台(如Linux, *BSD)上產生了各種漏洞。
所以,今天我要跟大家介紹的是在不使用ptrace的情況下獲得代碼注入。由於使用此方法不需要任何系統調用(sys call),因此使用一種簡單且無所不在的語言來完成代碼注入是可能的。
在不使用ptrace的情況下獲得代碼注入,就允許用戶執行任意的本機代碼,當只有標準的Bash shell和coreutils可用時,就可以製作一個從內存中執行二進位的有效載荷繞過noexecmountflag(掛載命令)。
無需Ptrace的進程間代碼注入
Linux上的/proc文件系統提供了對Linux系統運行的內省(Introspection),每個進程在文件系統中都有自己的目錄,其中包含有關流程及其內部的詳細信息。在這個目錄中,有兩個偽文件,分別是maps文件和mem文件。
maps文件包含分配給二進位文件的所有內存區域架構,以及所有包含的動態庫。不過,這個信息現在相對敏感,因為每個庫位置的偏移量是由ASLR隨機化的。
mem文件提供了流程使用的完整內存空間的稀疏架構,結合從maps文件獲得的偏移量,可以使用mem文件讀取和寫入進程的內存空間。如果偏移量是錯誤的,或者從開始位置按順序讀取文件,將返回讀寫錯誤,因為這相當於是讀取不可訪問的未分配內存。
譯者註:內省(Introspection)是面向對象語言和環境的一個強大特性,它是對象揭示自己作為一個運行時對象的詳細信息的一種能力。這些詳細信息包括對象在繼承樹上的位置,對象是否遵循特定的協議,以及是否可以響應特定的消息。
假定沒有其他限制訪問控制(如SELinux或AppArmor),這些目錄中的文件的讀寫許可權是由位於/proc/sys/kernel/yama中的ptrace_scope文件決定的。Linux內核提供了可以設置不同值的文檔,比如,對於Linux進程間代碼注入,就有兩層設置。較低的安全性設置(0和1)允許在同一uid下的任何進程,或者只是父進程,分別寫入進程/ proc/${PID}/ mem文件。這些設置中的任何一個都可以進行代碼注入。而更安全的設置,2和3,將限制admin寫入,或者完全禁止訪問。目前,大多數主要操作系統默認設置為「1」,只允許進程的父進程寫入其/ proc/${PID}/ mem文件。
這種代碼注入方法要使用這些文件,並且這個過程的棧存儲在一個標準內存區域內。這可以通過讀取一個進程的maps文件看到:
$ grep stack /proc/self/mapsn7ffd3574b000-7ffd3576c000 rw-p 00000000 00:00 0 [stack]n
其中,棧包含返回地址(在不使用「鏈接寄存器」存儲返回地址的架構上,例如ARM),因此函數知道返回地址後應在哪個位置繼續執行。通常,在諸如緩衝區溢出之類的攻擊中,棧是要被覆蓋的,而ROP技術則會對目標過程進行控制。ROP技術是用攻擊者控制的返回地址替換原始返回地址。這將允許攻擊者在每次執行ret指令時通過控制執行流調用自定義函數或系統調用。
雖然此代碼注入並不依賴於任何類型的緩衝區溢出,但我確實使用了一個ROP鏈。考慮到我獲得的訪問級別,我可以直接將棧寫入/ proc/${PID}/ mem中。
因此,該方法使用/proc/self/maps文件來查找ASLR隨機偏移量,從中我可以定位目標進程內的函數。使用這些函數地址,我可以替換當前棧上的正常返回地址,並獲得進程的控制。為了確保在重寫棧時,進程處於預期狀態,我使用sleep命令作為被覆蓋的從屬進程。sleep指令會在系統調用中使用nanosleep,這意味著sleep指令將在幾乎整個運行(不包括安裝和拆卸)中使用相同的函數。這就使我有足夠的機會在系統調用返回之前覆蓋整個流程的棧,這樣,我將控制我自定義的ROP指令片段(gadget)鏈。為了確保系統調用執行時棧指針的位置,我會將NOP sled作為載荷的前綴,這樣,棧指針幾乎就可以指向任何有效的位置,而這些位置在返回後,又會增加棧指針,直到它得到並執行我的有效載荷。
這些注入代碼可以在https://github.com/GDSSecurity/Cexigua上找到,不過,為了限制這個腳本的外部依賴,我做出了一些努力,因為在一些非常受限制的環境中,實用程序二進位文件可能不可用。當前的依賴性列表是:
GNU grep(必須支持- fao -byte-offset)
dd(用於讀取或寫入到一個文件的絕對偏移量)
Bash(用於數學和其他高級腳本特性)
該腳本的一般流程是在後台啟動sleep拷貝並記錄其進程id(PID),如上所述,sleep命令是一個理想的注入對象,因為在整個運行期間它只執行一個函數,這意味著當覆蓋棧時,我不會以意想不到的狀態結束。使用這個進程,我就可以發現實例化時哪些庫被載入。
使用/proc/${PID}/maps,我就可以嘗試找到所有我需要的gadget。如果我在自動載入的庫中找不到一個gadget,我將到/usr/lib的系統庫中擴展我的搜索,如果我在其他庫中找到該gadget,我就可以到下一個進程中使用LD_PRELOAD載入該庫。這將使丟失的gadget用於我的載荷。除此之外,我還驗證了我發現的gadget(使用一個純粹的grep命令)也位於載入庫的 .text部分。如果gadget不存在,那麼它們就有可能在執行時未被載入到可執行內存中,當我試圖返回到這個gadget時,就會導致運行崩潰。一句話,這個「預載入」階段應該會導致包含從標準載入庫中丟失的gadget的庫的空列表。
一旦我確認所有的gadget都可以提供給我,那我就會啟動另一個sleep進程。如果有必要的話,LD_PRELOAD額外的庫。現在,我重新在庫中找到這些gadget,然後將它們遷移到正確的ASLRbase,這樣我就知道這些gadget在目標區域的內存空間中的位置,而不僅僅是在磁碟上的二進位文件。如上所述,我在提交使用它之前,會驗證該gadget是否位於可執行內存區域。
我需要的gadget列表相對較短,對於以上的NOP sled,我需要一個NOP來填充所有要求函數調用的寄存器,以及一個用於調用標準函數的gadget。利用該函數組合,我就可以調用任何函數或系統調用,但不允許我執行任何類型的邏輯。一旦這些gadget被找到,我就可以將有效載荷描述文件中的偽指令轉換成一個ROP有效載荷。例如,對於一個64位系統,line的「syscall 60 0」將轉換為ROPgadget,將「60」載入到RAX寄存器、「0」到RDI,以及一個syscallgadget。這將產生40位元組的數據,即3個地址和2個常量,總共8個位元組。在執行時,這個系統調用將調用exit(0)。
我還可以調用PLT中的函數,包括從外部庫導入的函數,例如glibc。為了定位這些函數的偏移量,它們是由指針而不是系統調用來調用的,所以我需要首先在目標庫中解析ELF段頭,以找到函數偏移量。一旦我有了偏移量,我就可以將這些設備重新定位,並將它們添加到我的載荷中。
除此之外,我還處理了字元串參數,因為我知道內存棧的位置,因此我可以將字元串附加到有效載荷,並在必要時添加指向它們的指針。例如,fexecvesyscall需要參數數組的char * *。在注入我的載荷之前,我可以生成指針數組,並在執行時將棧上的指針指向一個指針數組,以便將一個正常的棧分配char * *一起使用。
一旦有效載荷被完全序列化,我就可以使用dd在過程中覆蓋棧,以及從/proc/${PID}/maps文件中獲得棧的偏移量。為了確保我不會遇到任何許可權問題,必須使用「exec dd」行來結束注入腳本,它用dd流程替換bash進程,因此將父進程的所有許可權從bash轉移到dd。
在棧被覆蓋之後,我就可以等待sleep二進位程序返回的nanosleepsyscall,這時我的ROP鏈就獲得了應用程序的控制權,載荷將被執行。
以ROP鏈被注入的特定載荷可以合理地避開一些運行時邏輯(runtime logic)。由於目前,我使用的有效載荷是一個簡單的open/memfd_create/sendfile/fexecve程序。它將目標二進位文件與文件系統noexecmountflag分離,然後將二進位文件從內存中執行,繞過noexec限制。由於sleep二進位文件是由bash執行的,因此不可能與二進位文件交互,因為它在dd退出後沒有父進程。為了繞過這個限制,可以使用在libfuse分布中存在的一個示例,假定fuse在目標系統上存在:passthrough二進位文件,那麼將創建根文件系統的鏡像掛載到目標目錄。這個新的掛載不是掛載的noexec,因此可以到一個二進位文件瀏覽這個新的掛載,然後執行。
點此鏈接,你可以看到允許在當前目錄中執行二進位文件是如何作為shell的標準子進程進行的有效載荷。
為了加快執行速度,在預載入和主運行之間緩存由其各自的ASLR base來緩存的gadget將是有用的。這可以通過使用聲明-p向磁碟轉儲關聯數組來實現,但是該方法不一定總是合適的。所以你還可以使用重新架構腳本,以在主bash進程的相同環境中執行有效載荷腳本,而不是使用$()執行的子進程。
通過取消對GNU grep的需求,可以進一步限制外部依賴關係。雖然在發現gadget時被認為太慢了,但是可能有更多的優化代碼。
所以,這種技術的明顯緩解策略是將ptrace_scope設置為一個更嚴格的值。雖然不能完全禁用系統上的ptrace,但是對於普通用戶來說,是無法使用ptrace的,你可以通過向/etc/sysctl.conf添加kernel.yama.ptrace_scope=2來設置。
其他緩解策略包括Seccomp、SELinux或 Apparmor 的組合,以限制獲取/proc/${PID}/map或/prop/${PID}/mem這樣敏感文件的許可權。另外,點擊該鏈接獲取Bash ROP和POC代碼。
本文翻譯自https://blog.gdssecurity.com/labs/2017/9/5/linux-based-inter-process-code-injection-without-ptrace2.html,如若轉載,請註明原文地址: http://www.4hou.com/technology/7614.html
推薦閱讀:
※動手教程:DIY一個OpenWRT的滲透工具
※iPhone X絕對安全?看我如何挫敗 iPhone X 的 Face ID
※為什麼人們會在乎自己的信息被泄露?
※以太坊錢包客戶端 Parity 遭黑客攻擊,價值3000萬美元的以太幣被盜
TAG:信息安全 |