# ptrace ptrace là một công cụ điều khiển luồng thực thi của một tiến trình. Ta có thể xem và thay đổi dữ liệu trên các phân vùng memory, đặc biệt là cho phép ghi luôn phân vùng chỉ đọc (tương tự như thay đổi bằng cách ghi vào `/proc/<pid>/mem`). Ngoài ra ptrace còn cho phép chúng ta xem và thay đổi dữ liệu trên các thanh ghi. > Bạn có biết: ptrace cũng được dùng để debug trong GDB Cấu trúc lệnh: ``` #include <sys/ptrace.h> long ptrace(enum __ptrace_request request, pid_t pid, void *addr, void *data); ``` Có nhiều request khác nhau để tương tác với tracee (chương trình con bị trace) và chúng ta sẽ tìm hiểu một vài request dưới đây để tương tác với chương trình. Các bạn có thể xem khai báo của các request ở [đây](https://elixir.bootlin.com/glibc/latest/source/sysdeps/unix/sysv/linux/x86/sys/ptrace.h) để biết được các request sẽ có mã số là bao nhiêu để code assembly. Giả sử chúng ta build một chương trình như sau: ```c #include <stdio.h> #include <unistd.h> #include <string.h> #include <stdlib.h> char s[0x50] = {0}; int main() { setbuf(stdout, 0); printf("%p\n", s); strcpy(s, "Hello World!"); while (1){ if (!strcmp(s, "Johnathan Huu Tri")) puts("Hello Johnathan Huu Tri"); sleep(1); } } ``` Compile và run file với lệnh sau: ```bash gcc -no-pie run.c -o run && ./run ``` Bây giờ chúng ta cùng xem tiếp cách sử dụng ptrace để khai thác tiến trình này nhé. ## Attach & Detach Để có thể trace một tiến trình, trước tiên ta phải attach tiến trình đó với requests `PTRACE_ATTACH` có số là `16`. Code C: ```c void attach(){ int res; printf("[*] Attaching to pid %d...\n", pid); res = ptrace(PTRACE_ATTACH, pid, 0, 0); if (res<0){ printf("[-] Failed!"); exit(0); } else puts("[+] Succeeded!"); } ``` Code assembly: ```asm section .data pid dd 2771 ; Thực thi `ps aux` để lấy pid section .text global _start _start: mov rax, 0x65 mov rdi, 16 ; Mã số của PTRACE_ATTACH mov rsi, [pid] xor rdx, rdx xor r10, r10 syscall ... ``` Sau khi attach xong, kết quả trả về nên là 0 vì 0 tức là đã attach thành công. Nếu kết quả trả về là -1 thì tức việc ptrace thất bại. Nếu ta ptrace thành công, tracee sẽ dừng lại và ta có thể thay đổi, đọc, ghi dữ liệu lên memory hoặc các thanh ghi tùy ý với các request ở phần kết tiếp. Sau khi ta thay đổi xong và muốn chương trình con tiếp tục thực thi thì ta detach tracee với requests là `PTRACE_DETACH` có mã số `17`: Code C: ```c void detach(){ int res; printf("[*] Detaching from pid %d...\n", pid); res = ptrace(PTRACE_DETACH, pid, 0, 0); if (res<0){ printf("[-] Failed!"); exit(0); } else puts("[+] Succeeded!"); } ``` Code assembly: ```asm section .data pid dd 2771 ; Thực thi `ps aux` để lấy pid section .text global _start _start: ... mov rax, 0x65 mov rdi, 17 ; Mã số của PTRACE_DETACH mov rsi, [pid] xor rdx, rdx xor r10, r10 syscall ``` ## Đọc dữ liệu trên memory Để đọc dữ liệu, ta có thể dùng request `PTRACE_PEEKTEXT` với mã số là `1` để đọc dữ liệu. Ta thấy chương trình có in ra địa chỉ của biến s, vậy ta sẽ đọc dữ liệu của biến s trong trường hợp địa chỉ biến s như sau: ![](https://hackmd.io/_uploads/SJLIp76w3.png) Code C: ```c void read_s(){ char buf[0x50] = {0}; puts("[*] Reading from s..."); ((long int*)buf)[0] = ptrace(PTRACE_PEEKTEXT, pid, 0x404080, 0); ((long int*)buf)[1] = ptrace(PTRACE_PEEKTEXT, pid, 0x404088, 0); printf("Data read: %s\n", buf); } ``` Code assembly (vì là assembly nên argument thứ 4 phải được set thành địa chỉ buf sẽ không bị lỗi): ```asm section .data pid dd 2771 ; Thực thi `ps aux` để lấy pid s dd 0x404080 ; Lấy địa chỉ được in ra section .text global _start _start: ... mov rax, 0x65 mov rdi, 1 ; Mã số của PTRACE_PEEKTEXT mov rsi, [pid] mov rdx, [s] mov r10, rsp syscall mov rax, 0x65 mov rdi, 1 ; Mã số của PTRACE_PEEKTEXT mov rsi, [pid] mov rdx, [s] add rdx, 8 lea r10, [rsp+8] syscall ... ``` Kết quả là mình có chuỗi `Hello World!` trên stack của tracer (kẻ đi trace): ![](https://hackmd.io/_uploads/Bkp6M4aDn.png) ## Ghi dữ liệu trên memory Để ghi dữ liệu vào trong vùng nhớ của tracee, ta có thể dùng request `PTRACE_POKETEXT` có mã số `4`. Giả sử ta muốn thay chuỗi `Hello World!` thành `Johnathan Huu Tri` thì ta có thể thực hiện như sau: Code C: ```c void write_s(){ char buf[0x50] = {0}; char *msg = "Johnathan Huu Tri"; long int *msg_long; puts("[*] Writing to s..."); msg_long = &msg[0]; ptrace(PTRACE_POKETEXT, pid, 0x404080, *msg_long); msg_long = &msg[8]; ptrace(PTRACE_POKETEXT, pid, 0x404088, *msg_long); msg_long = &msg[16]; ptrace(PTRACE_POKETEXT, pid, 0x404090, *msg_long); } ``` Code assembly: ```asm section .data pid dd 5079 ; Thực thi `ps aux` để lấy pid s dd 0x404080 ; Lấy địa chỉ được in ra section .text global _start _start: ... mov rax, 0x65 mov rdi, 4 ; Mã số của PTRACE_POKETEXT mov rsi, [pid] mov rdx, [s] mov r10, 'Johnatha' syscall mov rax, 0x65 mov rdi, 4 ; Mã số của PTRACE_POKETEXT mov rsi, [pid] mov rdx, [s] add rdx, 8 mov r10, 'n Huu Tr' syscall mov rax, 0x65 mov rdi, 4 ; Mã số của PTRACE_POKETEXT mov rsi, [pid] mov rdx, [s] add rdx, 0x10 mov r10, 'i' syscall ... ``` Kết quả là ta thay đổi được dữ liệu trong biến s: ![](https://hackmd.io/_uploads/SJgRbiEaDn.png) ## Đọc dữ liệu trên thanh ghi Để đọc từ thanh ghi của tracee, ta sẽ dùng requests `PTRACE_GETREGS` có mã số 12 và ta sẽ dùng struct `user_regs_struct` được định nghĩa ở [đây](https://elixir.bootlin.com/linux/v4.7/source/arch/x86/include/asm/user_64.h) để biết được format mà ptrace sẽ dùng để lưu trữ các thanh ghi. Code C: ```c void get_reg(){ struct user_regs_struct regs; puts("[*] Getting registers..."); ptrace(PTRACE_GETREGS, pid, 0, &regs); printf("rax: 0x%02llx\n", regs.rax); printf("rbx: 0x%02llx\n", regs.rbx); printf("rcx: 0x%02llx\n", regs.rcx); printf("rdx: 0x%02llx\n", regs.rdx); printf("rsi: 0x%02llx\n", regs.rsi); printf("rdi: 0x%02llx\n", regs.rdi); printf("rbp: 0x%02llx\n", regs.rbp); printf("rsp: 0x%02llx\n", regs.rsp); printf("rip: 0x%02llx\n", regs.rip); } ``` Code assembly: ```asm section .data pid dd 5398 ; Thực thi `ps aux` để lấy pid section .text global _start _start: ... mov rax, 0x65 mov rdi, 12 ; Mã số của PTRACE_GETREGS mov rsi, [pid] xor rdx, rdx mov r10, rsp syscall ... ``` Kết quả là ta có được dữ liệu các thanh ghi như sau (với dòng highlight là thanh ghi rip): ![](https://hackmd.io/_uploads/rylDIB6vh.png) ## Ghi dữ liệu trên thanh ghi Bây giờ ta muốn chương trình đang từ thực thi sleep thì thực thi luôn lệnh puts để in ra dòng chữ `Hello Johnathan Huu Tri` bằng cách thay đổi thanh ghi rip thành địa chỉ `0x0000000000401223` như phần dissassembly main bên dưới: ![](https://hackmd.io/_uploads/ByC1vHavh.png) Ta sẽ lấy các thanh ghi ra trước xong sau đó chỉ cần thay đổi rip thồi là được. Cuối cùng ta dùng requests `PTRACE_SETREGS` có mã số 13 để đưa thanh ghi vào tracee và detach để chương trình tiếp tục thực thi. Code C: ```c void set_reg(){ struct user_regs_struct regs; puts("[*] Getting registers..."); ptrace(PTRACE_GETREGS, pid, 0, &regs); regs.rip = 0x0000000000401223; puts("[*] Setting registers..."); ptrace(PTRACE_SETREGS, pid, 0, &regs); } ``` Code assembly: ```asm section .data pid dd 6104 ; Thực thi `ps aux` để lấy pid section .text global _start _start: ... mov rax, 0x65 mov rdi, 12 ; Mã số của PTRACE_GETREGS mov rsi, [pid] xor rdx, rdx mov r10, rsp syscall mov rax, 0x401223 mov [rsp+0x80], rax ; rsp+0x80 là rip mov rax, 0x65 mov rdi, 13 ; Mã số của PTRACE_SETREGS mov rsi, [pid] xor rdx, rdx mov r10, rsp syscall ... ``` Kết quả là ta in ra được chuỗi đó: ![](https://hackmd.io/_uploads/H1gOdr6w2.png)