中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

怎么利用Ptrace攔截和模擬Linux系統調用

發布時間:2021-08-05 09:48:38 來源:億速云 閱讀:208 作者:chen 欄目:網絡安全

本篇內容介紹了“怎么利用Ptrace攔截和模擬Linux系統調用”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!

寫在前面的話

ptrace(2)這個系統調用一般都跟調試離不開關系,它不僅是類Unix系統中本地調試器監控實現的主要機制,而且它還是strace系統調用常用的實現方法。ptrace()系統調用函數提供了一個進程(the “tracer”)監察和控制另一個進程(the “tracee”)的方法,它不僅可以監控系統調用,而且還能夠檢查和改變“tracee”進程的內存和寄存器里的數據,甚至它還可以攔截系統調用。

這里的“攔截”我指的是tracer能夠改變系統調用參數,改變系統調用的返回值,甚至屏蔽特定的系統調用。這也就意味著,一個tracer將能夠完全實現自己的系統調用,這就非常有趣了,也就是說,一個tracer將可以模擬出一整套操作系統機制,而且這一切都不需要內核提供任何其他幫助。

但問題在于,一個進程一次只能夠綁定一個tracer,因此我們無法在調試進程(GDB)的過程中模擬出一套外部操作系統,而另一個問題就是模擬系統調用將耗費更多的資源開銷。

在這篇文章中,我將主要討論x86-64架構下的Linux Ptrace,并且我還會使用到一些特定的Linux擴展。除此之外,我可能會忽略錯誤檢查,但最終發布的完整源碼將會解決這些問題。

strace

在開始之前,我們先看一看strace的實現骨架。Ptrace一直都沒有相應的使用標準,但在不同的操作系統中它的接口都是類似的,尤其是它的核心功能,但多多少少都會有一些細微的差別。Ptrace(2)的原型類似如下:

long ptrace(int request, pid_t pid, void *addr, void *data);

pid是tracee的進程ID,一個tracee一次只能綁定一個tracer,但一個tracer可以綁定多個tracee。

request域負責選擇一個指定的Ptrace函數,例如ioctl(2)接口。對于strace來說,只有下面是必須的:

PTRACE_TRACEME:它的父進程必須跟蹤這個進程。

PTRACE_SYSCALL:繼續運行,但是會在下一個系統調用入口暫停運行。

PTRACE_GETREGS:獲取tracee的寄存器備份。

另外兩個數據域,即addr和data,它們負責給選定的Ptrace函數提供參數,一般這兩個數據都可以忽略,這里我選擇傳入0。

strace接口本質上是其他命令的前綴:

$strace [strace options] program [arguments]

我的最小化配置不包含任何參數,所以要做的第一件事就是假設它至少包含一個參數(fork(2)),通過argv傳遞。在加載目標程序之前,新的進程會告知內核它的父進程將會對它進行跟蹤監視,tracee將會被這個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));
}

父進程將使用wait(2)來等待子進程的PTRACE_TRACEME,當wait(2)返回值之后,子進程將會被掛起:

wait pid(pid,0, 0);

在允許子進程繼續運行之前,我們將告訴操作系統tracee應該跟它的父進程一起終止。真實場景下的strace實現還需要設置其他的參數,例如PTRACE_O_TRACEFORK:

ptrace(PTRACE_SETOPTIONS,pid, 0, PTRACE_O_EXITKILL);

捕捉系統調用的循環步驟如下:

1.   等待進程進入下一次系統調用。

2.   打印系統調用信息。

3.   允許系統調用執行,并等待返回結果。

4.   打印系統調用的返回值。

PTRACE_SYSCALL請求可以完成等待下一個系統調用以及等待系統調用結束這兩個任務,跟之前一樣,這里也需要使用wait(2)來等待tracee進入特定狀態。

ptrace(PTRACE_SYSCALL,pid, 0, 0); waitpid(pid,0, 0);

wait(2)返回后,線程寄存器中將存儲有系統調用號和相應參數。下一步就是收集系統調用信息,在不同的系統架構中這一步的實現方式也不同。在x86-64中,系統調用號是通過rax傳遞的,參數(最大為6)將傳遞給rdi、rsi、rdx、r10、r8和r9。讀取寄存器還需要其他的Ptrace調用,但這里就不需要wait(2)了,因為tracee并不會改變狀態。

struct user_regs_struct regs;
ptrace(PTRACE_GETREGS,pid, 0, &regs);
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);

接下來就是另一個PTRACE_SYSCALL和wait(2),然后利用PTRACE_GETREGS獲取結果,結果將存儲在rax中:

ptrace(PTRACE_GETREGS,pid, 0, &regs); fprintf(stderr," = %ld\n", (long)regs.rax);

這個樣本程序的輸出結果還是比較簡陋的,其中沒有包含系統調用的符號名,并且每一個參數都是按數字形式打印的,不過這已經足夠奠定系統調用攔截的基礎了。

系統調用攔截

假設我們想利用Ptrace去實現一個類似OpenBSD的pledge(2)這樣的東西。基本思路如下:很多程序一般都有一個初始化過程,這個過程需要涉及到很多系統訪問權限,例如打開文件和綁定套接字等等。初始化完成之后,它們會進入主循環,并處理輸入數據,這里只需要使用到少量系統調用。

在進入主循環之前,進程可以限制自身只進行少量操作,如果程序存在漏洞的話,pledge還可以限制漏洞利用代碼所能完成的事情。當然了,我們不僅可以篡改系統調用參數,而且還可以修改系統調用號,并將其轉換成一個不存在的系統調用,然后在errno中報告一個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, &regs);
    /* 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,&regs);
    }
 
    /* 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,&regs);
    }
}

創建自定義的系統調用

我將我新創建的模仿pledge的系統調用稱為xpledge(),我選擇的系統調用號是10000:

#define SYS_xpledge 10000

下面是這個針對tracee的系統調用完整接口實現:

#define_GNU_SOURCE #include<unistd.h> #defineXPLEDGE_RDWR  (1 << 0) #defineXPLEDGE_OPEN  (1 << 1) #definexpledge(arg) syscall(SYS_xpledge, arg)

如果傳遞的參數為0,則只允許執行一些基本的系統調用,包括內存分配等。PLEDGE_RDWR指定的是各種讀寫操作,如read(2)、readv(2)、pread(2)和preadv(2)等。

在xpledge tracer中,我只需要檢測這個系統調用:

/*Handle entrance */ switch(regs.orig_rax) {
    case SYS_pledge:
        register_pledge(regs.rdi);
        break;
}

操作系統將返回ENOSYS,因為它不是一個真正的系統調用,所以我們需要用success(0)重寫返回結果:

/*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中有一個非常實用的函數:PTRACE_SYSMU,我們可以利用這個函數來實現系統模擬:

for(;;) {
    ptrace(PTRACE_SYSEMU, pid, 0, 0);
    waitpid(pid, 0, 0);
    struct user_regs_struct regs;
    ptrace(PTRACE_GETREGS, pid, 0, &regs);
    switch (regs.orig_rax) {
        case OS_read:
            /* ... */         case OS_write:
            /* ... */         case OS_open:
            /* ... */         case OS_exit:
            /* ... */         /* ... and so on ... */     }
}

此代碼框架在相同系統架構中的測試結果都是能夠穩定運行的,大家可以根據自己的需要來修改代碼。

“怎么利用Ptrace攔截和模擬Linux系統調用”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識可以關注億速云網站,小編將為大家輸出更多高質量的實用文章!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

民勤县| 景谷| 莫力| 呼和浩特市| 博罗县| 乃东县| 兰溪市| 玉环县| 贡山| 普兰县| 延川县| 兴业县| 高淳县| 苏尼特右旗| 兴城市| 黎平县| 宁德市| 民乐县| 龙岩市| 亚东县| 古田县| 南漳县| 石林| 穆棱市| 荥经县| 永康市| 桂阳县| 镇巴县| 绍兴市| 长兴县| 林口县| 永新县| 伊春市| 阳泉市| 体育| 潼南县| 廉江市| 太和县| 遵化市| 威宁| 富顺县|