這學期選了軟體安全課,做題的過程中獲得了不少新姿♂勢,在這裡記錄一下。
雖然做題搞的我每個周一晚上都熬夜QAQ
ptrace以及shellcode
有一道題是這樣的,一個進程通過ptrace(PTRACE_TRACEME, 0, 1, 0)來檢查是否有調試器,沒有源代碼,只有二進位文件;程序屬於特別的組,要求以這個組的身份調用一個外部程序。
自己ptrace自己意味著用gdb會被發現。我選擇LD_PRELOAD來hook掉ptrace。具體方法是寫一個假冒的ptrace函數:
1 2 3 4 5 6 |
#include <stdio.h> int ptrace(int request, int pid, int addr, int data){ printf("false ptrace, haha\n"); fflush(stdout); return 0; } |
只要永遠返回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並不一樣)。
具體的,我們可以通過如下代碼實現:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
#include <sys/ptrace.h> #include <sys/types.h> #include <sys/wait.h> #include <unistd.h> #include <stdio.h> #include <stdlib.h> unsigned char[] shellcode="....."; void main(){ int pid; int status; if((pid = fork()) == 0) { execl("/path/to/vulnerable","vulnerable",shellcode, 0); printf("child exit"); }else{ printf("called\n"); wait(&status); printf("waked\n"); printf("pid=%d\n", pid); //sleep(30); ptrace(PTRACE_CONT, pid , 0, 0); } } |
注意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?
參考:
x64棧溢出
除了地址長度有了不同以外,和x86相比,我們還需要注意寄存器的字長。進入shellcode之後,如果棧空間充裕,盡量先將所有通用寄存器(用xor rax, rax的形式)清零;以防給eax或者al賦了值,高位還不是0。
其他的好像都是基本的套路。歡迎交♂流。