作者:蒸米
地址:http://drops.wooyun.org/tips/9300
0x00 序
随着移动安全越来越火,各种调试工具也都层出不穷,但因为环境和需求的不同,并没有工具是万能的。另外工具是死的,人是活的,如果能搞懂工具的原理再结合上自身的经验,你也可以创造出属于自己的调试武器。因此,笔者将会在这一系列文章中分享一些自己经常用或原创的调试工具以及手段,希望能对国内移动安全的研究起到一些催化剂的作用。
目录如下:
安卓动态调试七种武器之长生剑 - Smali Instrumentation
安卓动态调试七种武器之孔雀翎 – Ida Pro
安卓动态调试七种武器之离别钩 – Hooking (上)
安卓动态调试七种武器之离别钩 – Hooking (下)
安卓动态调试七种武器之碧玉刀- Customized DVM
安卓动态调试七种武器之多情环- Customized Kernel
安卓动态调试七种武器之霸王枪 - Anti Anti-debugging
安卓动态调试七种武器之拳头 - Tricks & Summary
文章中所有提到的代码和工具都可以在我的github下载到,地址是:
https://github.com/zhengmin1989/TheSevenWeapons
0x01 离别钩
Hooking翻译成中文就是钩子的意思,所以正好配合这一章的名字《离别钩》。
“我知道钩是种武器,在十八般兵器中名列第七,离别钩呢?”
“离别钩也是种武器,也是钩。”
“既然是钩,为什么要叫做离别?”
“因为这柄钩,无论钩住什么都会造成离别。如果它钩住你的手,你的手就要和腕离别;如果它钩住你的脚,你的脚就要和腿离别。”
“如果它钩住我的咽喉,我就和这个世界离别了?”
“是的,”
“你为什么要用如此残酷的武器?”
“因为我不愿被人强迫与我所爱的人离别。”
“我明白你的意思了。”
“你真的明白?”
“你用离别钩,只不过为了要相聚。”
“是的。”
一提到hooking,让我又回想起了2011年的时候。当时android才出来没多久,各大安全公司都在忙着研发自己的手机助手。当时手机上最泛滥的病毒就是短信扣费类的病毒,但仅仅是靠云端的病毒库扫描是远远不够的。而这时候”LBE安全大师”横空出世,提供了主动防御的技术,可以在病毒发送短信之前拦截下来,并让用户选择是否发送。 其实这个主动防御技术就是hooking。虽然在PC上hooking的技术已经很成熟了,但是在android上的资料却非常稀少,只有少数人掌握着android上hooking的技术,因此这些人也变成了各大公司争相抢夺的对象。但是没有什么东西是能够永久保密的,这些技术早晚会被大家研究出来并对外公开的。因此,到了2015年,android上的hook资料已经遍地都是了,各种开源的hook框架也层出不穷,使用这些hook工具就可以轻松的hook native,jni和java层的函数。但这同样也带来了一些问题,新手想研究hook的时候因为资料和工具太多往往不知道如何下手,并且就算使用了工具成功的hook,也根本不知道原理是什么。因此笔者准备从hook的原理开始,配合开源工具循序渐进的介绍native,jni和java层的hook,方便大家对hook进行系统的学习。
0x02 Playing with Ptrace on Android
其实无论是hook还是调试都离不开ptrace这个system call,利用ptrace,我们可以跟踪目标进程,并且在目标进程暂停的时候对目标进程的内存进行读写。在linux上有一篇经典的文章叫《Playing with Ptrace》,简单介绍了如何玩转ptrace。在这里我们照猫画虎,来试一下playing with Ptrace on Android
。PS:这里的一部分内容借鉴了harry大牛在百度Hi写的一篇文章,可惜后来百度Hi关了,就失传了。不过不用担心,我这篇比他写的还详细。:)
首先来看我们要ptrace的目标程序,用来一直循环输出一句话”Hello,LiBieGou!”:
#include <stdio.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;
}
想要编译它非常简单,首先建立一个Android.mk文件,然后填入如下内容,让ndk将文件编译为elf可执行文件:
1
2
3
4
5
6
7
|
LOCAL_PATH := $(call my-
dir
)
include $(CLEAR_VARS)
LOCAL_MODULE := target
LOCAL_SRC_FILES := target.c
include $(BUILD_EXECUTABLE)
|
接下来我们写出hook1.c程序来hook target程序的system call,main函数如下:
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
|
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);
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;
}
|
首先要知道hook的目标的pid,这个用ps命令就能获取到。然后我们使用ptrace(PTRACE_ATTACH, pid, NULL, NULL)
这个函数对目标进程进行加载。加载成功后我们可以使用ptrace(PTRACE_SYSCALL, pid, NULL, NULL)
这个函数来对目标程序下断点,每当目标程序调用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) {
scno = regs->ARM_r7;
}
else
{
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指令中获取立即数,反向计算出调用号。
我们接着看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
26
27
28
29
30
31
32
|
void
hookSysCallBefore(pid_t pid)
{
struct
pt_regs regs;
int
sysCallNo = 0;
ptrace(PTRACE_GETREGS, pid, NULL, ®s);
sysCallNo = getSysCallNo(pid, ®s);
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);
}
}
|