hook native层学习

参考资料

https://github.com/zhengmin1989/TheSevenWeapons/tree/master/LiBieGou
http://man7.org/linux/man-pages/man2/ptrace.2.html
在阅读之前,最好先看一下我的另一篇文章,关于怎么编译ARM程序
以及关于ptrace的基础知识

Playing with Ptrace Android

示例代码target.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <stdio.h>
#include <string.h>
int count = 0;
void sevenWeapons(int number)
{
char* str = "Hello,LiBieGou!";
printf("%s %d\n",str,number);
}
int main()
{
while(1)
{
sevenWeapons(count);
count++;
sleep(1);
}
return 0;
}

hook程序hook1.c

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
#include <sys/ptrace.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <sys/syscall.h>
long getSysCallNo(int pid, struct pt_regs *regs)
{
long scno = 0;
scno = ptrace(PTRACE_PEEKTEXT, pid, (void *)(regs->ARM_pc - 4), NULL);
if(scno == 0)
return 0;

if (scno == 0xef000000) {
scno = regs->ARM_r7;
} else {
if ((scno & 0x0ff00000) != 0x0f900000) {
return -1;
}
scno &= 0x000fffff;
}
return scno;
}
void hookSysCallBefore(pid_t pid)
{
struct pt_regs regs;
int sysCallNo = 0;

ptrace(PTRACE_GETREGS, pid, NULL, &regs);
sysCallNo = getSysCallNo(pid, &regs);
printf("Before SysCallNo = %d\n",sysCallNo);

if(sysCallNo == __NR_write)
{
printf("__NR_write: %ld %p %ld\n",regs.ARM_r0,(void*)regs.ARM_r1,regs.ARM_r2);
}
}
void hookSysCallAfter(pid_t pid)
{
struct pt_regs regs;
int sysCallNo = 0;
ptrace(PTRACE_GETREGS, pid, NULL, &regs);
sysCallNo = getSysCallNo(pid, &regs);

printf("After SysCallNo = %d\n",sysCallNo);

if(sysCallNo == __NR_write)
{
printf("__NR_write return: %ld\n",regs.ARM_r0);
}

printf("\n");
}
int main(int argc, char *argv[])
{
if(argc != 2) {
printf("Usage: %s <pid to be traced>\n", argv[0]);
return 1;
}

pid_t pid;
int status;
pid = atoi(argv[1]);

if(0 != ptrace(PTRACE_ATTACH, pid, NULL, NULL))
{
printf("Trace process failed:%d.\n", errno);
return 1;
}

ptrace(PTRACE_SYSCALL, pid, NULL, NULL);

while(1)
{
wait(&status); //wait函数会延迟父进程的执行,直到被调试的进程切换为停止状态或者终止为止.
hookSysCallBefore(pid);
ptrace(PTRACE_SYSCALL, pid, NULL, NULL);

wait(&status);
hookSysCallAfter(pid);
ptrace(PTRACE_SYSCALL, pid, NULL, NULL);
}

ptrace(PTRACE_DETACH, pid, NULL, NULL);
return 0;
}
  • PTRACE_SYSCALL
    使被调试进程继续运行,但是在下一个系统调用的入口处或出口处停下,或者是执行完一条指令后停下.
    例如,调试进程可以监视被调试进程系统调用入口处的参数,接着再使用SYSCALL,监视系统调用的返回值.

每当目标程序调用system call前的时候,就会暂停下载。然后我们就可以读取寄存器的值来获取system call的各项信息。然后我们再一次使用ptrace(PTRACE_SYSCALL, pid, NULL, NULL)这个函数就可以让system call在调用完后再一次暂停下来,并获取system call的返回值。

获取system call编号

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
long getSysCallNo(int pid, struct pt_regs *regs)
{
long scno = 0;
scno = ptrace(PTRACE_PEEKTEXT, pid, (void *)(regs->ARM_pc - 4), NULL); //读出指令
if(scno == 0)
return 0;

if (scno == 0xef000000) { //如果指令是EABI
scno = regs->ARM_r7; //直接从r7从取出调用号
} else { //如果指令是OABI
if ((scno & 0x0ff00000) != 0x0f900000) {
return -1;
}
scno &= 0x000fffff;
}
return scno;
}

ARM架构上,所有的系统调用都是通过SWI来实现的。并且在ARM 架构中有两个SWI指令,分别针对EABI和OABI:
[EABI] 机器码:
1110 1111 0000 0000 – SWI 0
具体的调用号存放在寄存器r7中.
[OABI] 机器码:
1101 1111 vvvv vvvv – SWI immed_8
调用号进行转换以后得到指令中的立即数。立即数=调用号 | 0x900000
既然需要兼容两种方式的调用,我们在代码上就要分开处理。首先要获取SWI指令判断是EABI还是OABI,如果是EABI,可从r7中获取调用号。如果是OABI,则从SWI指令中获取立即数,反向计算出调用号。

OABI和EABI的区别
两种ABI在如下方面有区别:
A。调用规则(包括参数如何传递及如何获得返回值)
B。系统调用的数目以及应用程序应该如何去做系统调用
C。目标文件的二进制格式,程序库等
D。结构体中的填充(padding/packing)和对齐。

PTRACE_PEEKTEXT, PTRACE_PEEKDATA
形式:ptrace(PTRACE_PEEKTEXT, pid, addr, data)
ptrace(PTRACE_PEEKDATA, pid, addr, data)
描述:从内存地址中读取一个字节,pid表示被跟踪的子进程,内存地址由addr给出,data为用户变量地址用于返回读到的数据。在Linux(i386)中用户代码段与用户数据段重合所以读取代码段和数据段数据处理是一样的。

hook程序运行逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
if(0 != ptrace(PTRACE_ATTACH, pid, NULL, NULL))
{
printf("Trace process failed:%d.\n", errno);
return 1;
}
ptrace(PTRACE_SYSCALL, pid, NULL, NULL);
while(1)
{
wait(&status); //wait函数会延迟父进程的执行,直到被调试的进程切换为停止状态或者终止为止.
hookSysCallBefore(pid);
ptrace(PTRACE_SYSCALL, pid, NULL, NULL);
wait(&status);
hookSysCallAfter(pid);
ptrace(PTRACE_SYSCALL, pid, NULL, NULL);
}

被调试进程:被debugger attach ——被PTRACE_SYSCALL——->在syscall的入口处停止—被PTRACE_SYSCALL—>在syscall的出口处停止

进行调试的进程:attach要调试的进程——PTRACE_SYSCALL/wait——–>等待被调试进程停止—->读取调用入口处的参数—PTRACE_SYSCALL/wait—>监视系统调用的返回值

hook system call前的函数,和hook system call后的函数

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
void hookSysCallBefore(pid_t pid)
{
struct pt_regs regs;
int sysCallNo = 0;
ptrace(PTRACE_GETREGS, pid, NULL, &regs);
sysCallNo = getSysCallNo(pid, &regs);
printf("Before SysCallNo = %d\n",sysCallNo);
if(sysCallNo == __NR_write)
{
printf("__NR_write: %ld %p %ld\n",regs.ARM_r0,(void*)regs.ARM_r1,regs.ARM_r2);
}
}
void hookSysCallAfter(pid_t pid)
{
struct pt_regs regs;
int sysCallNo = 0;
ptrace(PTRACE_GETREGS, pid, NULL, &regs);
sysCallNo = getSysCallNo(pid, &regs);
printf("After SysCallNo = %d\n",sysCallNo);
if(sysCallNo == __NR_write)
{
printf("__NR_write return: %ld\n",regs.ARM_r0);
}
printf("\n");
}

PTRACE_GETREGS
形式:ptrace(PTRACE_GETREGS, pid, 0, data)
描述:读取寄存器值,pid表示被跟踪的子进程,data为用户变量地址用于返回读到的数据。此功能将读取所有17个基本寄存器的值。

在获取了system call的number以后,我们可以进一步获取个个参数的值.比如说write这个system call。
在arm上,如果形参个数少于或等于4,则形参由R0,R1,R2,R3四个寄存器进行传递。
若形参个数大于4,大于4的部分必须通过堆栈进行传递。
而执行完函数后,函数的返回值会保存在R0这个寄存器里。

我们可以看到第一个SysCallNo是162,也就是sleep函数。第二个SysCallNo是4,也就是write函数,因为printf本质就是调用write这个系统调用来完成的。关于system call number对应的具体system call可以参考我在github上的reference文件夹中的systemcalllist.txt文件,里面有对应的列表。我们的hook1程序还对write的参数做了解析,比如1表示stdout,0xadf020表示字符串的地址,19代表字符串的长度。而返回值19表示write成功写入的长度,也就是字符串的长度。

测试

ps | grep target

得到pid为10797
target

hook

我们可以看到第一个SysCallNo是162,也就是sleep函数。
第二个SysCallNo是4,也就是write函数,因为printf本质就是调用write这个系统调用来完成的。
关于system call number对应的具体system call
可以参考我在github上的reference文件夹中的systemcalllist.txt文件,里面有对应的列表。
我们的hook1程序还对write的参数做了解析,比如1表示stdout,0x1459020表示字符串的地址,19代表字符串的长度。而返回值19表示write成功写入的长度,也就是字符串的长度。

利用Ptrace动态修改内存

仅仅是用ptrace来获取system call的参数和返回值还不能体现出ptrace的强大,下面我们就来演示用ptrace读写内存。我们在hook1.c的基础上继续进行修改,在write被调用之前对要输出string进行翻转操作。
我们在hookSysCallBefore()函数中加入modifyString(pid, regs.ARM_r1, regs.ARM_r2)这个函数:

修改内存,转置字符串

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
void hookSysCallBefore(pid_t pid)
{
struct pt_regs regs;
int sysCallNo = 0;

ptrace(PTRACE_GETREGS, pid, NULL, &regs);
sysCallNo = getSysCallNo(pid, &regs);
printf("Before SysCallNo = %d\n",sysCallNo);

if(sysCallNo == __NR_write)
{
printf("__NR_write: %ld %p %ld\n",regs.ARM_r0,(void*)regs.ARM_r1,regs.ARM_r2);
modifyString(pid, regs.ARM_r1, regs.ARM_r2); //r1是要打印的字符串的地址,r2是要打印的字符串的长度
}
}
void modifyString(pid_t pid, long addr, long strlen)
{
char* str;
str = (char *)calloc((strlen+1) * sizeof(char), 1);//申请一块空间str,存放要被修改的字符串
getdata(pid, addr, str, strlen);//从要打印的字符串的地址读出字符串,将其存入申请的空间str
reverse(str); //转置字符串
putdata(pid, addr, str, strlen);//将转置后的字符串放入要打印的字符串的地址,实现修改
}

getdata和putdata

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
void getdata(pid_t child, long addr,
char *str, int len)
{ char *laddr;
int i, j;
union u {
long val;
char chars[long_size];
}data;
i = 0;
j = len / long_size;
laddr = str;
while(i < j) {
data.val = ptrace(PTRACE_PEEKDATA,
child, addr + i * 4,
NULL);
memcpy(laddr, data.chars, long_size);
++i;
laddr += long_size;
}
j = len % long_size;
if(j != 0) {
data.val = ptrace(PTRACE_PEEKDATA,
child, addr + i * 4,
NULL);
memcpy(laddr, data.chars, j);
}
str[len] = '\0';
}
void putdata(pid_t child, long addr,
char *str, int len)
{
char *laddr;
int i, j;
union u {
long val;
char chars[long_size];
}data;
i = 0;
j = len / long_size;
laddr = str;
while(i < j) {
memcpy(data.chars, laddr, long_size);
ptrace(PTRACE_POKEDATA, child,
addr + i * 4, data.val);
++i;
laddr += long_size;
}
j = len % long_size;
if(j != 0) {
memcpy(data.chars, laddr, j);
ptrace(PTRACE_POKEDATA, child,
addr + i * 4, data.val);
}
}

getdata()和putdata()分别使用PTRACE_PEEKDATA和PTRACE_POKEDATA对内存进行读写操作。
因为ptrace的内存操作一次只能控制4个字节,所以如果修改比较长的内容需要进行多次操作。

  • PTRACE_PEEKTEXT, PTRACE_PEEKDATA
    从内存地址中读取四个字节,内存地址由addr给出。

  • PTRACE_POKETEXT, PTRACE_POKEDATA
    往内存地址中写入四个字节。内存地址由addr给出。

tip:arm里的word是指四个字节

测试


利用Ptrace动态执行sleep()函数——调用系统so库中的函数

目标函数是libc.so中的sleep函数.
正常情况是每输出一次暂停一秒,现在我们让它暂停10秒

总体思路

获取目标进程sleep函数地址
在目标进程内执行sleep函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void inject(pid_t pid)
{
struct pt_regs old_regs,regs;
long sleep_addr;
//save old regs
ptrace(PTRACE_GETREGS, pid, NULL, &old_regs);
memcpy(&regs, &old_regs, sizeof(regs));
printf("getting remote sleep_addr:\n");
sleep_addr = get_remote_addr(pid, libc_path, (void *)sleep); //获取目标进程sleep函数地址

long parameters[1];
parameters[0] = 10;

ptrace_call(pid, sleep_addr, parameters, 1, &regs);

//restore old regs
ptrace(PTRACE_SETREGS, pid, NULL, &old_regs);
}

如何获取函数地址

  • 已知条件: 本进程的基址、目标进程的基址、本进程中sleep函数的地址(当然,这些已知条件也是需要获得的。
    /proc/pid/maps文件中存储的是进程内存映射详情,我们可以在这个文件中查询进程中so的基址;
    sleep函数在本进程中的地址直接可以获得(void*)
  • 求解: 目标进程中sleep函数地址
  • 计算: 本进程sleep地址 - 本进程基址 + 目标进程基址

获取so库的加载基址

因为libc.so在内存中的地址是随机的,所以我们需要先获取目标进程的libc.so的加载地址,再获取自己进程的libc.so的加载地址和sleep()在内存中的地址。然后我们就能计算出sleep()函数在目标进程中的地址了。要注意的是获取目标进程和自己进程的libc.so的加载地址是通过解析/proc/[pid]/maps得到的。

打开/proc/pid/maps文件找到基址.

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
void* get_module_base(int pid, const char* module_name)
{
FILE *f; //文件指针
long addr = 0; //模块地址
char filename[32]; //maps路径
char *pch;
char line[1024]; //每行
if(pid == 0){
snprintf(filename, sizeof(filename), "/proc/self/maps");
} else {
snprintf(filename, sizeof(filename), "/proc/%d/maps", pid);
}
f = fopen(filename, "r");
if(f != NULL){
while(fgets(line,sizeof(line),f)){
if(strstr(line, module_name)) { //找到该行是否含有module_name
pch = strtok(line,"-"); //分割出基址字符串
addr = strtoul(pch,NULL,0x10); //转换为16进制数
if(addr == 0x8000) //32位linux程序中默认的text加载地址为0x08408000,64位的改为0x00400000,此时计算base地址就没什么用了
addr = 0;
break;
}
}
fclose(f);
}
return (void*)addr;
}

计算目标进程中sleep函数地址

1
2
3
4
5
6
7
8
9
10
long get_remote_addr(int target_pid, const char* module_name, void* local_addr)
{
void* local_handle = get_module_base(0,module_name); //本进程的基址
void* remote_handle = get_module_base(target_pid,module_name); //目标进程的基址
printf("local_handle:%p remote_handle:%p\n", local_handle, remote_handle);
//计算公式
long remote_addr = (long)((uint32_t)local_addr - (uint32_t)local_handle + (uint32_t)remote_handle);
printf("remote_addr:%p\n", remote_addr);
return remote_addr;
}

如何执行sleep函数

  • 设置函数参数,如果参数个数小于等于4,参数按顺序放入R0~R4寄存器中;如果参数个数大于4,多余的部分需要入栈.
  • 设置pc寄存器的值,设置当前指令集标志位.
  • 应用以上寄存器的修改使之生效.
  • 等待函数执行.
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
//目标进程id,目标函数地址,参数地址,参数个数,寄存器地址
int ptrace_call(int pid, long addr, long *params, uint32_t params_num, struct pt_regs* regs)
{
uint32_t i;
for (i = 0; i < params_num && i < 4; i++) { //设置少于4个的参数
regs->uregs[i] = params[i];
}
//设置多于4个的参数
if (i < params_num) {
regs->ARM_sp -= (params_num - i) * long_size; //抬高栈顶指针(分配空间)
writeData(pid, (long)regs->ARM_sp, (char*)&params[i], (params_num - i) * long_size); //写入
}
regs->ARM_pc = addr; //设置pc
if (regs->ARM_pc & 1) { //判断是否是Thumb指令
regs->ARM_pc &= (~1u); //Thumb的pc最后一位总是0
regs->ARM_cpsr |= CPSR_T_MASK; //T标志位为1
} else { //arm
regs->ARM_cpsr &= ~CPSR_T_MASK; //T标志位为0
}
regs->ARM_lr = 0; //为了使sleep函数执行完毕后产生“内存访问错误”,这样我们就知道什么时候执行完了
if(ptrace_setregs(pid,regs)==-1 || ptrace_continue(pid)==-1){ //目标进程继续执行
return -1;
}
int stat = 0; //WUNTRACED表示如果pid进程进入暂停状态,那么waitpid函数立即返回
waitpid(pid,&stat,WUNTRACED); //等待sleep函数执行,等待过程中本进程暂停执行
printf("%d\n", stat);
while (stat != 0xb7f) { //0xb7f表示目标进程进入暂停状态
printf("%d\n", stat);
if (ptrace_continue(pid) == -1) {
return -1;
}
waitpid(pid,&stat,WUNTRACED);
}
return 0;
}

完整hook代码

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
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
#include <stdio.h>
#include <stdlib.h>
#include <asm/user.h>
#include <asm/ptrace.h>
#include <sys/ptrace.h>
#include <sys/wait.h>
#include <sys/mman.h>
#include <dlfcn.h>
#include <dirent.h>
#include <unistd.h>
#include <string.h>
#include <elf.h>
#include <android/log.h>
#define CPSR_T_MASK ( 1u << 5 )
const char *libc_path = "/system/lib/libc.so";
const int long_size = sizeof(long);
int ptrace_setregs(pid_t pid, struct pt_regs * regs)
{
if (ptrace(PTRACE_SETREGS, pid, NULL, regs) < 0) {
perror("ptrace_setregs: Can not set register values");
return -1;
}
return 0;
}
int ptrace_continue(pid_t pid)
{
if (ptrace(PTRACE_CONT, pid, NULL, 0) < 0) {
perror("ptrace_cont");
return -1;
}
return 0;
}
void putdata(pid_t child, long addr,
char *str, int len)
{ char *laddr;
int i, j;
union u {
long val;
char chars[long_size];
}data;
i = 0;
j = len / long_size;
laddr = str;
while(i < j) {
memcpy(data.chars, laddr, long_size);
ptrace(PTRACE_POKEDATA, child,
addr + i * 4, data.val);
++i;
laddr += long_size;
}
j = len % long_size;
if(j != 0) {
memcpy(data.chars, laddr, j);
ptrace(PTRACE_POKEDATA, child,
addr + i * 4, data.val);
}
}
void* get_module_base(pid_t pid, const char* module_name)
{
FILE *fp;
long addr = 0;
char *pch;
char filename[32];
char line[1024];
if (pid == 0) {
snprintf(filename, sizeof(filename), "/proc/self/maps");
} else {
snprintf(filename, sizeof(filename), "/proc/%d/maps", pid);
}
fp = fopen(filename, "r");
if (fp != NULL) {
while (fgets(line, sizeof(line), fp)) {
if (strstr(line, module_name)) {
pch = strtok( line, "-" );
addr = strtoul( pch, NULL, 16 );
if (addr == 0x8000)
addr = 0;
break;
}
}
fclose(fp) ;
}
return (void *)addr;
}
long get_remote_addr(pid_t target_pid, const char* module_name, void* local_addr)
{
void* local_handle, *remote_handle;
local_handle = get_module_base(0, module_name);
remote_handle = get_module_base(target_pid, module_name);
printf("module_base: local[%p], remote[%p]\n", local_handle, remote_handle);
long ret_addr = (long)((uint32_t)local_addr + (uint32_t)remote_handle - (uint32_t)local_handle);
printf("remote_addr: [%p]\n", (void*) ret_addr);
return ret_addr;
}
int ptrace_call(pid_t pid, long addr, long *params, uint32_t num_params, struct pt_regs* regs)
{
uint32_t i;
for (i = 0; i < num_params && i < 4; i ++) {
regs->uregs[i] = params[i];
}
//
// push remained params onto stack
//
if (i < num_params) {
regs->ARM_sp -= (num_params - i) * sizeof(long) ;
putdata(pid, (long)regs->ARM_sp, (char*)&params[i], (num_params - i) * sizeof(long));
}
regs->ARM_pc = addr;
if (regs->ARM_pc & 1) {
/* thumb */
regs->ARM_pc &= (~1u);
regs->ARM_cpsr |= CPSR_T_MASK;
} else {
/* arm */
regs->ARM_cpsr &= ~CPSR_T_MASK;
}
regs->ARM_lr = 0;
if (ptrace_setregs(pid, regs) == -1
|| ptrace_continue(pid) == -1) {
printf("error\n");
return -1;
}
int stat = 0;
waitpid(pid, &stat, WUNTRACED);
while (stat != 0xb7f) {
if (ptrace_continue(pid) == -1) {
printf("error\n");
return -1;
}
waitpid(pid, &stat, WUNTRACED);
}
return 0;
}
void inject(pid_t pid)
{
struct pt_regs old_regs,regs;
long sleep_addr;
//save old regs
ptrace(PTRACE_GETREGS, pid, NULL, &old_regs);
memcpy(&regs, &old_regs, sizeof(regs));
printf("getting remote sleep_addr:\n");
sleep_addr = get_remote_addr(pid, libc_path, (void *)sleep);

long parameters[1];
parameters[0] = 10;

ptrace_call(pid, sleep_addr, parameters, 1, &regs);

//restore old regs
ptrace(PTRACE_SETREGS, pid, NULL, &old_regs);
}
int main(int argc, char *argv[])
{
if(argc != 2) {
printf("Usage: %s <pid to be traced>\n", argv[0]);
return 1;
}

pid_t pid;
int status;
pid = atoi(argv[1]);

if(0 != ptrace(PTRACE_ATTACH, pid, NULL, NULL))
{
printf("Trace process failed:%d.\n", errno);
return 1;
}

inject(pid);

ptrace(PTRACE_DETACH, pid, NULL, 0);

return 0;
}

关于waitpid

详细介绍可看官方文档.

参数status

wait函数调用过后,status指针指向可以被宏解析的值,这些宏在ndk目录下platforms/android-21/arch-arm/usr/include/sys/wait.h文件中定义.
高2字节用于表示导致子进程的退出或暂停状态信号值(WTERMSIG),低2字节表示子进程是退出(0x0)还是暂停(0x7f)状态(WEXITSTATUS)。
如:0xb7f就表示子进程为暂停状态,导致它暂停的信号量为11即sigsegv错误。
关于错误代码的文档可看这里,
定义在ndk目录下platforms/android-21/arch-arm/usr/include/asm/signal.h中.

其中两个宏:
WEXITSTATUS(statusPtr):
if the child process terminates normally, this macro evaluates to the lower 8 bits of the value passed to the exit or _exit function or returned from main.
WTERMSIG(
statusPtr)
if the child process ends by a signal that was not caught, this macro evaluates to the number of that signal.

参数options

指定了waitpid的额外行动.选项有:

  • WNOHANG:
    告诉waitpid不等程序中止立即返回status信息.
    正常情况是当主进程对子进程使用了waitpid,主进程就会阻塞直到waitpid返回status信息;如果指定了WNOHANG选项,主进程就不会阻塞了.
    如果还没有可用的status信息,waitpid返回0.
  • WUNTRACED:
    告诉waitpid,如果子进程进入暂停状态或者已经终止,那么就立即返回status信息,正常情况是子进程终止的时候才返回.
    如果是被ptrace的子进程,那么即使不提供WUNTRACED参数,也会在子进程进入暂停状态的时候立即返回。
    对于使用ptrace_cont运行的子进程,它会在3种情况下进入暂停状态:①下一次系统调用;②子进程退出;③子进程的执行发生错误。

    总结

    程序中的0xb7f就表示子进程进入了暂停状态,且发送的错误信号为11(SIGSEGV),它表示试图访问未分配给自己的内存, 或试图往没有写权限的内存地址写数据。
    当子进程执行完注入的函数后,由于我们在前面设置了regs->ARM_lr = 0,它就会返回到0地址处继续执行,这样就会产生SIGSEGV了.

测试

正常的情况是target程序每秒输出一句话,但是用hook3程序hook后,就会暂停10秒钟的时间,因为我们利用ptrace运行了sleep(10)在目标程序中。


利用Ptrace动态加载so并执行自定义函数

总体思路

  • 保存当前寄存器的状态
  • 获取目标程序的mmap, dlopen, dlsym, dlclose函数地址
  • 调用mmap分配空间保存参数信息
  • 调用dlopen加载so库
  • 调用dlsym找到目标函数地址
  • 执行目标函数
  • 调用dlclose卸载so库
  • 恢复寄存器的状态

保存当前寄存器的状态

1
2
3
struct pt_regs old_regs,regs;
ptrace(PTRACE_GETREGS, pid, NULL, &old_regs);
memcpy(&regs,&old_regs,sizeof(regs));

获取目标程序的mmap, dlopen, dlsym, dlclose函数地址

1
2
3
4
5
long mmap_addr,dlopen_addr,dlsym_addr,dlclose_addr;
mmap_addr = get_remote_addr(pid, libc_path, (void*)mmap);
dlopen_addr = get_remote_addr(pid, libc_path, (void*)dlopen);
dlsym_addr = get_remote_addr(pid, libc_path, (void*)dlsym);
dlclose_addr = get_remote_addr(pid, libc_path, (void*)dlclose);

调用mmap分配空间保存参数信息

mmap的原型如下:
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
参数 描述

  • addr 映射的起始地址,为0表示由系统决定映射的起始地址
  • length 映射的长度
  • prot 映射的内存保存属性,不能与文件的打开模式冲突
  • flags 指定映射对象的类型,映射选项和映射页是否可以共享
  • fd 有效的文件描述符,一般是由open()函数返回;其值也可以设置为-1,此时需要指定flags参数中的MAP_ANON,表明进行的是匿名映射
  • offset 被映射对象内容的起点

这里我们需要的调用语句是
mmap(0,0x4000,PROT_READ|PROT_WRITE|PROT_EXEC,MAP_ANONYMOUS|MAP_PRIVATE,0,0)

  • PROT_EXEC表示可执行.
  • PROT_READ表示可读.
  • PROT_WRITE表示可写.
  • MAP_PRIVATE表示建立一个写入时拷贝的私有映射.内存区域的写入不会影响到原文件.这个标志和以上标志是互斥的,只能使用其中一个.
  • MAP_ANONYMOUS表示匿名映射,映射区不与任何文件关联.

mmap()可以用来将一个文件或者其它对象映射进内存,如果我们把flag设置为MAP_ANONYMOUS并且把参数fd设置为0的话就相当于直接映射一段内容为空的内存。
则:

1
2
3
4
5
6
7
8
9
10
11
long parameters[10];    
parameters[0] = 0; //构造参数
parameters[1] = 0x4000;
parameters[2] = PROT_READ | PROT_WRITE | PROT_EXEC;
parameters[3] = MAP_ANONYMOUS | MAP_PRIVATE;
parameters[4] = 0;
parameters[5] = 0;
ptrace_call(pid,mmap_addr,parameters,6,&regs);
//调用结束后获得r0中保存的返回值
ptrace(PTRACE_GETREGS,pid,NULL,&regs);
long mapping_base = regs.ARM_r0;

在我们使用ptrace_call(pid, mmap_addr, parameters, 6, &regs)调用完mmap()函数之后,要记得使用ptrace(PTRACE_GETREGS, pid, NULL, &regs); 用来获取保存返回值的regs.ARM_r0,这个返回值也就是映射的内存的起始地址。
mmap()映射的内存主要用来保存我们传给其他函数的参数。比如接下来我们需要用dlopen()去加载”/data/local/tmp/libinject.so”这个文件,所以我们需要先用putdata()将”/data/local/tmp/libinject.so”这个字符串放置在mmap()所映射的内存中,然后就可以将这个映射的地址作为参数传递给dlopen()了。接下来的dlsym(),so中的目标函数,dlclose()都是相同调用的方式,这里就不一一赘述了。

调用dlsym找到目标函数地址

原型:
void *dlsym(void *handle, const char *symbol);
参数 描述
handle so库的基址
symbol 函数名地址
这里我们需要的调用语句是dlsym(handle, function_name),则:

1
2
3
4
5
6
writeData(pid, mapping_base, function_name, strlen(function_name)+1);
parameters[0] = handle;
parameters[1] = mapping_base;
ptrace_call(pid, dlsym_addr, parameters, 2, &regs);
ptrace(PTRACE_GETREGS,pid,NULL,&regs); //调用结束后获得r0中保存的返回值
long function_addr = regs.ARM_r0;

执行目标函数

1
2
3
writeData(pid, mapping_base, function_parameters, strlen(function_parameters)+1);
parameters[0] = mapping_base;
ptrace_call(pid, function_addr, parameters, 1, &regs);

调用dlclose卸载so库

原型:int dlclose(void *handle);
则:

1
2
parameters[0] = handle;
ptrace_call(pid,dlclose_addr,parameters,1,&regs);

恢复寄存器的状态

ptrace(PTRACE_SETREGS,pid,NULL,&old_regs);

完整代码

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
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
#include <stdio.h>
#include <stdlib.h>
#include <asm/user.h>
#include <asm/ptrace.h>
#include <sys/ptrace.h>
#include <sys/wait.h>
#include <sys/mman.h>
#include <dlfcn.h>
#include <dirent.h>
#include <unistd.h>
#include <string.h>
#include <elf.h>
#include <android/log.h>

#define CPSR_T_MASK ( 1u << 5 )

const char *libc_path = "/system/lib/libc.so";

const int long_size = sizeof(long);

int ptrace_setregs(pid_t pid, struct pt_regs * regs)
{
if (ptrace(PTRACE_SETREGS, pid, NULL, regs) < 0) {
perror("ptrace_setregs: Can not set register values");
return -1;
}

return 0;
}

int ptrace_continue(pid_t pid)
{
if (ptrace(PTRACE_CONT, pid, NULL, 0) < 0) {
perror("ptrace_cont");
return -1;
}

return 0;
}

void putdata(pid_t child, long addr,
char *str, int len)
{ char *laddr;
int i, j;
union u {
long val;
char chars[long_size];
}data;
i = 0;
j = len / long_size;
laddr = str;
while(i < j) {
memcpy(data.chars, laddr, long_size);
ptrace(PTRACE_POKEDATA, child,
addr + i * 4, data.val);
++i;
laddr += long_size;
}
j = len % long_size;
if(j != 0) {
memcpy(data.chars, laddr, j);
ptrace(PTRACE_POKEDATA, child,
addr + i * 4, data.val);
}
}


void* get_module_base(pid_t pid, const char* module_name)
{
FILE *fp;
long addr = 0;
char *pch;
char filename[32];
char line[1024];

if (pid == 0) {
snprintf(filename, sizeof(filename), "/proc/self/maps");
} else {
snprintf(filename, sizeof(filename), "/proc/%d/maps", pid);
}

fp = fopen(filename, "r");

if (fp != NULL) {
while (fgets(line, sizeof(line), fp)) {
if (strstr(line, module_name)) {
pch = strtok( line, "-" );
addr = strtoul( pch, NULL, 16 );

if (addr == 0x8000)
addr = 0;

break;
}
}

fclose(fp) ;
}

return (void *)addr;
}


long get_remote_addr(pid_t target_pid, const char* module_name, void* local_addr)
{
void* local_handle, *remote_handle;

local_handle = get_module_base(0, module_name);
remote_handle = get_module_base(target_pid, module_name);

long ret_addr = (long)((uint32_t)local_addr + (uint32_t)remote_handle - (uint32_t)local_handle);

return ret_addr;
}


int ptrace_call(pid_t pid, uint32_t addr, long *params, uint32_t num_params, struct pt_regs* regs)
{
uint32_t i;
for (i = 0; i < num_params && i < 4; i ++) {
regs->uregs[i] = params[i];
}

//
// push remained params onto stack
//
if (i < num_params) {
regs->ARM_sp -= (num_params - i) * sizeof(long) ;
putdata(pid, regs->ARM_sp, (char*)&params[i], (num_params - i) * sizeof(long));
}

regs->ARM_pc = addr;
if (regs->ARM_pc & 1) {
/* thumb */
regs->ARM_pc &= (~1u);
regs->ARM_cpsr |= CPSR_T_MASK;
} else {
/* arm */
regs->ARM_cpsr &= ~CPSR_T_MASK;
}

regs->ARM_lr = 0;

if (ptrace_setregs(pid, regs) == -1
|| ptrace_continue(pid) == -1) {
printf("error\n");
return -1;
}

int stat = 0;
waitpid(pid, &stat, WUNTRACED);
while (stat != 0xb7f) {
if (ptrace_continue(pid) == -1) {
printf("error\n");
return -1;
}
waitpid(pid, &stat, WUNTRACED);
}

return 0;
}


void injectSo(pid_t pid,char* so_path, char* function_name,char* parameter)
{
struct pt_regs old_regs,regs;
long mmap_addr, dlopen_addr, dlsym_addr, dlclose_addr;

//save old regs

ptrace(PTRACE_GETREGS, pid, NULL, &old_regs);
memcpy(&regs, &old_regs, sizeof(regs));

//get remote addres

printf("getting remote addres:\n");
mmap_addr = get_remote_addr(pid, libc_path, (void *)mmap);
dlopen_addr = get_remote_addr( pid, libc_path, (void *)dlopen );
dlsym_addr = get_remote_addr( pid, libc_path, (void *)dlsym );
dlclose_addr = get_remote_addr( pid, libc_path, (void *)dlclose );

printf("mmap_addr=%p dlopen_addr=%p dlsym_addr=%p dlclose_addr=%p\n",
(void*)mmap_addr,(void*)dlopen_addr,(void*)dlsym_addr,(void*)dlclose_addr);


long parameters[10];

//mmap

parameters[0] = 0; //address
parameters[1] = 0x4000; //size
parameters[2] = PROT_READ | PROT_WRITE | PROT_EXEC; //WRX
parameters[3] = MAP_ANONYMOUS | MAP_PRIVATE; //flag
parameters[4] = 0; //fd
parameters[5] = 0; //offset

ptrace_call(pid, mmap_addr, parameters, 6, &regs);
ptrace(PTRACE_GETREGS, pid, NULL, &regs);

long map_base = regs.ARM_r0;
printf("map_base = %p\n", (void*)map_base);

//dlopen

printf("save so_path = %s to map_base = %p\n", so_path, (void*)map_base);
putdata(pid, map_base, so_path, strlen(so_path) + 1);

parameters[0] = map_base;
parameters[1] = RTLD_NOW| RTLD_GLOBAL;

ptrace_call(pid, dlopen_addr, parameters, 2, &regs);
ptrace(PTRACE_GETREGS, pid, NULL, &regs);

long handle = regs.ARM_r0;

printf("handle = %p\n",(void*) handle);

//dlsym

printf("save function_name = %s to map_base = %p\n", function_name, (void*)map_base);
putdata(pid, map_base, function_name, strlen(function_name) + 1);

parameters[0] = handle;
parameters[1] = map_base;

ptrace_call(pid, dlsym_addr, parameters, 2, &regs);
ptrace(PTRACE_GETREGS, pid, NULL, &regs);

long function_ptr = regs.ARM_r0;

printf("function_ptr = %p\n", (void*)function_ptr);

//function_call

printf("save parameter = %s to map_base = %p\n", parameter, (void*)map_base);
putdata(pid, map_base, parameter, strlen(parameter) + 1);

parameters[0] = map_base;

ptrace_call(pid, function_ptr, parameters, 1, &regs);

//dlcose

parameters[0] = handle;

ptrace_call(pid, dlclose_addr, parameters, 1, &regs);

//restore old regs

ptrace(PTRACE_SETREGS, pid, NULL, &old_regs);
}


int main(int argc, char *argv[])
{
if(argc != 2) {
printf("Usage: %s <pid to be traced>\n", argv[0]);
return 1;
}

pid_t pid;
int status;
pid = atoi(argv[1]);

if(0 != ptrace(PTRACE_ATTACH, pid, NULL, NULL))
{
printf("Trace process failed:%d.\n", errno);
return 1;
}

char* so_path = "/data/local/tmp/libinject.so";
char* function_name = "mzhengHook";
char* parameter = "sevenWeapons";
injectSo(pid, so_path, function_name, parameter);

ptrace(PTRACE_DETACH, pid, NULL, 0);

return 0;
}