這學期選了軟體安全課,做題的過程中獲得了不少新姿♂勢,在這裡記錄一下。

雖然做題搞的我每個周一晚上都熬夜QAQ

ptrace以及shellcode

有一道題是這樣的,一個進程通過ptrace(PTRACE_TRACEME, 0, 1, 0)來檢查是否有調試器,沒有源代碼,只有二進位文件;程序屬於特別的組,要求以這個組的身份調用一個外部程序。

自己ptrace自己意味著用gdb會被發現。我選擇LD_PRELOAD來hook掉ptrace。具體方法是寫一個假冒的ptrace函數:

只要永遠返回0(即成功)就可以了。然後-shared參數編譯成動態庫。

進入gdb之後,set environment LD_PRELOAD=./fake_ptrace.so就可以了。然後找棧的偏移,溢出。

注意在shellcode中需要調用setregid(getegid(), getegid())來把進程的egid變成我們需要的組id。有關於syscall的代號可以查詢linux內核源文件。注意要選擇合適自己所在的平台以及內核版本。

有一點要注意的是,setuid/setgid的程序,是無視LD_PRELOAD的,所以在gdb外我們沒法擼掉ptrace。

最要命的是在gdb中能夠用shellcode調起外部程序,出了gdb就變成了類似

[1]+  Stopped                 ./shell3

這樣的輸出。想來想去應該是ptrace的問題。看了一下文檔才知道,當一個進程被ptrace之後,每次它調用exec(或者system什麼的)就會收到一個SIGTRAP讓它停止運行(stopped狀態)。這時候父進程可以通過ptrace(PTRACE_CONT, child_pid , 0, 0)來恢復其運行。當然這個設計的初衷是讓父進程(tracer)可以對被調試進程(tracee)做修改或者審查寄存器等等。

這樣我們接下來要做的就是啟動這個進程,然後給它發PTRACE_CONT信號(注意,和SIGCONT並不一樣)。

 

具體的,我們可以通過如下代碼實現:

注意execl不能換成system,因為system啟動的進程,parent pid並不是當前進程,而是1,這將導致父進程收不到提醒信號以及無法發送PTRACE_CONT。

exec及各種變體在調用程序時不會改變pid,可以看做是將這個新的程序的映像載入到了內存里。system實際上是fork,exec,wait的一個打包,所以生出了新的進程。

wait(&status);表明父進程會阻塞直到接收到了任意信號。此時fork出來的子進程已經在運行/path/to/vulnerabled的代碼,而由於我們的棧溢出,這時運行到了shellcode里的exec,收到一個SIGTRAP而停止;父進程收到了一個信號表明子進程停止了,從wait中出來,運行到ptrace(PTRACE_CONT, pid , 0, 0),恢復子進程。

 

有一個不解的問題是,這種情況下不能在shellcode中啟動一個shell,而只能啟動一個立即退出的程序(比如ls之類)。難道是因為沒有tty?

參考:

  1. ptrace() Tutorial | Cvet’s Blog
  2. Tracing tricks with ptrace

x64棧溢出

除了地址長度有了不同以外,和x86相比,我們還需要注意寄存器的字長。進入shellcode之後,如果棧空間充裕,盡量先將所有通用寄存器(用xor rax, rax的形式)清零;以防給eax或者al賦了值,高位還不是0。

 

 

其他的好像都是基本的套路。歡迎交♂流。