翻译: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=Y
CONFIG_MODULE_UNLOAD=y
CONFIG_STRICT_MEMORY_RWX=N
CONFIG_DEVMEM=Y
CONFIG_DEVKMEM=Y
CONFIG_KALLSYMS=Y
CONFIG_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/mmcblk0p24
lrwxrwxrwx root     root              1970-08-30 22:31 aboot -> /dev/block/mmcblk0p6
lrwxrwxrwx root     root              1970-08-30 22:31 abootb -> /dev/block/mmcblk0p11
lrwxrwxrwx 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_table
c000f984 T sys_call_table

这是我们仅需要写入的内核地址,其他的可以通过便宜计算出来。

我们将使用内核模块隐藏一个文件。让我们在设备创建一个文件,以便我们能在后面隐藏它:

1
2
3
$ adb shell "su -c echo ABCD > /data/local/tmp/nowyouseeme"             
$ adb shell cat /data/local/tmp/nowyouseeme
ABCD

最后时候写内核模块了。为了文件隐藏,我们需要挂钩用来打开文件的一个系统调用。有很多关于打开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.o
all:
        make ARCH=arm CROSS_COMPILE=$(TOOLCHAIN)/bin/arm-eabi- -C $(KERNEL) M=$(shell pwd) CFLAGS_MODULE=-fno-pic modules
clean:
        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 lsmod
kernel_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_openat
bf000000 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 bf000000
Original value: c017a390
New value: bf000000

假设一切正常,/bin/cat应该不能看见这个文件。

1
2
berndt@osboxes:~/Desktop/Module$ adb shell su -c cat /data/local/tmp/nowyouseeme
tmp-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系统调用的乐趣和好处相关推荐

  1. Hook android系统调用研究(一)

    本文的博客链接:http://blog.csdn.net/qq1084283172/article/details/55657300 一.Android内核源码的编译环境 系统环境:Ubuntu 14 ...

  2. Xposed: 勾住(Hook) Android应用程序对象的方法,实现AOP

    Xposed Xposed能够勾住(Hook) Android应用程序对象的方法,实现AOP,一个简单的例子: public class WebViewHook implements IXposedH ...

  3. Linux上监控应用程序启动 (hook execve系统调用)

    对于linux x86-64平台,hook普通的系统调用是一件比较简单的事情,可以看hook系统调用完整实例讲解.但是对于execve.fork.clone等这些系统调用的hook却并没那么简单了. ...

  4. Android编译源码hook,Hook Android C代码(Cydia Substrate)

    本帖最后由 PJ头狼 于 2016-9-26 18:00 编辑 之前对于Cydia Substrate这个框架的使用及如何hook到Android的Java层,是在学习了鬼哥的Hook Android ...

  5. hook android api伪造设备信息做刷量

    hook android api伪造设备信息做刷量 概述 Android平台上app的统计数据都是基于Android的设备信息的,比如首次使用(激活),活跃(日活跃DAU,月活跃MAU)等都需要根据设 ...

  6. 玩转Hook——Android权限管理功能探讨(一)

    随着Android设备上的隐私安全问题越来越被公众重视,恶意软件对用户隐私,尤其是对电话.短信等私密信息的威胁日益突出,各大主流安全软件均推出了自己的隐私行为监控功能,在root情况下能有效防止恶意软 ...

  7. 使用Cydia Substrate Hook Android Java世界

    从来没接触过Android的HOOK,在看雪上找到了一篇HOOK 的文章,但是太复杂了,应该是本地环境问题,测试不成功. 后来搜到Cydia Substrate,看了几篇文章,进入官网查看了一下文档, ...

  8. 用hook android 微信,【第一篇】【安卓微信】HOOK微信发消息,当HOOK遇上HOOK。

    前人栽树,后人吃桃. 准备入坑 安卓 xposed hook 微信(及时通信),好吧,第一步当然是论坛里面搜索啦,像掷骰子.计步器什么的都是小孩子过家家入门级别的,当然是要搞就搞(及时通信).微信 a ...

  9. Frida Hook Android App 进阶用法之 Java 运行时

    FridaHookAndroid 本文旨在覆盖使用 Frida 对 Android App 进行 hook 的绝大多数场景.文章提到的所有代码以及被测 App,详见:https://github.co ...

最新文章

  1. PostgreSQL_case when
  2. 【机器视觉】 measure_projection算子
  3. 【神经网络八股扩展】:自制数据集
  4. 国科大高级人工智能-总结
  5. Gradle插件学习笔记(二)
  6. c语言程序设计二级考试哪些题型,计算机二级考试题型及分值
  7. JavaScript笔记2———js的数据类型
  8. 求出数组最大值的方法
  9. Cousera- software security
  10. 2022-07微软漏洞通告
  11. vCenter 6.0 web访问503错误:服务不可用,无法连接endpoint,
  12. Python计算机视觉-仿射扭曲简单实例
  13. 数据可视化发挥流程的价值——江汽物流数据监控平台建设经验
  14. 暴走英雄坛服务器维护到什么时候,各位侠士久等了,暴走英雄坛1.8.1新版本更新前瞻一览。我们...
  15. Linux-lsxxx
  16. 百度推广系列之广告词编写必杀技
  17. 9.7. Pattern Matching
  18. 小白入门网络安全,需要学习哪些内容?
  19. EXCEL调用(执行)VBA代码(过程或宏)教程
  20. 绘画新手怎么画好结构素描

热门文章

  1. 菲律宾当地的蜂窝网络情况
  2. 2022仿拼多多拼团PHP系统源码+可封装APP/多商家入驻
  3. P2392 kkksc03考前临时抱佛脚 (DFS)
  4. Linux网络编程(IP地址,端口,通信协议,网络字节序)
  5. 塑壳断路器用考虑启动电流么_针对塑壳断路器智能脱扣器的解决方案
  6. php登录时使用优盘,U盘的正确使用方法
  7. Eloquent JavaScript 笔记 十九:Node.js
  8. 互联网时代,企业薪酬管理逐渐向数智化转型
  9. 存储调研:MooseFS分布式文件系统体系结构
  10. Python-Django毕业设计快递寄取微信小程序(程序+Lw)