<think>我们正在讨论Linuxptrace系统调用的使用教程和示例代码。ptrace是一个强大的系统调用,主要用于进程跟踪和调试,允许一个进程(tracer)观察和控制另一个进程(tracee)的执行,包括读写其内存和寄存器等。根据引用[3],ptrace的函数原型为:```c#include<sys/ptrace.h>longptrace(enum__ptrace_requestrequest,pid_tpid,void*addr,void*data);```其中,`request`参数指定要执行的操作,`pid`是目标进程的ID,`addr`和`data`的含义取决于`request`。引用[4]提到,ptrace可以用于拦截系统调用、修改系统调用参数、实现调试器的功能等。下面我们通过几个示例来展示ptrace的基本用法。###示例1:跟踪子进程执行以下示例展示如何使用ptrace跟踪子进程,并在子进程执行系统调用时中断。```c#include<stdio.h>#include<sys/ptrace.h>#include<sys/types.h>#include<sys/wait.h>#include<unistd.h>#include<sys/user.h>intmain(){pid_tchild;child=fork();//创建子进程if(child==0){//子进程:请求被跟踪ptrace(PTRACE_TRACEME,0,NULL,NULL);execl("/bin/ls","ls",NULL);//执行ls命令}else{//父进程:跟踪子进程intstatus;wait(&status);//等待子进程状态改变while(WIFSTOPPED(status)){//当子进程被停止时structuser_regs_structregs;//获取寄存器状态ptrace(PTRACE_GETREGS,child,NULL,®s);//在x86_64架构下,系统调用号存放在orig_rax寄存器中printf("系统调用号:%lld\n",regs.orig_rax);//继续执行,并在下一次系统调用入口停止ptrace(PTRACE_SYSCALL,child,NULL,NULL);wait(&status);//等待子进程状态改变}}return0;}```在这个例子中,子进程通过`PTRACE_TRACEME`请求被父进程跟踪。父进程使用`PTRACE_SYSCALL`让子进程继续运行,并在每次进入和退出系统调用时停止。然后父进程通过`PTRACE_GETREGS`读取子进程的寄存器,获取系统调用号。###示例2:附加到运行中的进程ptrace还可以附加到一个已经运行的进程上。以下示例展示如何附加到一个进程并读取其内存。```c#include<stdio.h>#include<sys/ptrace.h>#include<sys/types.h>#include<sys/wait.h>#include<unistd.h>#include<sys/user.h>intmain(intargc,char*argv[]){if(argc!=2){printf("用法:%s<进程ID>\n",argv[0]);return1;}pid_tpid=atoi(argv[1]);//目标进程IDlongret=ptrace(PTRACE_ATTACH,pid,NULL,NULL);if(ret==-1){perror("ptraceattach失败");return1;}intstatus;waitpid(pid,&status,0);//等待目标进程停止//读取目标进程的内存longword=ptrace(PTRACE_PEEKTEXT,pid,(void*)0x400000,NULL);printf("地址0x400000处的值:%lx\n",word);//分离目标进程ptrace(PTRACE_DETACH,pid,NULL,NULL);return0;}```这个例子中,我们使用`PTRACE_ATTACH`附加到指定进程,然后使用`PTRACE_PEEKTEXT`读取目标进程内存中指定地址的值,最后使用`PTRACE_DETACH`分离。###示例3:代码注入引用[5]提到了使用ptrace进行代码注入。以下是一个简化的代码注入示例,将一段代码(payload)注入到目标进程中执行。```c#include<stdio.h>#include<stdlib.h>#include<string.h>#include<sys/ptrace.h>#include<sys/types.h>#include<sys/wait.h>#include<unistd.h>#include<sys/user.h>#include<sys/mman.h>#include<dlfcn.h>//用于注入的shellcodeconstcharshellcode[]="\x48\x31\xff\xb0\x3c\x48\x31\xf6\x48\x31\xd2\x0f\x05";//退出系统调用的shellcode:exit(0)intmain(intargc,char*argv[]){if(argc!=2){fprintf(stderr,"用法:%s<进程ID>\n",argv[0]);return1;}pid_tpid=atoi(argv[1]);//附加到目标进程if(ptrace(PTRACE_ATTACH,pid,NULL,NULL)==-1){perror("附加失败");return1;}intstatus;waitpid(pid,&status,0);//等待目标进程停止//获取目标进程的寄存器structuser_regs_structregs;ptrace(PTRACE_GETREGS,pid,NULL,®s);//在目标进程中分配内存void*remote_mem=(void*)ptrace(PTRACE_PEEKTEXT,pid,(void*)regs.rip,NULL);//实际上,分配内存需要使用目标进程调用mmap,这里简化处理,假设已经有一块可写内存//更完整的做法是调用目标进程的mmap函数,但比较复杂,这里不展开。//将shellcode写入目标进程的内存for(inti=0;i<sizeof(shellcode);i++){//每次写入一个字节longdata=ptrace(PTRACE_PEEKDATA,pid,remote_mem+i,NULL);//修改低字节为shellcode[i]data=(data&~0xFF)|shellcode[i];ptrace(PTRACE_POKEDATA,pid,remote_mem+i,data);}//修改目标进程的指令指针指向注入的代码regs.rip=(unsignedlonglong)remote_mem;ptrace(PTRACE_SETREGS,pid,NULL,®s);//继续执行目标进程,执行我们的shellcodeptrace(PTRACE_DETACH,pid,NULL,NULL);return0;}```注意:这个代码注入示例非常简化,实际应用中需要考虑更多因素,比如内存权限、避免破坏原有进程等。完整的代码注入通常需要调用目标进程的mmap来分配内存,并设置内存为可执行。###使用注意事项1.权限:需要具有相应权限(通常是root或目标进程的所有者)才能使用ptrace。2.多线程:跟踪多线程进程时,ptrace会跟踪所有线程,但需要分别处理每个线程。3.安全性:ptrace功能强大,但使用不当可能导致目标进程崩溃。以上示例和说明可以帮助你开始使用ptrace。更多详细内容可以参考Linux的man手册(`manptrace`)以及相关的项目示例(如引用[1][2][5]中的项目)。§§相关问题§§1.ptrace如何用于实现一个简单的调试器?2.如何用ptrace修改目标进程的系统调用参数?3.ptrace在安全领域有哪些应用?4.ptrace和strace、gdb等工具有什么关系?</think>### Linux ptrace 使用教程与示例代码
`ptrace` 是 Linux 的核心调试工具,允许一个进程(tracer)监控和控制另一个进程(tracee)的执行。它广泛应用于调试器、代码注入、系统调用拦截等场景[^3][^4]。
#### 核心功能
```c
#include <sys/ptrace.h>
long ptrace(enum __ptrace_request request, pid_t pid, void *addr, void *data);
```
- **request**:操作类型(如 `PTRACE_TRACEME`, `PTRACE_ATTACH`)
- **pid**:目标进程ID
- **addr/data**:操作参数(如内存地址/寄存器值)
---
### 基础使用示例
#### 示例 1:跟踪子进程(基础调试)
```c
#include <sys/ptrace.h>
#include <sys/wait.h>
#include <unistd.h>
int main() {
pid_t child = fork();
if (child == 0) {
ptrace(PTRACE_TRACEME, 0, NULL, NULL); // 子进程请求被跟踪
execl("/bin/ls", "ls", NULL); // 执行目标程序
} else {
wait(NULL); // 等待子进程停止
ptrace(PTRACE_CONT, child, NULL, NULL); // 继续执行子进程
wait(NULL); // 等待子进程结束
}
return 0;
}
```
**功能**:父进程跟踪子进程执行 `ls` 命令[^3][^4]。
---
#### 示例 2:拦截系统调用
```c
#include <sys/ptrace.h>
#include <sys/user.h>
// ...
void trace_syscalls(pid_t child) {
while (1) {
// 在系统调用入口暂停
ptrace(PTRACE_SYSCALL, child, NULL, NULL);
wait(NULL);
struct user_regs_struct regs;
ptrace(PTRACE_GETREGS, child, NULL, ®s);
printf("系统调用号: %llu\n", regs.orig_rax); // 打印系统调用号
// 在系统调用退出暂停
ptrace(PTRACE_SYSCALL, child, NULL, NULL);
wait(NULL);
}
}
```
**功能**:捕获并打印所有系统调用(类似 `strace` 基础功能)[^4]。
---
### 进阶应用:代码注入
#### 示例 3:注入 Shellcode 到运行进程
```c
#include <sys/mman.h>
// ...
void inject_code(pid_t target_pid, void *shellcode, size_t size) {
// 附加到目标进程
ptrace(PTRACE_ATTACH, target_pid, NULL, NULL);
wait(NULL);
// 在目标进程分配内存
long remote_addr = ptrace(PTRACE_PEEKTEXT, target_pid,
mmap(NULL, size, PROT_READ|PROT_WRITE|PROT_EXEC,
MAP_PRIVATE|MAP_ANONYMOUS, -1, 0), NULL);
// 写入 Shellcode
for (int i = 0; i < size; i += sizeof(long)) {
long data = *(long *)((char *)shellcode + i);
ptrace(PTRACE_POKETEXT, target_pid, remote_addr + i, data);
}
// 修改指令指针执行 Shellcode
struct user_regs_struct regs;
ptrace(PTRACE_GETREGS, target_pid, NULL, ®s);
regs.rip = remote_addr; // x86_64 指令指针
ptrace(PTRACE_SETREGS, target_pid, NULL, ®s);
ptrace(PTRACE_DETACH, target_pid, NULL, NULL);
}
```
**关键步骤**:
1. 附加目标进程
2. 分配可执行内存
3. 写入机器码
4. 劫持控制流
5. 恢复执行[^5]
> **安全警告**:代码注入可能破坏进程稳定性,仅用于学习目的。
---
### 实用技巧
1. **权限要求**:
- `ptrace` 需要 `CAP_SYS_PTRACE` 能力或目标进程属主身份
- `/proc/sys/kernel/yama/ptrace_scope` 控制权限级别
2. **调试多线程**:
- 使用 `PTRACE_ATTACH` 附加线程需指定线程ID
- `PTRACE_SETOPTIONS` 设置 `PTRACE_O_TRACECLONE` 跟踪子线程
3. **错误处理**:
```c
if (ptrace(PTRACE_ATTACH, pid, NULL, NULL) == -1) {
perror("ptrace attach failed");
exit(EXIT_FAILURE);
}
```
---
### 学习资源推荐
1. **官方文档**:
- `man 2 ptrace`(Linux 手册页)
- [Linux 内核源码](https://elixir.bootlin.com/linux/latest/source/kernel/ptrace.c)
2. **实战项目**:
- [ptrace-examples](https://gitcode.com/gh_mirrors/pt/ptrace-examples):基础操作示例[^1]
- [ptrace-call-userspace](https://gitcode.com/gh_mirrors/pt/ptrace-call-userspace):用户态函数调用[^2]
3. **调试工具源码**:
- `strace`(系统调用跟踪)
- `gdb`(GNU 调试器)