- 資訊首頁(yè) > 網(wǎng)絡(luò )安全 >
- 怎么利用Ptrace攔截和模擬Linux系統調用
本篇內容介紹了“怎么利用Ptrace攔截和模擬Linux系統調用”的有關(guān)知識,在實(shí)際案例的操作過(guò)程中,不少人都會(huì )遇到這樣的困境,接下來(lái)就讓小編帶領(lǐng)大家學(xué)習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學(xué)有所成!
ptrace(2)這個(gè)系統調用一般都跟調試離不開(kāi)關(guān)系,它不僅是類(lèi)Unix系統中本地調試器監控實(shí)現的主要機制,而且它還是strace系統調用常用的實(shí)現方法。ptrace()系統調用函數提供了一個(gè)進(jìn)程(the “tracer”)監察和控制另一個(gè)進(jìn)程(the “tracee”)的方法,它不僅可以監控系統調用,而且還能夠檢查和改變“tracee”進(jìn)程的內存和寄存器里的數據,甚至它還可以攔截系統調用。
這里的“攔截”我指的是tracer能夠改變系統調用參數,改變系統調用的返回值,甚至屏蔽特定的系統調用。這也就意味著(zhù),一個(gè)tracer將能夠完全實(shí)現自己的系統調用,這就非常有趣了,也就是說(shuō),一個(gè)tracer將可以模擬出一整套操作系統機制,而且這一切都不需要內核提供任何其他幫助。
但問(wèn)題在于,一個(gè)進(jìn)程一次只能夠綁定一個(gè)tracer,因此我們無(wú)法在調試進(jìn)程(GDB)的過(guò)程中模擬出一套外部操作系統,而另一個(gè)問(wèn)題就是模擬系統調用將耗費更多的資源開(kāi)銷(xiāo)。
在這篇文章中,我將主要討論x86-64架構下的Linux Ptrace,并且我還會(huì )使用到一些特定的Linux擴展。除此之外,我可能會(huì )忽略錯誤檢查,但最終發(fā)布的完整源碼將會(huì )解決這些問(wèn)題。
在開(kāi)始之前,我們先看一看strace的實(shí)現骨架。Ptrace一直都沒(méi)有相應的使用標準,但在不同的操作系統中它的接口都是類(lèi)似的,尤其是它的核心功能,但多多少少都會(huì )有一些細微的差別。Ptrace(2)的原型類(lèi)似如下:
long ptrace(int request, pid_t pid, void *addr, void *data);
pid是tracee的進(jìn)程ID,一個(gè)tracee一次只能綁定一個(gè)tracer,但一個(gè)tracer可以綁定多個(gè)tracee。
request域負責選擇一個(gè)指定的Ptrace函數,例如ioctl(2)接口。對于strace來(lái)說(shuō),只有下面是必須的:
PTRACE_TRACEME:它的父進(jìn)程必須跟蹤這個(gè)進(jìn)程。
PTRACE_SYSCALL:繼續運行,但是會(huì )在下一個(gè)系統調用入口暫停運行。
PTRACE_GETREGS:獲取tracee的寄存器備份。
另外兩個(gè)數據域,即addr和data,它們負責給選定的Ptrace函數提供參數,一般這兩個(gè)數據都可以忽略,這里我選擇傳入0。
strace接口本質(zhì)上是其他命令的前綴:
$strace [strace options] program [arguments]
我的最小化配置不包含任何參數,所以要做的第一件事就是假設它至少包含一個(gè)參數(fork(2)),通過(guò)argv傳遞。在加載目標程序之前,新的進(jìn)程會(huì )告知內核它的父進(jìn)程將會(huì )對它進(jìn)行跟蹤監視,tracee將會(huì )被這個(gè)Ptrace系統調用掛起:
pid_tpid = fork(); switch(pid) { case -1: /* error */ FATAL("%s", strerror(errno)); case 0: /* child */ ptrace(PTRACE_TRACEME, 0, 0, 0); execvp(argv[1], argv + 1); FATAL("%s", strerror(errno)); }
父進(jìn)程將使用wait(2)來(lái)等待子進(jìn)程的PTRACE_TRACEME,當wait(2)返回值之后,子進(jìn)程將會(huì )被掛起:
wait pid(pid,0, 0);
在允許子進(jìn)程繼續運行之前,我們將告訴操作系統tracee應該跟它的父進(jìn)程一起終止。真實(shí)場(chǎng)景下的strace實(shí)現還需要設置其他的參數,例如PTRACE_O_TRACEFORK:
ptrace(PTRACE_SETOPTIONS,pid, 0, PTRACE_O_EXITKILL);
捕捉系統調用的循環(huán)步驟如下:
1. 等待進(jìn)程進(jìn)入下一次系統調用。
2. 打印系統調用信息。
3. 允許系統調用執行,并等待返回結果。
4. 打印系統調用的返回值。
PTRACE_SYSCALL請求可以完成等待下一個(gè)系統調用以及等待系統調用結束這兩個(gè)任務(wù),跟之前一樣,這里也需要使用wait(2)來(lái)等待tracee進(jìn)入特定狀態(tài)。
ptrace(PTRACE_SYSCALL,pid, 0, 0); waitpid(pid,0, 0);
wait(2)返回后,線(xiàn)程寄存器中將存儲有系統調用號和相應參數。下一步就是收集系統調用信息,在不同的系統架構中這一步的實(shí)現方式也不同。在x86-64中,系統調用號是通過(guò)rax傳遞的,參數(最大為6)將傳遞給rdi、rsi、rdx、r10、r8和r9。讀取寄存器還需要其他的Ptrace調用,但這里就不需要wait(2)了,因為tracee并不會(huì )改變狀態(tài)。
struct user_regs_struct regs; ptrace(PTRACE_GETREGS,pid, 0, ®s); longsyscall = regs.orig_rax; fprintf(stderr,"%ld(%ld, %ld, %ld, %ld, %ld, %ld)", syscall, (long)regs.rdi, (long)regs.rsi,(long)regs.rdx, (long)regs.r10, (long)regs.r8, (long)regs.r9);
接下來(lái)就是另一個(gè)PTRACE_SYSCALL和wait(2),然后利用PTRACE_GETREGS獲取結果,結果將存儲在rax中:
ptrace(PTRACE_GETREGS,pid, 0, ®s); fprintf(stderr," = %ld\n", (long)regs.rax);
這個(gè)樣本程序的輸出結果還是比較簡(jiǎn)陋的,其中沒(méi)有包含系統調用的符號名,并且每一個(gè)參數都是按數字形式打印的,不過(guò)這已經(jīng)足夠奠定系統調用攔截的基礎了。
假設我們想利用Ptrace去實(shí)現一個(gè)類(lèi)似OpenBSD的pledge(2)這樣的東西?;舅悸啡缦拢汉芏喑绦蛞话愣加幸粋€(gè)初始化過(guò)程,這個(gè)過(guò)程需要涉及到很多系統訪(fǎng)問(wèn)權限,例如打開(kāi)文件和綁定套接字等等。初始化完成之后,它們會(huì )進(jìn)入主循環(huán),并處理輸入數據,這里只需要使用到少量系統調用。
在進(jìn)入主循環(huán)之前,進(jìn)程可以限制自身只進(jìn)行少量操作,如果程序存在漏洞的話(huà),pledge還可以限制漏洞利用代碼所能完成的事情。當然了,我們不僅可以篡改系統調用參數,而且還可以修改系統調用號,并將其轉換成一個(gè)不存在的系統調用,然后在errno中報告一個(gè)EPERM錯誤信息:
for(;;) { /* Enter next system call */ ptrace(PTRACE_SYSCALL, pid, 0, 0); waitpid(pid, 0, 0); struct user_regs_struct regs; ptrace(PTRACE_GETREGS, pid, 0, ®s); /* Is this system call permitted? */ int blocked = 0; if (is_syscall_blocked(regs.orig_rax)) { blocked = 1; regs.orig_rax = -1; // set to invalidsyscall ptrace(PTRACE_SETREGS, pid, 0,®s); } /* Run system call and stop on exit */ ptrace(PTRACE_SYSCALL, pid, 0, 0); waitpid(pid, 0, 0); if (blocked) { /* errno = EPERM */ regs.rax = -EPERM; // Operation notpermitted ptrace(PTRACE_SETREGS, pid, 0,®s); } }
我將我新創(chuàng )建的模仿pledge的系統調用稱(chēng)為xpledge(),我選擇的系統調用號是10000:
#define SYS_xpledge 10000
下面是這個(gè)針對tracee的系統調用完整接口實(shí)現:
#define_GNU_SOURCE #include<unistd.h> #defineXPLEDGE_RDWR (1 << 0) #defineXPLEDGE_OPEN (1 << 1) #definexpledge(arg) syscall(SYS_xpledge, arg)
如果傳遞的參數為0,則只允許執行一些基本的系統調用,包括內存分配等。PLEDGE_RDWR指定的是各種讀寫(xiě)操作,如read(2)、readv(2)、pread(2)和preadv(2)等。
在xpledge tracer中,我只需要檢測這個(gè)系統調用:
/*Handle entrance */ switch(regs.orig_rax) { case SYS_pledge: register_pledge(regs.rdi); break; }
操作系統將返回ENOSYS,因為它不是一個(gè)真正的系統調用,所以我們需要用success(0)重寫(xiě)返回結果:
/*Handle exit */ switch(regs.orig_rax) { case SYS_pledge: ptrace(PTRACE_POKEUSER, pid, RAX * 8,0); break; }
樣例程序的輸出結果如下:
$./example fread("/dev/urandom")[1]= 0xcd2508c7 XPledging... XPledgefailed: Function not implemented fread("/dev/urandom")[2]= 0x0be4a986 fread("/dev/urandom")[1]= 0x03147604
在tracer下運行的結果如下:
$./xpledge ./example fread("/dev/urandom")[1]= 0xb2ac39c4 XPledging... fopen("/dev/urandom")[2]:Operation not permitted fread("/dev/urandom")[1]= 0x2e1bd1c4
Linux下的Ptrace中有一個(gè)非常實(shí)用的函數:PTRACE_SYSMU,我們可以利用這個(gè)函數來(lái)實(shí)現系統模擬:
for(;;) { ptrace(PTRACE_SYSEMU, pid, 0, 0); waitpid(pid, 0, 0); struct user_regs_struct regs; ptrace(PTRACE_GETREGS, pid, 0, ®s); switch (regs.orig_rax) { case OS_read: /* ... */ case OS_write: /* ... */ case OS_open: /* ... */ case OS_exit: /* ... */ /* ... and so on ... */ } }
此代碼框架在相同系統架構中的測試結果都是能夠穩定運行的,大家可以根據自己的需要來(lái)修改代碼。
免責聲明:本站發(fā)布的內容(圖片、視頻和文字)以原創(chuàng )、來(lái)自互聯(lián)網(wǎng)轉載和分享為主,文章觀(guān)點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權請聯(lián)系QQ:712375056 進(jìn)行舉報,并提供相關(guān)證據,一經(jīng)查實(shí),將立刻刪除涉嫌侵權內容。
Copyright ? 2009-2021 56dr.com. All Rights Reserved. 特網(wǎng)科技 特網(wǎng)云 版權所有 珠海市特網(wǎng)科技有限公司 粵ICP備16109289號
域名注冊服務(wù)機構:阿里云計算有限公司(萬(wàn)網(wǎng)) 域名服務(wù)機構:煙臺帝思普網(wǎng)絡(luò )科技有限公司(DNSPod) CDN服務(wù):阿里云計算有限公司 中國互聯(lián)網(wǎng)舉報中心 增值電信業(yè)務(wù)經(jīng)營(yíng)許可證B2
建議您使用Chrome、Firefox、Edge、IE10及以上版本和360等主流瀏覽器瀏覽本網(wǎng)站