hook Android系统调用的乐趣和好处
翻译:myswsun
0x00 前言
Android的内核是逆向工程师的好伙伴。虽然常规的Android应用被限制和沙盒化,逆向工程师可以按自己希望自定义和改变操作系统和内核中行为。这给了你不可多得的优势,因为大部分完整性校验和防篡改功能都依赖内核的服务。部署这种可以滥用信任并自我欺骗的内核和环境,可以避免走很多歪路。
Android应用有几种方式和系统环境交互。标准方法是通过安卓应用框架的API函数。然而在底层,很多重要的函数,例如分配内存和访问文件,都是被转化为linux的系统调用。在ARM linux中,系统调用的调用是通过SVC指令触发软件中断实现的。中断调用内核函数vector_swi(),用系统调用号作为一个函数指针表的偏移(如安卓上的sys_call_table)。
拦截系统调用最直接的方法是注入你的代码到内核内存中,覆盖系统调用表中的原始函数地址重定向执行。不幸的是,目前Android内核加强了内存限制阻止了这种操作。具体来说,内核是在启用CONFIG_STRICT_MEMORY_RWX选项的情况下构建的。这阻止了向只读内核内存区写入,意味着任何试图修改系统代码或者系统调用表的操作都将导致崩溃和重启。绕过这个的一个方法就是自己编译内核:能够禁用这种保护,做更多自定义的修改有利于逆向分析。如果你按常规方法逆向Android应用,构建你自己的逆向沙盒是不明智的。
注意:下面的步骤是最好在Ubuntu 14.04环境中用Android NDK 4.8完成。我个人在Mac上面失败了很多次才完成。我推荐用Ubuntu虚拟机,除非你是个受虐狂。
0x01 构建内核
为了hack的目的,我推荐使用支持AOSP的设备。Google的Nexus智能手机和平板电脑是最合理的选择,从AOSP构建的内核和系统组件在上面运行没有问题。另外,索尼的Xperia也可以。为了构建AOSP内核,需要一系列工具(交叉编译工具)和相应的内核源代码。根据谷歌的指导,确定了正确的git仓库和分支。
例如,获取匹配Nexus 5的Lollipop的内核源码,需要克隆“msm”仓库并检出一个分支“android-msm-hammerhead”。一旦源码下载了,用make hammerhead_defconfig(或者whatever_defconfig,取决于你的设备)命令创建默认内核配置。
|
1
2
3
4
5
6
7
|
$ git clone https://android.googlesource.com/kernel/msm.git$ cd msm$ git checkout origin/android-msm-hammerhead-3.4-lollipop-mr1 $ export ARCH=arm $ export SUBARCH=arm$ make hammerhead_defconfig$ vim .config |
为了启动系统调用挂钩功能,我推荐增加可加载模块的支持,由/dev/kmem接口支持,同时导出全局内核符号表。不要忘了禁用内存保护。这些选项的值在配置文件中已经存在,只需要简单的设置以下值。
|
1
2
3
4
5
6
7
|
CONFIG_MODULES=YCONFIG_MODULE_UNLOAD=yCONFIG_STRICT_MEMORY_RWX=NCONFIG_DEVMEM=YCONFIG_DEVKMEM=YCONFIG_KALLSYMS=YCONFIG_KALLSYMS_ALL=Y |
一旦你完成了编辑配置文件。或者,您现在可以创建一个独立的工具链,用于交叉编译内核和以后的任务。为了给Android 5.1创建一个工具链,运行Android NDK包的make-standalone_toolchain.sh:
|
1
2
|
$ cd android-ndk-rXXX$ build/tools/make-standalone-toolchain.sh --arch=arm --platform=android-21 --install-dir=/tmp/my-android-toolchain |
设置CROSS_COMPILE环境变量,指向NDK目录,并运行make构建内核。
|
1
2
|
$ export CROSS_COMPILE=/tmp/my-android-toolchain/bin/arm-eabi- $ make |
当构建过程完成后,能在arch/arm/boot/zImage-dtb找到引导内核模块。
0x02 启动新内核
在启动新内核前,备份一个你设备的原始引导映像。找到启动分区的位置:
|
1
2
3
4
5
6
7
|
root@hammerhead:/dev # ls -al /dev/block/platform/msm_sdcc.1/by-name/ lrwxrwxrwx root root 1970-08-30 22:31 DDR -> /dev/block/mmcblk0p24lrwxrwxrwx root root 1970-08-30 22:31 aboot -> /dev/block/mmcblk0p6lrwxrwxrwx root root 1970-08-30 22:31 abootb -> /dev/block/mmcblk0p11lrwxrwxrwx root root 1970-08-30 22:31 boot -> /dev/block/mmcblk0p19(...)lrwxrwxrwx root root 1970-08-30 22:31 userdata -> /dev/block/mmcblk0p28 |
然后,将所有的内容转储到一个文件中:
|
1
2
|
$ adb shell "su -c dd if=/dev/block/mmcblk0p19 of=/data/local/tmp/boot.img"$ adb pull /data/local/tmp/boot.img |
接下来,提取ramdisk以及有关引导映像结构的一些信息。有很多工具可以做到这个,我使用Gilles Grandou的abootimg工具。安装工具并执行以下的命令:
|
1
|
$ abootimg -x boot.img |
在本地目录会创建bootimg.cfg、initrd.img和zImage(原始的内核)文件。
能用快速引导测试新内核。“fastboot boot”命令允许测试内核。在fastboot模式下用下面命令重启设备:
|
1
|
$ adb reboot bootloader |
然后,用“fastboot boot”命令引导Android的新内核。除了新建的内核和原始ramdisk,还需指定内核偏移量,ramdisk偏移量,标签偏移量和命令行(使用在之前提取的bootimg.cfg中列出的值)。
|
1
|
$ fastboot boot zImage-dtb initrd.img --base 0 --kernel-offset 0x8000 --ramdisk-offset 0x2900000 --tags-offset 0x2700000 -c "console=ttyHSL0,115200,n8 androidboot.hardware=hammerhead user_debug=31 maxcpus=2 msm_watchdog_v2.enable=1" |
现在应该手动重启。为了快速验证内核=正确运行了,通过校验Settings->About phone中的“内核版本”的值。

如果一切运行良好,将显示自定义构建的版本字符串。
0x03 用内核模块hook系统调用
hook系统调用能让我们绕过任何依赖内核提供的反逆向防御措施。在我们自定义的内核中,我们能用LKM加载例外的代码到内核中。我们也可以访问/dev/kmem接口,用来修改内核内存。这是个经典的linux rootkit技术。

首先需要的是sys_call_table的地址。幸运的是它被Android内核导出了符号(iOS没这么幸运)。我们在/proc/kallsyms寻找地址:
|
1
2
3
|
$ adb shell "su -c echo 0 > /proc/sys/kernel/kptr_restrict"$ adb shell cat /proc/kallsyms | grep sys_call_tablec000f984 T sys_call_table |
这是我们仅需要写入的内核地址,其他的可以通过便宜计算出来。
我们将使用内核模块隐藏一个文件。让我们在设备创建一个文件,以便我们能在后面隐藏它:
|
1
2
3
|
$ adb shell "su -c echo ABCD > /data/local/tmp/nowyouseeme" $ adb shell cat /data/local/tmp/nowyouseemeABCD |
最后时候写内核模块了。为了文件隐藏,我们需要挂钩用来打开文件的一个系统调用。有很多关于打开open, openat, access, accessat, facessat, stat, fstat,等等。现在我们只需要挂钩openat系统调用,这个系统调用被“/bin/cat”程序访问文件时使用。
你能在内核头文件中(arch/arm/include/asm/unistd.h)找到所有系统调用的函数原型。用下面代码创建一个文件kernel_hook.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
|
#include <linux/kernel.h>#include <linux/module.h>#include <linux/moduleparam.h>#include <linux/unistd.h>#include <linux/slab.h>#include <asm/uaccess.h>asmlinkage int (*real_openat)(int, const char __user*, int);void **sys_call_table;int new_openat(int dirfd, const char __user* pathname, int flags){ char *kbuf; size_t len; kbuf=(char*)kmalloc(256,GFP_KERNEL); len = strncpy_from_user(kbuf,pathname,255); if (strcmp(kbuf, "/data/local/tmp/nowyouseeme") == 0) { printk("Hiding file!\n"); return -ENOENT; } kfree(kbuf); return real_openat(dirfd, pathname, flags);}int init_module() { sys_call_table = (void*)0xc000f984; real_openat = (void*)(sys_call_table[__NR_openat]);return 0;} |
为了构建内核模块,需要内核资源和工具链,因为之前编译了内核,一切就绪。用以下内容创建makefile文件:
|
1
2
3
4
5
6
7
|
KERNEL=[YOUR KERNEL PATH]TOOLCHAIN=[YOUR TOOLCHAIN PATH]obj-m := kernel_hook.oall: make ARCH=arm CROSS_COMPILE=$(TOOLCHAIN)/bin/arm-eabi- -C $(KERNEL) M=$(shell pwd) CFLAGS_MODULE=-fno-pic modulesclean: make -C $(KERNEL) M=$(shell pwd) clean |
运行make编译代码,得到文件kernel_hook.ko。复制这个文件到设备并用insmod命令加载它。用lsmod命令验证模块是否加载成功。
|
1
2
3
4
5
6
7
|
$ make(...)$ adb push kernel_hook.ko /data/local/tmp/[100%] /data/local/tmp/kernel_hook.ko$ adb shell su -c insmod /data/local/tmp/kernel_hook.ko$ adb shell lsmodkernel_hook 1160 0 [permanent], Live 0xbf000000 (PO) |
0x04 修改系统调用表
现在,我们访问/dev/kmem来用我们注入的函数地址来覆盖sys_call_table中的原始函数的指针(这也能直接在内核模块中做,但是用/dev/kmem更加简单)。我参考了Dong-Hoon You的文章,但是我用文件接口代替nmap(),因为我发现会引起一些内核警告。用下面代码创建文件kmem_util.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
|
#include <stdio.h> #include <stdlib.h>#include <fcntl.h> #include <asm/unistd.h> #include <sys/mman.h>#define MAP_SIZE 4096UL#define MAP_MASK (MAP_SIZE - 1)int kmem;void read_kmem2(unsigned char *buf, off_t off, int sz){ off_t offset; ssize_t bread; offset = lseek(kmem, off, SEEK_SET); bread = read(kmem, buf, sz); return; }void write_kmem2(unsigned char *buf, off_t off, int sz) { off_t offset; ssize_t written; offset = lseek(kmem, off, SEEK_SET); if (written = write(kmem, buf, sz) == -1) { perror("Write error"); exit(0); } return;}int main(int argc, char *argv[]) { off_t sys_call_table; unsigned int addr_ptr, sys_call_number; if (argc < 3) { return 0; } kmem=open("/dev/kmem",O_RDWR); if(kmem<0){ perror("Error opening kmem"); return 0; } sscanf(argv[1], "%x", &sys_call_table); sscanf(argv[2], "%d", &sys_call_number); sscanf(argv[3], "%x", &addr_ptr); char buf[256]; memset (buf, 0, 256); read_kmem2(buf,sys_call_table+(sys_call_number*4),4); printf("Original value: %02x%02x%02x%02x\n", buf[3], buf[2], buf[1], buf[0]); write_kmem2((void*)&addr_ptr,sys_call_table+(sys_call_number*4),4); read_kmem2(buf,sys_call_table+(sys_call_number*4),4); printf("New value: %02x%02x%02x%02x\n", buf[3], buf[2], buf[1], buf[0]); close(kmem); return 0; } |
构建kmem_util.c并复制到设备中。注意因为是Android Lollipop,所以所有的可执行文件必须是PIE支持编译的。
|
1
2
3
|
$ /tmp/my-android-toolchain/bin/arm-linux-androideabi-gcc -pie -fpie -o kmem_util kmem_util.c$ adb push kmem_util /data/local/tmp/$ adb shell chmod 755 /data/local/tmp/kmem_util |
在我们开始修改内核内存前,我们需要知道的是系统调用表正确的偏移位置。这个openat调用在unistd.h中定义:
|
1
2
|
$ grep -r "__NR_openat" arch/arm/include/asm/unistd.h#define __NR_openat (__NR_SYSCALL_BASE+322) |
最后一个难题是我们替换openat函数的地址。我们能从/proc/kallsyms得到这个地址:
|
1
2
|
$ adb shell cat /proc/kallsyms | grep new_openatbf000000 t new_openat [kernel_hook] |
现在我们可以覆盖系统调用表的入口了。Kmem_util语法如下:
|
1
|
./kmem_util <syscall_table_base_address> <offset> <func_addr> |
用下面的命令修改系统调用表指向我们的新函数。
|
1
2
3
|
berndt@osboxes:~/Host/Research/SoftToken/Android/Kernel/msm$ adb shell su -c /data/local/tmp/kmem_util c000f984 322 bf000000Original value: c017a390New value: bf000000 |
假设一切正常,/bin/cat应该不能看见这个文件。
|
1
2
|
berndt@osboxes:~/Desktop/Module$ adb shell su -c cat /data/local/tmp/nowyouseemetmp-mksh: cat: /data/local/tmp/nowyouseeme: No such file or directory |
现在通过所有的用户进程已经无法看见隐藏的文件了(但是为了隐藏文件有许多需要做的,包括挂钩stat,access和其他系统调用,还有在文件夹中隐藏)。
文件隐藏的教程只是一个小例子:你可以完成一大堆事,包括绕过启动检测,完整性校验和反调试技巧。
尽管代码覆盖使用符号执行是一个好的方法,但它是个复杂的任务。路径遍历意味着内存消耗,并且一些情况下要计算的表达式太过复杂。目前,判定器非常慢,判定表达式非常慢。
0x05 总结
hook系统调用对于Android逆向分析是一个有用的技术。为了使用它,需要用自定义内核构建自己的逆向工程沙盒。这个文章介绍了如何在Nexus5运行Lollipop,其他AOSP设备也是类似的。
转载链接:http://bobao.360.cn/learning/detail/3432.html
原文链接:https://www.vantagepoint.sg/blog/82-hooking-android-system-calls-for-pleasure-and-benefit
hook Android系统调用的乐趣和好处的更多相关文章
- Hook android系统调用的实践
本文博客地址:http://blog.csdn.net/qq1084283172/article/details/71037182 一.环境条件 Ubuntukylin 14.04.5 x64bit ...
- Hook android系统调用研究(一)
本文的博客链接:http://blog.csdn.net/qq1084283172/article/details/55657300 一.Android内核源码的编译环境 系统环境:Ubuntu 14 ...
- 使用Cydia Substrate 从Native Hook Android Native世界
同系列文章: 使用Cydia Substrate 从Native Hook Android Java世界 使用Cydia Substrate Hook Android Java世界 一.建立工程 手机 ...
- 玩转Hook——Android权限管理功能探讨(一)
随着Android设备上的隐私安全问题越来越被公众重视,恶意软件对用户隐私,尤其是对电话.短信等私密信息的威胁日益突出,各大主流安全软件均推出了自己的隐私行为监控功能,在root情况下能有效防止恶意软 ...
- 使用Cydia Substrate 从Native Hook Android Java世界
这里介绍了如何使用Cydia Substrate Hook安卓Java世界.这篇文章介绍如何从Native中Hook 安卓Java世界. 手机端配置见之前文章. 一.建立工程 建立一个Android工 ...
- 使用Cydia Substrate Hook Android Java世界
从来没接触过Android的HOOK,在看雪上找到了一篇HOOK 的文章,但是太复杂了,应该是本地环境问题,测试不成功. 后来搜到Cydia Substrate,看了几篇文章,进入官网查看了一下文档, ...
- Linux、Android系统调用从上层到底层的调用路径浅析
参考: https://blog.csdn.net/liuhangtiant/article/details/85149369 http://blog.sina.com.cn/s/blog_79433 ...
- Xposed框架Hook Android应用的所有类方法打印Log日志
本文博客地址:https://blog.csdn.net/QQ1084283172/article/details/80954759 在进行Android程序的逆向分析的时候,经常需要Android程 ...
- Android系统调用
android 中intent是经常要用到的.不管是页面牵转,还是传递数据,或是调用外部程序,系统功能都要用到intent. 在做了一些intent的例子之后,整理了一下intent,希望对大家有用. ...
随机推荐
- 9.Vue之webpack打包基础---模块化思维
主要内容: 1. 什么是模块化思维? 2. ES6包的封装思想 一.什么是模块化思维呢? 现实工作中, 一个项目可能会有多个人同时开发. 然后, 将所有人开发的内容, 合并到一个文件中. 比如: 1 ...
- Microsoft Teams 最新功能发布:协作篇
正在进行的2021年的Microsoft Ignite大会,发布了一系列跟Microsoft Teams相关的新功能,英文介绍请参考 https://techcommunity.microsoft.c ...
- iot漏洞mips汇编基础
1 基础概念 MIPS(Microprocessor without Interlocked Piped Stages architecture),是一种采取精简指令集(RISC)的处理架构,由MIP ...
- Java 获取小程序openid(基于SpringBoot)
Java 获取小程序openid(基于SpringBoot) 官方文档 wx.login 1.引入Request封装依赖 <!--Request依赖--> <dependency&g ...
- 2019 GDUT Rating Contest III : Problem C. Team Tic Tac Toe
题面: C. Team Tic Tac Toe Input file: standard input Output file: standard output Time limit: 1 second M ...
- python inspect库
一.介绍 inspect模块用于收集python对象的信息,可以获取类或函数的参数的信息,源码,解析堆栈,对对象进行类型检查等等. inspect模块主要提供了四种用处: 对是否是模块.框架.函数进行 ...
- 攻防世界 csaw2013reversing2 CSAW CTF 2014
运行程序 flag显示乱码 IDA打开查看程序逻辑 1 int __cdecl __noreturn main(int argc, const char **argv, const char **en ...
- warpperspective 透视变化的opencv实现
warpperspective 透视变化的opencv2.0实现 1st-------2nd | | | | | |3rd-------4th 原始代码 cv::Mat sr ...
- shell字符串处理总结
1. 字符串切片 1.1 基于偏移量取字符串 返回字符串 string 的长度 ${#string} 示例 [root@centos8 script]#str=" I Love Python ...
- 13个精选的React JS框架
如果你正在使用 React.js 或 React Native 创建用户界面,可以试一试本文推荐的这些框架. React.js 和 React Native 是流行的用户界面(UI)开发平台,且都是开 ...