Linux 系统调用 - ptrace
ptrace 是 Linux 中的一个系统调用,可以让父进程控制子进程运行,并可以检查和改变子进程的核心 image 的功能
其基本原理是:
- 当使用了 ptrace 跟踪后,所有发送给被跟踪的子进程的信号(除了SIGKILL),都会被转发给父进程
- 子进程会被阻塞,这时子进程的状态就会被系统标注为 TASK_TRACED
- 父进程收到信号后,就可以对停止下来的子进程进行检查和修改,然后让子进程继续运行
ptrace 在用户态的定义如下:
1 | long int |
- 对于不同的 ptrace 命令有着不同的参数,简化版本如下:
1 |
|
- enum __ptrace_request request:指示了 ptrace 要执行的命令
- pid_t pid:指示 ptrace 要跟踪的进程ID
- void *addr:指示要监控的内存地址
- void *data:存放读取出的或者要写入的数据
ptrace 在内核中的接口如下:
1 | SYSCALL_DEFINE4(ptrace, long, request, long, pid, unsigned long, addr, |
- 其中对 PTRACE_TRACEME 和 PTRACE_ATTACH 做了特殊处理
常见 ptrace 命令如下:
1 | /* Type of the REQUEST argument to `ptrace.' */ |
ptrace 的使用 - 调试
Linux 调试工具 GDB 的底层就是使用了 ptrace,主要是 PTRACE_ATTACH 功能
在使用 ptrace 之前需要在两个进程间建立追踪关系:(追踪者 tracer 和被追踪者 tracee)
- ptrace 编程的主要部分是 tracer,它可以通过附着的方式与 tracee 建立追踪关系
- 建立之后,可以控制 tracee 在特定的时候暂停并向 tracer 发送相应信号,而 tracer 则通过循环等待 waitpid 来处理 tracee 发来的信号
其中会用到4个 ptrace 命令:
- PTRACE_TRACEME:tracee 表明自己想要被追踪,这会自动与父进程建立追踪关系(这也是唯一能被 tracee 使用的 request,其他的 request 都由 tracer 指定)
- PTRACE_PEEKUSER:返回进程用户区域中,偏移为ADDR处,一字大小的数据,使用
ORIG_RAX
计算RAX
的偏移,从而获取将要执行的系统调用号 - PTRACE_GETREGS:获取寄存器内容,用结构体
user_regs_struct
进行保存 - PTRACE_SYSCALL:在子进程进入和退出系统调用时都将其暂停
使用 ptrace 的编程案例如下:
1 |
|
被测试的文件:
1 | int main(){ |
结果:
1 | ➜ exp ./test |
- 可以看见 tracee 从调用
execve-59
构建进程上下文到调用write-1
的全过程
ptrace 的使用 - 反调试
ptrace 反调试的核心就在于主动执行 PTRACE_TRACEME:
- 如果当前进程已经被追踪了,就不能被其他父进程追踪
- 因此可以提前执行 PTRACE_TRACEME 命令来避免被 GDB 调试
测试案例如下:
1 |
|
正常执行结果:
1 | ➜ exp ./test |
调试结果:
1 | c |
绕过的方式很简单,只要把相关的地方给 nop 掉就好:
可以直接查看符号表来确定是否存在 prtace:
1 | ➜ exp objdump -t test | grep ptrace |
ptrace 的使用 - 代码注入
ptrace 可以对进程的追踪,并进行流程控制:
- 用户寄存器值读取和写入操作
- 内存进行读取和修改
需要用到的 ptrace 命令如下:
- PTRACE_POKETEXT,PTRACE_POKEDATA:往内存地址中写入一个字节,内存地址由 addr 给出
- PTRACE_PEEKTEXT,PTRACE_PEEKDATA:从内存地址中读取一个字节,内存地址由 addr 给出
- PTRACE_ATTACH:跟踪指定 pid 进程
- PTRACE_GETREGS:读取所有寄存器的值
- PTRACE_CONT:继续执行被跟踪的子进程,signal 为“0”则忽略引起调试进程中止的信号,若不为“0”则继续处理信号 signal
- PTRACE_SETREGS:设置寄存器
- PTRACE_DETACH:结束跟踪
下面程序用于在 tracee 中注入一段 shellcode:
1 |
|
其中的 shellcode 就是 execve(/bin/sh)
,汇编如下:
1 | section .text |
- 进行编译:
1 | ➜ exp nasm -f elf64 sh.s -o sh.o |
- 显示二进制代码:
1 | ➜ exp objdump --disassemble ./sh |
测试文件如下:
1 |
|