@oro-oro
2015-09-17T18:04:53.000000Z
字数 9695
阅读 2596
DroidHOOK
目的:初步了解 Linux 下的 ptrace
平台:虚拟机 Ubuntu32
ptrace - process trace
#include <sys/ptrace.h>
long ptrace(enum __ptrace_request request, pid_t pid, void *addr, void *data);
// request,表示请求的操作
// pid,表示请求的目标进程
// addr,内存地址
// data,操作的数据
ptrace() 提供了跟踪和调试的功能。它允许一个进程(跟踪进程tracer)去控制另外一个进程(被跟踪进程tracee)。
tracer可以观察和控制tracee的运行,可以查看和改变tracee的内存和寄存器。它主要用来实现断点调试和系统调用跟踪。
tracer如果要跟踪一个tracee,首先得附上(attach)tracee。
注:tracee不能同时被2个tracer附加,第二个tracer附加会失败。
当 tracee 被附加上后,它会每收到一个信号都会停止一次,即使这个信号将会被忽略(除了 SIGKILL,这个信号通常都有效)。此时 tracer 可使用 wait/waitpid (这个调用会返回一个状态值,表示 tracee 停止的原因)或发送一个 SIGCHLD 信号,检查停止状态的 tracee,去分析和修改 tracee;根据情况终止它或者让 tracee 继续运行,可选地忽略已发送的信号(或者甚至发送一个不同的信号替换掉)。当 tracer 完成跟踪,可以通过 PTRACE_DETACH 让 tracee 回到一般、非跟踪模式下继续运行。
附上一个进程有两种方法:
tracer使用PTRACE_ATTACH或PTRACE_SEIZE直接附上正在运行的tracee,用PTRACE_DETACH可退出调试。注意:这种情况下,tracer需要root权限!
例子:让一个死循环跳转到一个fun函数里面,不断打印 hello。
IP(instruction pointer)指令指针寄存器。存放当前指令的下一条指令,CPU 将要执行的下一条指令。
EIP 是32位,RIP是64位。
获取当前的 eip,然后,修改eip为fun函数的地址,则tracee执行下一条指令fun了。
https://git.oschina.net/acgmohu/PtraceX86Examples/blob/master/tracee3.c
#include <stdio.h> /* printf */
#include <unistd.h> /* sleep */
int fun()
{
int i = 0;
while(i < 10) {
printf("hello\n");
sleep(1);
i++;
}
return 0;
}
void main()
{
while(1);
}
https://git.oschina.net/acgmohu/PtraceX86Examples/blob/master/tracer3.c
/**
* This program need root, run with sudo.
*/
#include <stdio.h> /* scanf */
#include <sys/user.h> /* user_regs_struct */
#include <sys/ptrace.h> /* ptrace */
#include <sys/types.h> /* wait */
#include <sys/wait.h> /* wait */
#include <unistd.h> /* sleep */
void main()
{
int status = 0, pid;
int addr = 0x0804844d; // tracee fun address
struct user_regs_struct uregs;
printf("pid : ");
scanf("%d", &pid);
ptrace(PTRACE_ATTACH, pid, 0, 0);
wait(&status);
if(WIFSTOPPED(status))
printf("tracee has stopped...\n");
ptrace(PTRACE_GETREGS, pid, 0, &uregs);
printf("eip: %x\n", uregs.eip);
uregs.eip = addr;
printf("eip: %x\n", uregs.eip);
ptrace(PTRACE_SETREGS, pid, 0, &uregs);
ptrace(PTRACE_DETACH, pid, 0, 0);
printf("tracer detach\n");
return;
}
tracer使用fork获得它的子进程,子进程执行PTRACE_TRACEME,再执行execve启动要跟踪的程序。
例子1:PTRACE_TRACEME 在 tracee 中:
https://git.oschina.net/acgmohu/PtraceX86Examples/blob/master/tracee1.c
#include <sys/ptrace.h> /* ptrace */
#include <stdio.h> /* printf */
#include <unistd.h> /* sleep */
int main()
{
ptrace(PTRACE_TRACEME, 0, 0, 0);
int i = 0;
while(i < 10) {
printf("hello %d.\n", i++);
sleep(1);
}
return 0;
}
https://git.oschina.net/acgmohu/PtraceX86Examples/blob/master/tracer1.c
/* `tracer' parent */
#include <unistd.h> /* fork, execl */
#include <stdlib.h> /* exit */
#include <sys/types.h> /* kill, wait */
#include <signal.h> /* kill */
#include <sys/wait.h> /* wait */
#include <sys/ptrace.h> /* ptrace */
#include <stdio.h> /* printf */
int main()
{
int pid, status;
if((pid = fork()) == 0) {
execl("./tracee1", "tracee1", 0, NULL);
printf("exec failed...\n");
exit(0);
} else {
sleep(1);
printf("send a signal SIGINT to interrupt tracee and waiting ...\n");
kill(pid, SIGINT);
wait(&status);
if(WIFSTOPPED(status))
printf("tracee has stopped...\n");
printf("tracer sleep 5 second ... \n");
sleep(5);
printf("ptrace continue ... \n");
ptrace(PTRACE_CONT, pid, 0, 0);
/* Will not come out of this wait because
* child does not terminate.
*/
wait(&status);
if(WIFEXITED(status))
printf("tracee has exited.\n");
}
printf("tracer exit.\n");
}
例子2:PTRACE_TRACEME 在 tracer 中:
https://git.oschina.net/acgmohu/PtraceX86Examples/blob/master/tracee2.c
#include <sys/ptrace.h> /* ptrace */
#include <stdio.h> /* printf */
#include <unistd.h> /* sleep */
int main()
{
int i = 0;
while(i < 10) {
printf("hello %d.\n", i++);
sleep(1);
}
return 0;
}
https://git.oschina.net/acgmohu/PtraceX86Examples/blob/master/tracer2.c
/* `tracer' parent */
#include <unistd.h> /* fork, execl */
#include <stdlib.h> /* exit */
#include <sys/types.h> /* kill, wait */
#include <signal.h> /* kill */
#include <sys/wait.h> /* wait */
#include <sys/ptrace.h> /* ptrace */
#include <stdio.h> /* printf */
int main()
{
int pid, status;
if((pid = fork()) == 0) {
ptrace(PTRACE_TRACEME, pid, 0, 0);
execl("./tracee2", "tracee2", 0, NULL);
printf("exec failed...\n");
exit(0);
} else {
wait(&status);
if(WIFSTOPPED(status))
printf("tracee has stopped...\n");
printf("tracer sleep 5 second ... \n");
sleep(5);
printf("ptrace continue ... \n");
ptrace(PTRACE_CONT, pid, 0, 0);
/* Will not come out of this wait because
* child does not terminate.
*/
wait(&status);
if(WIFEXITED(status))
printf("tracee has exited.\n");
}
printf("tracer exit.\n");
}
获取和修改寄存器。
PTRACE_GETREGS, PTRACE_GETFPREGS
Copy the tracee's general-purpose or floating-point registers, respectively, to the address data in the tracer. See <sys/user.h> for information on the format of this data. (addr is ignored.) Note that SPARC systems have the meaning of data and addr reversed; that is, data is ignored and the registers are copied to the address addr. PTRACE_GETREGS and PTRACE_GETFPREGS are not present on all architectures.
PTRACE_SETREGS, PTRACE_SETFPREGS
Modify the tracee's general-purpose or floating-point registers, respectively, from the address data in the tracer. As for PTRACE_POKEUSER, some general-purpose register modifications may be disallowed. (addr is ignored.) Note that SPARC systems have the meaning of data and addr reversed; that is, data is ignored and the registers are copied from the address addr. PTRACE_SETREGS and PTRACE_SETFPREGS are not present on all architectures.
子进程是一个死循环,如果如果ebx 等于 245,则退出循环。
tracer 停止 tracee,获取 ebx 的值,并将这个值修改为 245,使 tracee 退出循环。
https://git.oschina.net/acgmohu/PtraceX86Examples/blob/master/tracer4.c
#include <sys/ptrace.h>
#include <signal.h>
#include <stdio.h>
#include <sys/user.h>
#include <sys/types.h>
#include <sys/wait.h>
void main()
{
int i = 0, status = 0, pid;
struct user_regs_struct uregs;
if ((pid=fork())==0) {
ptrace(PTRACE_TRACEME, 0, 0, 0);
execl("./tracee4", "tracee4", 0, NULL);
printf("execl error...\n");
} else {
wait(&status);
ptrace(PTRACE_CONT, pid, 0, 0);
sleep(1);
//child should have got into the loop by
//this time!
kill(pid, SIGINT);
wait(&status);
ptrace(PTRACE_GETREGS, pid, 0, &uregs);
printf("ebx = %d\n", uregs.ebx);
uregs.ebx = 245;
ptrace(PTRACE_SETREGS, pid, 0, &uregs);
ptrace(PTRACE_CONT, pid, 0, 0);
wait(&status);
if (WIFEXITED(status)) {
printf("tracee has exited.\n");
}
printf("tracer exits.\n");
}
}
https://git.oschina.net/acgmohu/PtraceX86Examples/blob/master/tracee4.c
#include <stdio.h>
void main()
{
printf("tracee starts...\n");
asm("pushl %ebx\n\t"
"movl $143, %ebx\n\t"
"L1: cmpl $245, %ebx\n\t"
"jne L1\n\t"
"popl %ebx\n\t");
printf("tracee outside loop...\n");
}
读写数据段数据。
PTRACE_PEEKTEXT, PTRACE_PEEKDATA
Read a word at the address addr in the tracee's memory, returning the word as the result of the ptrace() call. Linux does not have separate text and data address spaces, so these two requests are currently equivalent. (data is ignored.)
PTRACE_POKETEXT, PTRACE_POKEDATA
Copy the word data to the address addr in the tracee's memory. As for PTRACE_PEEKTEXT and PTRACE_PEEKDATA, these two requests are currently equivalent.
例子:
tracee5 中,是个死循环,只有 i 等于 245才能退出循环。
tracer5 将tracee5 中的 i 的数据修改为 245,使之退出循环。
使用 nm tracee5,可以查看 tracee5 中参数 i 的数据(objdump,readelf也可以)。
https://git.oschina.net/acgmohu/PtraceX86Examples/blob/master/tracee5.c
#include <stdio.h>
int i = 143;
void main()
{
printf("tracee starts...\n");
while(i != 245);
printf("tracee outside loop...\n");
}
https://git.oschina.net/acgmohu/PtraceX86Examples/blob/master/tracer5.c
#include <sys/ptrace.h>
#include <signal.h>
#include <stdio.h>
#include <sys/user.h>
#include <sys/types.h>
#include <sys/wait.h>
void main()
{
int i = 0, status = 0, pid;
struct user_regs_struct uregs;
int data;
int addr = 0x0804a020;
if ((pid=fork())==0) {
ptrace(PTRACE_TRACEME, 0, 0, 0);
execl("./tracee5", "tracee5", 0, NULL);
printf("execl error...\n");
} else {
wait(&status);
ptrace(PTRACE_CONT, pid, 0, 0);
sleep(1);
//child should have got into the loop by
//this time!
kill(pid, SIGINT);
wait(&status);
//segment of code which alters value of
//a variable in a child process. Identical
//to code which reads and changes register value.
//`addr' is address of variable being accessed.
data = ptrace(PTRACE_PEEKDATA, pid, addr, 0);
printf("data = %d\n", data);
data = 245;
ptrace(PTRACE_POKEDATA, pid, addr, data);
ptrace(PTRACE_CONT, pid, 0, 0);
wait(&status);
printf("tracer exits.\n");
}
}
这是传说中的单步调试了。
tracee6 i 初始值为 10,默认打印10.
i 的地址为addr。
tracer6 单步执行 tracee6,如果 addr 中的数据的值为10,则修改为2341.
最终tracee6 打印2341.
https://git.oschina.net/acgmohu/PtraceX86Examples/blob/master/tracee6.c
#include <stdio.h>
int i;
void main()
{
i = 10;
printf("child: i = %d\n", i);
}
https://git.oschina.net/acgmohu/PtraceX86Examples/blob/master/tracer6.c
#include <sys/ptrace.h>
#include <signal.h>
#include <stdio.h>
#include <sys/user.h>
#include <sys/types.h>
#include <sys/wait.h>
void main()
{
int i = 0, status = 0, pid;
struct user_regs_struct uregs;
int dat;
int addr = 0x0804a024;
if ((pid=fork())==0) {
ptrace(PTRACE_TRACEME, 0, 0, 0);
execl("./tracee6", "tracee6", 0, NULL);
printf("execl error...\n");
} else {
//wait for child to stop at the `exec'
wait(&status);
while(1) {
// do just one instruction
ptrace(PTRACE_SINGLESTEP, pid, 0, 0);
wait(&status);
if(WIFEXITED(status))
break;
// `addr' is address of `i' in child process
dat = ptrace(PTRACE_PEEKDATA, pid, addr, 0);
if(dat == 10) {
ptrace(PTRACE_POKEDATA, pid, addr, 2341);
ptrace(PTRACE_CONT, pid, 0, 0);
break;
}
}
wait(&status);
if(WIFEXITED(status))printf("child over\n");
printf("tracer exits.\n");
}
}
ptrace 有跟踪系统调用的能力。
PTRACE_SYSCALL 请求会重启子进程(类似PTRACE_CONT),但会在下次进入或退出系统调用时停止。
例子:
子进程会打印它自己的pid以及它的父进程pid,然后,发一个信号给自己。
因为子进程被父进程附加了,所以,它收到信号后会暂停。
父进程会通过请求 PTRACE_SYSCALL 重启子进程。
getpid 为系统调用,它有一个唯一的系统调用号。父进程通过 PTRACE_GETREGS 获得这个系统调用号,再用 PTRACE_SETREGS 将 getpid 系统调用号,替换为 getppid 系统调用号。之后,用 PTRACE_CONT 让子进程跑完。由于系统调用号已经被修改了,所以,子进程会执行getppid
而不是getpid
。
#include <sys/ptrace.h>
#include <signal.h>
#include <linux/user.h>
#include <sys/types.h>
#include <sys/wait.h>
main()
{
int status = 0, pid, r;
struct user_regs_struct uregs;
if ((pid=fork())==0) {
printf("pid = %d, ppid = %d\n", getpid(), getppid());
ptrace(PTRACE_TRACEME, 0, 0, 0);
kill(getpid(), SIGINT);
r = getpid();
printf("%d\n", r);
} else {
wait(&status);
ptrace(PTRACE_SYSCALL, pid, 0, 0);
wait(&status);
ptrace(PTRACE_GETREGS, pid, 0, &uregs);
//this should print 20, syscall number of getpid
printf("syscall nr: %d\n", uregs.orig_eax);
// 64 is syscall number of getppid
uregs.orig_eax = 64;
ptrace(PTRACE_SETREGS, pid, 0, &uregs);
ptrace(PTRACE_CONT, pid, 0, 0);
wait(&status);
if(WIFEXITED(status))printf("child over\n");
sleep(1000);
}
}