[ptrace修改內存]實現進程代碼注入

一、背景

ptrace是Unix系列系統的系統調用之一。其主要功能是實現對進程的追蹤。對目標進程,進行流程控制,用戶寄存器值讀取&寫入操作,內存進行讀取&修改。這樣的特性,就非常適合,用於編寫實現,遠程代碼注入。大部分的病毒會使用到這一點,實現,自用空間注入,rip位置直接注入,text段與data段之間的空隙注入。

二、測試環境

  • Linux ubuntu 4.15.0-34-generic x86_64

三、主要流程

實驗使用的方式是,直接rip位置代碼注入。注入的shellcode,主要實現,使用syscall,進行系統調用。調用mmap(),在目標進程的0x00100000地址位置,申請8192大小的內存空間,將指定的注入程序,注入到這申請的空間中。並且將rip調整至其entry入口地址。

  1. attach目標進程。
  2. 讀取user_reg,獲取rip的值。
  3. 將shellcode注入到rip位置。
  4. 使用PTRACE_CONT參數,恢復程序執行。
  5. 當shellcode執行完成mmap匿名內存空間申請成功後。發送int3中斷信號給追蹤進程。
  6. 將要注入的elf可執行文件open,並且read(注意read要64位對齊)到緩衝區。
  7. PTRACE_POKE將elf可執行文件寫入到被注入進程shellcode創建的內存空間上。
  8. 讀寫user_reg將rip設置到elf可執行文件的入口點,也就是:mmap申請內存的起始地址+phdr->entry。
  9. 使用PTRACE_CONT參數,恢復程序執行,完成注入。

四、代碼實現

GitHub : https://github.com/HotIce0/code_injection

  • code_injection.c 代碼注入的執行程序
  • inject_me.c 被執行遠程注入的程序
  • test.c 要注入elf可執行程序

五、重點

  1. gcc -fpie -pie 用於生產與位置無關的目標代碼。
  2. gcc -nostdlib 用於生成,不使用標準鏈接庫的代碼。
  3. #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錯誤。這個問題很簡單。

  1. 通過readelf --segments 查看段。
    在這裏插入圖片描述
    第一個LOAD段也就是代碼段,因爲起始地址爲0。代碼段中包括以下節,.interp .note.ABI.tag .note.gnu.build-id 等等。

  2. 查看節詳細信息readelf --sections.
    在這裏插入圖片描述
    這裏可以看到從.interp 到.rela.plt都屬於text段。但是並沒有X權限,也就是執行權限。

  3. 查看elf官方文檔可知:

    1. W是可寫權限。
    2. A是ALLOC(聯想起malloc)也就是內存分配,這部分將會被分配內存,並且將文件中的內容映像近內存,X是可執行權限。A是ALLOC(聯想起malloc)也就是內存分配,這部分將會被分配內存,並且將文件中的內容映像近內存。
    3. X是可執行權限。X是可執行權限。
  4. 也就是說,如果直接寫入到代碼段首地址。這裏的代碼將無法執行,因爲,沒有X可執行權限。那麼代碼必須寫到具有執行權限的位置。解決方法是:
    1. 讀取可執行文件。
    2. 解析elf信息,讀取到具有執行權限的節,並且節的大小足夠注入injection_code。
    3. 進行代碼注入。

要讀取到可執行文件可以這樣。

  1. 通過pid,使用readlink,讀取proc/exe這個軟連接。讀取到可執行文件的path。
  2. 再通過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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章