自制 debuger 之 Linux ptrace int3 实践

被调试的程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
[root@localhost int3_on_call_ret]# cat hello.c 
#include "stdio.h"

int fun(int a, int b)
{
if(a > b)
{
return 0;
}
a++;
b--;
printf("a=%u b=%u\n", a, b);
fun(a, b);
}

int main()
{
printf("Hello\n");
fun(1,8);
return 0;
}
[root@localhost int3_on_call_ret]#

调试器

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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
[root@localhost int3_on_call_ret]# cat debuger.c 
#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
#include <signal.h>
#include <syscall.h>
#include <sys/ptrace.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/reg.h>
#include <sys/user.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>

void run_target(const char* programname)
{
printf("target started. will run '%s'\n", programname);

/* Allow tracing of this process */
if (ptrace(PTRACE_TRACEME, 0, 0, 0) < 0)
{
perror("ptrace");
return;
}

/* Replace this process's image with the given program */
execl(programname, programname, (char *)NULL);
}

void run_debugger(pid_t child_pid)
{
int wait_status;
struct user_regs_struct regs;

printf("debugger started\n");

/* Wait for child to stop on its first instruction */
wait(&wait_status);




/* Obtain and show child's instruction pointer */
ptrace(PTRACE_GETREGS, child_pid, 0, &regs);
printf("Child started. RIP = 0x%016x\n", regs.rip);

//修改指令
size_t addr = 4005e2; // hello 这里会调用一个CALLQ指令: 参考 objdump -d hello
size_t data = ptrace(PTRACE_PEEKTEXT, child_pid, (void*)addr, 0);
size_t trap = (data & 0xFFFFFFFFFFFFFF00) | 0xCC;

/* 写入 INT 3 指令 */
ptrace(PTRACE_POKETEXT, child_pid, (void*)addr, (void*)trap);



/* Let the child run to the breakpoint and wait for it to reach it */
ptrace(PTRACE_CONT, child_pid, 0, 0);

//wait to breakpoint
wait(&wait_status);
if (WIFSTOPPED(wait_status))
{
printf("Child got a signal: %s\n", strsignal(WSTOPSIG(wait_status)));
}
else
{
perror("wait");
return;
}


/* 恢复指令 */
ptrace(PTRACE_POKETEXT, child_pid, (void*)addr, (void*)data);

/* IP寄存器值 减1 */
ptrace(PTRACE_GETREGS, child_pid, 0, &regs);
regs.rip -= 1;
ptrace(PTRACE_SETREGS, child_pid, 0, &regs);

/* The child can continue running now */
ptrace(PTRACE_CONT, child_pid, 0, 0);

wait(&wait_status);
if (WIFEXITED(wait_status))
{
printf("Child exited\n");
}
else
if(WIFSIGNALED(wait_status))
{
printf("signal !!!\n");
}
else
{
printf("Unexpected signal. %s \n", strsignal(WSTOPSIG(wait_status)));
}
}

int main(int argc, char** argv)
{
pid_t child_pid;

if (argc < 2)
{
fprintf(stderr, "Expected a program name as argument\n");
return -1;
}

child_pid = fork();
if (child_pid == 0)
{
run_target(argv[1]);
}
else
if (child_pid > 0)
{
run_debugger(child_pid);
}
else
{
perror("fork");
return -1;
}

return 0;
}

[root@localhost int3_on_call_ret]#

编译&运行

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
[root@localhost int3_on_call_ret]# gcc -g -O0 hello.c -o hello
[root@localhost int3_on_call_ret]# gcc -g -O0 debuger.c -o debuger
[root@localhost int3_on_call_ret]# objdump -d hello
00000000004005ca <main>:
4005ca: 55 push %rbp
4005cb: 48 89 e5 mov %rsp,%rbp
4005ce: bf 8b 06 40 00 mov $0x40068b,%edi
4005d3: e8 78 fe ff ff callq 400450 <puts@plt>
4005d8: be 08 00 00 00 mov $0x8,%esi
4005dd: bf 01 00 00 00 mov $0x1,%edi
4005e2: e8 96 ff ff ff callq 40057d <fun>
4005e7: b8 00 00 00 00 mov $0x0,%eax
4005ec: 5d pop %rbp
4005ed: c3 retq
4005ee: 66 90 xchg %ax,%ax

[root@localhost int3_on_call_ret]# ./debuger hello
debugger started
target started. will run 'hello'
Child started. RIP = 0x00000000c7585170
Hello
a=2 b=7
a=3 b=6
a=4 b=5
a=5 b=4
Child got a signal: Trace/breakpoint trap
Child exited
[root@localhost int3_on_call_ret]#