linux ptrace,Linux ptrace 简介

本文介绍了Linux内核的ptrace系统调用,用于进程间调试和系统调用拦截。通过示例代码展示了如何使用ptrace跟踪子进程,检查和修改其内存及寄存器状态,特别是在拦截和修改write系统调用参数方面的应用。文章还探讨了ptrace的工作原理,包括进程状态切换和参数获取,并提供了实际的代码实现。
摘要由CSDN通过智能技术生成

Linux ptrace 简介

2017-06-15 Thursday

ptrace() 是一个由 Linux 内核提供的系统调用,允许一个用户态进程检查、修改另一个进程的内存和寄存器,通常用在类似 gdb、strace 的调试器中,用来实现断点调试、系统调用的跟踪。

你想过怎么实现对系统调用的拦截吗?你尝试过通过改变系统调用的参数来愚弄你的系统 kernel 吗?你想过调试器是如何使运行中的进程暂停并且控制它吗?

这里简单介绍如何使用该接口。

简介

在执行系统调用之前,内核会先检查当前进程是否处于被 “跟踪” traced 状态,如果是,内核暂停当前进程并将控制权交给跟踪进程,使跟踪进程得以察看或者修改被跟踪进程的寄存器。

其中函数的声明如下:

#include

long ptrace(enum __ptrace_request request, pid_t pid, void *addr, void *data);

ptrace有四个参数:

1). enum __ptrace_request request:指示了ptrace要执行的命令。

2). pid_t pid: 指示ptrace要跟踪的进程。

3). void *addr: 指示要监控的内存地址。

4). void *data: 存放读取出的或者要写入的数据。

如下是一个示例,父进程会 fork 出了一个子进程,然后跟踪它。

#include

#include

#include

#include

#include

#include

#include

#define log_info(...) do { printf("info : " __VA_ARGS__); putchar('\n'); } while(0);

#define log_error(...) do { printf("error: " __VA_ARGS__); putchar('\n'); } while(0);

int main(void)

{

pid_t pid;

int status, insyscall = 0;

long orax, rax, params[3];

struct user_regs_struct regs;

pid = fork();

if (pid < 0) {

log_error("fork failed, %s.", strerror(errno));

exit(EXIT_FAILURE); /* 1 */

} else if (pid == 0){

ptrace(PTRACE_TRACEME, 0, NULL, NULL);

execlp("/usr/bin/ls", "ls", NULL);

exit(EXIT_SUCCESS);

}

log_info("current pid %d, child pid %d.", getpid(), pid);

while (1) {

wait(&status);

//wait4(pid, &sta, 0, &ru);

if (WIFEXITED(status)) {

log_info("child exited with %d.", status);

break;

}

orax = ptrace(PTRACE_PEEKUSER, pid, 8 * ORIG_RAX, NULL);

if (orax == SYS_write) {

if (insyscall == 0) {

insyscall = 1;

params[0] = ptrace(PTRACE_PEEKUSER, pid, 8 * RDI, NULL);

params[1] = ptrace(PTRACE_PEEKUSER, pid, 8 * RSI, NULL);

params[2] = ptrace(PTRACE_PEEKUSER, pid, 8 * RDX, NULL);

log_info("write called with %ld, %ld, %ld.",

params[0], params[1], params[2]);

ptrace(PTRACE_GETREGS, pid, NULL, &regs);

log_info("write called with %lld, %lld, %lld.",

regs.rdi, regs.rsi, regs.rdx);

} else {

insyscall = 0;

rax = ptrace(PTRACE_PEEKUSER, pid, 8 * RAX, NULL);

log_info("write returned with %ld.", rax);

}

}

ptrace(PTRACE_SYSCALL, pid, NULL, NULL);

}

return 0;

}

进程状态切换

在子进程中调用 exec 函数之前,首先通过 PTRACE_TRACEME 参数告知内核允许等待其它进程跟踪,那么在执行到 execve() 函数时会将控制权交换给父进程。

此时父进程在使用 wait() 函数来等待来自内核的通知,现在它得到了通知,那么接下看可以开始察看子进程都作了些什么,比如看看寄存器的值之类。

查看状态

在进入系统调用时,内核会将 eax 设置为系统调用号,那么此时可以使用 PTRACE_PEEKUSER 获取对应的参数,然后再通过PTRACE_CONT 使子进程继续运行。

其中实际的系统调用号可以在头文件 asm/unistd_64.h 中查看;在使用 PTRACE_PEEKUSER 时入参通过 ORIG_RAX 查看,而返回值通过 RAX 查看。

PTRACE_SYSCALL 类似于 PTRACE_CONT ,不过在接下来系统调用的入参或者出参时仍然会阻塞。

示例 2 获取一个具体系统调用的参数,实际上也就是如何获取寄存器中的值,这里采用了两种方式,一种与上述获取系统调用号方式一致,另外一种是通过一次调用直接获取。

注意,不同的系统调用获取参数的方式也有所区别,详细可以查看 man 2 syscall 中的 Architecture calling conventions 内容。

例如,对于 x86_64 中的 write() 来说,其声明如下:

ssize_t write(int fd, const void *buf, size_t count);

那么对应的系统调用号为 rax ,三个入参 fd buf count 分别对应了寄存器 rdi rsi rdx 三个。

示例

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#define log_info(...) do { printf("info : " __VA_ARGS__); putchar('\n'); } while(0);

#define log_error(...) do { printf("error: " __VA_ARGS__); putchar('\n'); } while(0);

void reverse(pid_t pid, long addr, int len)

{

char *buff, tmp;

long i, j, value, offset;

union {

long value;

char chars[sizeof(long)];

} data;

buff = (char *)malloc(len + 1);

if (buff == NULL)

exit(1);

for (i = 0, offset = 0; i < len / (long)sizeof(long); i++, offset += sizeof(long)) {

value = ptrace(PTRACE_PEEKDATA, pid, addr + offset, NULL);

memcpy(buff + offset, &value, sizeof(long));

}

if (len % sizeof(long)) {

value = ptrace(PTRACE_PEEKDATA, pid, addr + offset, NULL);

memcpy(buff + offset, &value, len % sizeof(long));

}

buff[len] = 0;

for(i = 0, j = len - 2; i <= j; ++i, --j) {

tmp = buff[i];

buff[i] = buff[j];

buff[j] = tmp;

}

for (i = 0, offset = 0; i < len / (long)sizeof(long); i++, offset += sizeof(long)) {

memcpy(data.chars, buff + offset, sizeof(long));

ptrace(PTRACE_POKEDATA, pid, addr + offset, data.value);

}

if (len % sizeof(long)) {

memcpy(data.chars, buff + offset, sizeof(long));

ptrace(PTRACE_POKEDATA, pid, addr + offset, data.value);

}

free(buff);

}

int main()

{

pid_t pid;

int status, insyscall = 0;

long orax, rax, params[3];

pid = fork();

if (pid < 0) {

log_error("fork failed, %s.", strerror(errno));

exit(EXIT_FAILURE); /* 1 */

} else if (pid == 0){

ptrace(PTRACE_TRACEME, 0, NULL, NULL);

execlp("/usr/bin/ls", "ls", NULL);

exit(EXIT_SUCCESS);

}

log_info("current pid %d, child pid %d.", getpid(), pid);

while (1) {

wait(&status);

//wait4(pid, &sta, 0, &ru);

if (WIFEXITED(status)) {

log_info("child exited with %d.", status);

break;

}

orax = ptrace(PTRACE_PEEKUSER, pid, 8 * ORIG_RAX, NULL);

if (orax == SYS_write) {

if (insyscall == 0) {

insyscall = 1;

params[0] = ptrace(PTRACE_PEEKUSER, pid, 8 * RDI, NULL); /* fd */

params[1] = ptrace(PTRACE_PEEKUSER, pid, 8 * RSI, NULL); /* buff */

params[2] = ptrace(PTRACE_PEEKUSER, pid, 8 * RDX, NULL); /* count */

log_info("write called with %ld, %ld, %ld.",

params[0], params[1], params[2]);

reverse(pid, params[1], params[2]);

} else {

insyscall = 0;

rax = ptrace(PTRACE_PEEKUSER, pid, 8 * RAX, NULL);

log_info("write returned with %ld.", rax);

}

}

ptrace(PTRACE_SYSCALL, pid, NULL, NULL);

}

return 0;

}

如果喜欢这里的文章,而且又不差钱的话,欢迎打赏个早餐 ^_^

支付宝打赏

微信打赏

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值