一、背景
ptrace是Unix系列系統的系統調用之一。其主要功能是實現對進程的追蹤。對目標進程,進行流程控制,用戶寄存器值讀取&寫入操作,內存進行讀取&修改。這樣的特性,就非常適合,用於編寫實現,遠程代碼注入。大部分的病毒會使用到這一點,實現,自用空間注入,rip位置直接注入,text段與data段之間的空隙注入。
二、測試環境
- Linux ubuntu 4.15.0-34-generic x86_64
三、主要流程
實驗使用的方式是,直接rip位置代碼注入。注入的shellcode,主要實現,使用syscall,進行系統調用。調用mmap(),在目標進程的0x00100000地址位置,申請8192大小的內存空間,將指定的注入程序,注入到這申請的空間中。並且將rip調整至其entry入口地址。
- attach目標進程。
- 讀取user_reg,獲取rip的值。
- 將shellcode注入到rip位置。
- 使用PTRACE_CONT參數,恢復程序執行。
- 當shellcode執行完成mmap匿名內存空間申請成功後。發送int3中斷信號給追蹤進程。
- 將要注入的elf可執行文件open,並且read(注意read要64位對齊)到緩衝區。
- PTRACE_POKE將elf可執行文件寫入到被注入進程shellcode創建的內存空間上。
- 讀寫user_reg將rip設置到elf可執行文件的入口點,也就是:mmap申請內存的起始地址+phdr->entry。
- 使用PTRACE_CONT參數,恢復程序執行,完成注入。
四、代碼實現
GitHub : https://github.com/HotIce0/code_injection
- code_injection.c 代碼注入的執行程序
- inject_me.c 被執行遠程注入的程序
- test.c 要注入elf可執行程序
五、重點
- gcc -fpie -pie 用於生產與位置無關的目標代碼。
- gcc -nostdlib 用於生成,不使用標準鏈接庫的代碼。
- #define WORD_ALIGN(x) ((x + 7) & ~7) 用於64位對齊。因爲,在64位的ptrace的文檔中說明,PTRACE_POKE && PTRACE_PEEK一次操作的內存大小是64位,使用malloc申請的內存空間如果不是64位的整數倍,可能會出現內存非法訪問。segement fault。
// Open specify ELF file.
fd = open(h.exec, O_RDONLY);
// Read file status.
if (fstat(fd, &st)) {
perror("fstat");
exit(-1);
}
h.mem = (u_int8_t *)malloc(WORD_ALIGN(st.st_size));
4.在injection_code()函數中,要使用write輸出到屏幕一段字符串,如果直接“mmap _”,這段數據將會存儲到strtab節中,這會導致,輸出不知道什麼(看運氣),因爲,strtab的數據並沒有寫入目標進程中。
所以,這裏我使用的是字符串數組,這樣存儲在棧中。
5.這裏並沒有採用常用的shellcode一樣的字節碼,而是,直接將編譯好的代碼,讀取,寫入到目標進程。計算函數的大小,也是通過,直接injection_code()下面的函數,減去injection_code。函數名即函數指針。
6.其中的block_print()函數,對功能實現是無用的,只是我調試代碼時編寫的。Elf64_Addr get_text_segment_addr_by_pid(pid_t pid) 這個函數是用於,如果代碼注入位置的是text段的起始位置時用到的。由於我是直接注入到rip所在位置,所以,就沒用到。
7.我的代碼中實現的是直接注入到rip(r:register寄存器,I:instruction指令,p:point指針),也就是程序的當前執行位置。
當然也可以插入到代碼段的可執行區域。我自己編寫時就遇到了問題,因爲,我直接通過讀取{pid}/proc/maps文件中的地址映射,讀取到了text段的地址範圍。於是我直接把injection_code注入到了text段的首地址起始的一段內存中。並將rip指向該區域。隨後,報出了segments fault錯誤。這個問題很簡單。
-
通過
readelf --segments
查看段。
第一個LOAD段也就是代碼段,因爲起始地址爲0。代碼段中包括以下節,.interp .note.ABI.tag .note.gnu.build-id 等等。 -
查看節詳細信息
readelf --sections
.
這裏可以看到從.interp 到.rela.plt都屬於text段。但是並沒有X權限,也就是執行權限。 -
查看elf官方文檔可知:
- W是可寫權限。
- A是ALLOC(聯想起malloc)也就是內存分配,這部分將會被分配內存,並且將文件中的內容映像近內存,X是可執行權限。A是ALLOC(聯想起malloc)也就是內存分配,這部分將會被分配內存,並且將文件中的內容映像近內存。
- X是可執行權限。X是可執行權限。
-
也就是說,如果直接寫入到代碼段首地址。這裏的代碼將無法執行,因爲,沒有X可執行權限。那麼代碼必須寫到具有執行權限的位置。解決方法是:
1. 讀取可執行文件。
2. 解析elf信息,讀取到具有執行權限的節,並且節的大小足夠注入injection_code。
3. 進行代碼注入。
要讀取到可執行文件可以這樣。
- 通過pid,使用readlink,讀取proc/exe這個軟連接。讀取到可執行文件的path。
- 再通過path,使用open打開,read讀入。代碼如下
// get path by pid via read /proc/{pid}/exe soft link path.
char *get_path_by_pid(pid_t pid)
{
char str_proc_pid_path[PATH_MAX], str_path[PATH_MAX], *p;
if (snprintf(str_proc_pid_path, PATH_MAX, "/proc/%d/exe", pid) < 0) {
perror("snprintf");
exit(-1);
}
// readlink to get the path
if (readlink(str_proc_pid_path, str_path, PATH_MAX) < 0) {
perror("readlink");
exit(-1);
}
if ((p = strdup(str_path)) == NULL) {
perror("strdup");
exit(-1);
}
return p;
}