本文是对大犇蒸米spark安卓动态调试七种武器之离别钩 – Hooking的实践记录以及知识整理!原文请戳链接.

实现hook就离不开ptrace.

ARM上的系统调用

系统调用由SWI实现,即软件中断(Software Interrupt),在请求系统服务时造成的中断,由SWI指令造成异常从而切入特权模式,从而允许非特权模式访问特权模式的函数.

ARM中有两种系统调用方式: OABI(old application binary interface)和EABI(extended application binary interface).见(内核源码arch/arm/kernel/entry-common.S文件).

对于OABI: 通过跟随在swi指令后的调用号来进行. 1101 1111 vvvv vvvv -- SWI immed_8 (Thumb指令)格式)

1
swi (#num | 0x900000) (0x900000是个magic值)

对于EABI: 调用号存放在r7中. 1110 1111 0000 0000 -- SWI 0 (Thumb指令格式)

1
2
mov r7, #num
swi 0x0

arm.pdfA4.1.107 SWIA7.1.69 SWI 分别是对ARM和Thumb中SWI的描述.

所有的系统调用号在arch/arm/include/asm/unistd.h文件.

所以在得到一条SWI指令时,要解析出系统调用号得分两种情况:
这里的源程序直接按ARM指令集处理了,没有做判断thumb的处理.

1
2
3
4
5
6
7
8
if(ins == 0xef000000){ //直接和指令比较
return regs->ARM_r7;
}else{
if((ins & 0x0ff00000) != 0x0f900000){ //和magic值比较,这里我认为前面两位0f改成别的值也是可以的,重要的是magic值
return -1;
}
return ins &= 0x000fffff;
}

拦截系统调用

整个思路是:

使被调试程序在下次次调用系统函数前后停下(SYSCALL),这时调试程序对被调试进行操作(PTRACE_PEEKTEXT/PTRACE_GETREGS…),随后使被调试程序继续运行(SYSCALL),调试程序等待(wait).

被调试程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include<stdio.h>
#include<stdlib.h>
int count = 0;
void print()
{
printf("hello,%d\n",count);
sleep(1);
}
int main(int argc, char const *argv[])
{
while(1){
print();
count++;
}
return 0;
}

调试程序:

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
#include<stdio.h>
#include<sys/ptrace.h> //头文件路径根据ndk目录下/platforms/android-21/arch-arm/usr/include
#include<asm/unistd.h>
long getSystemCallNumber(int pid, struct pt_regs *regs)
{
long ins = 0;
ins = ptrace(PTRACE_PEEKTEXT, pid, (void *)(regs->ARM_pc - 4), NULL); //r15-4
if(ins == 0){
return 0;
}
if(ins == 0xef000000){ //EABI
return regs->ARM_r7;
}else{
if((ins & 0x0ff00000) != 0x0f900000){ //OABI
return -1;
}
return ins &= 0x000fffff;
}
}
void doSthBefore(int pid)
{
struct pt_regs regs;
int sysCallNumber = 0;
ptrace(PTRACE_GETREGS,pid,NULL,&regs);
sysCallNumber = getSystemCallNumber(pid,&regs);
printf("before syscallno: %d\n", sysCallNumber);
if(sysCallNumber == __NR_write){ //得到参数
printf("__NR_write << %ld, %p, %ld\n",regs.ARM_r0, (void*)regs.ARM_r1, regs.ARM_r2);
}
}
void doSthAfter(int pid)
{
struct pt_regs regs;
int sysCallNumber = 0;
ptrace(PTRACE_GETREGS,pid,NULL,&regs);
sysCallNumber = getSystemCallNumber(pid,&regs);
printf("after syscallno: %d\n", sysCallNumber);
if(sysCallNumber == __NR_write){
printf("__NR_write >> %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;
}
int pid = atoi(argv[1]);
if(0 != ptrace(PTRACE_ATTACH, pid, NULL, NULL)){
printf("attach failed.");
return 1;
}
ptrace(PTRACE_SYSCALL, pid, NULL, NULL); //使之在系统调用前后停下
int status;
while(1){
wait(&status);
doSthBefore(pid);
ptrace(PTRACE_SYSCALL, pid, NULL, NULL);
wait(&status);
doSthAfter(pid);
ptrace(PTRACE_SYSCALL, pid, NULL, NULL);
}
ptrace(PTRACE_DETACH, pid, NULL, NULL);
return 0;
}

结果:

修改函数参数

修改printf的参数:字符串及其长度.

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
#define long_size 4
void readData(int pid, long addr, char *str, int len)
{
int i,j;
char *laddr;
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, pid, addr+i*4, NULL); //PEEKDATA一次读一个字
memcpy(laddr,data.chars,long_size);
laddr += long_size;
i++;
}
j = len % long_size;
if (j != 0) {
data.val = ptrace(PTRACE_PEEKDATA, pid, addr+i*4, NULL); //PEEKDATA一次读一个字
memcpy(laddr,data.chars,long_size);
}
str[len] = '\0';
}
void writeData(int pid, long addr, char *str, int len)
{
int i,j;
char *laddr;
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, pid, addr+i*4, data.val); //PEEKDATA一次写一个字
laddr += long_size;
i++;
}
j = len % long_size;
if (j != 0) {
memcpy(data.chars,laddr,j);
ptrace(PTRACE_POKEDATA, pid, addr+i*4, data.val); //POKEDATA一次写一个字
}
}
void strrev(char *p)
{
char *q = p;
while(q && *q) ++q;
for(--q; p < q; ++p, --q)
*p = *p ^ *q,
*q = *p ^ *q,
*p = *p ^ *q;
}
void modifyString(int pid,long addr,long len)
{
char *str;
str = (char*)calloc(sizeof(char)*(len+1),1);
readData(pid,addr,str,len);
strrev(str);
writeData(pid,addr,str,len);
}
void doSthBefore(int pid)
{
struct pt_regs regs;
int sysCallNumber = 0;
ptrace(PTRACE_GETREGS,pid,NULL,&regs);
sysCallNumber = getSystemCallNumber(pid,&regs);
printf("before syscallno: %d\n", sysCallNumber);
if(sysCallNumber == __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);
}
}

Reference

安卓动态调试七种武器之离别钩 – Hooking

Android ptrace简介

浅谈EABI和OABI

Arm Linux系统调用流程详细解析-SWI

linux arm 系统调用