The Android kernel is a powerful ally to the reverse engineer. While regular Android apps are hopelessly restricted and sandboxed, you - the reverser - can customize and alter the behavior of the operating system and kernel any way you wish. This gives you a really unfair advantage, because most integrity checks and anti-tampering features ultimately rely on services performed by the kernel. Deploying a kernel that abuses this trust, and unabashedly lies about itself and the environment, goes a long way in defeating most reversing defenses that malware authors (or normal developers) can throw at you.

Android apps have several ways of interacting with the OS environment. The standard way is through the APIs of the Android Application Framework. On the lowest level however, many important functions, such as allocating memory and accessing files, are translated into perfectly old-school Linux system calls. In ARM Linux, system calls are invoked via the SVC instruction which triggers a software interrupt. This interrupt calls the vector_swi() kernel function, which then uses the system call number as an offset into a table of function pointers (a.k.a. sys_call_table on Android).

The most straightforward way of intercepting system calls is injecting your own code into kernel memory, then overwriting the original function in the system call table to redirect execution. Unfortunately, current stock Android kernels enforce memory restrictions that prevent this from working. Specifically, stock Lollipop and Marshmallow kernel are built with the CONFIG_STRICT_MEMORY_RWX option enabled. This prevents writing to kernel memory regions marked as read-only, which means that any attempts to patch kernel code or the system call table result in a segmentation fault and reboot. A way to get around this is to build your own kernel: You can then deactivate this protection, and make many other useful customizations to make reverse engineering easier. If you're reversing Android apps on a regular basis, building your own reverse engineering sandbox is a no-brainer.

Note: The steps below work on Ubuntu 14.04 with Android NDK 4.8. Personally I'm still traumatized from multiple failed attempts of getting this to work on Mac OS. I recommend taking that route only if you're a masochist - everyone else is better served by using an Ubuntu VM.

Building the Kernel
For hacking purposes, I recommend using an AOSP-supported device. Google’s Nexus smartphones and tablets are the most logical candidates – kernels and system components built from the AOSP run on them without issues. Alternatively, Sony’s Xperia series is also known for its openness. To build the AOSP kernel you need a toolchain (set of programs to cross-compile the sources) as well as the appropriate version of the kernel sources. Follow Google's instructions to identify the correct git repo and branch for a given device and Android version.

For example, to get kernel sources for Lollipop that are compatible with the Nexus 5, you need to clone the "msm" repo and check out one the "android-msm-hammerhead" branch (hammerhead is the codename of the Nexus 5, and yes, finding the right branch is a confusing process). Once the sources are downloaded, create the default kernel config with the command make hammerhead_defconfig (or whatever_defconfig, depending on your target device).

$ 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

To enable system call hooking, I recommend adding loadable module support, exposing the /dev/kmem interface, and exporting the global kernel symbols. Don't forget to deactivate strict memory protection as well. Most of these options should already exist in the config file - simply set them to the values recommended below.

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

Once you are finished editing save the .config file. Optionally, you can now create a standanlone toolchain for cross-compiling the kernel and later tasks. To create a toolchain for Android 5.1, run make-standalone-toolchain.sh from the Android NDK package as follows:

$ cd android-ndk-rXXX
$ build/tools/make-standalone-toolchain.sh --arch=arm --platform=android- --install-dir=/tmp/my-android-toolchain

Set the CROSS_COMPILE environment variable to point to your NDK directory and run "make" to build the kernel.

$ export CROSS_COMPILE=/tmp/my-android-toolchain/bin/arm-eabi-
$ make

When the build process (hopefully) completes successfully, you will find the bootable kernel image at arch/arm/boot/zImage-dtb.

Booting Your Shiny New Kernel

Before booting into the new Kernel, make a copy of the original boot image from your device. Look up the location of the boot partition as follows:

root@hammerhead:/dev # ls -al /dev/block/platform/msm_sdcc./by-name/
lrwxrwxrwx root root -- : DDR -> /dev/block/mmcblk0p24
lrwxrwxrwx root root -- : aboot -> /dev/block/mmcblk0p6
lrwxrwxrwx root root -- : abootb -> /dev/block/mmcblk0p11
lrwxrwxrwx root root -- : boot -> /dev/block/mmcblk0p19
(...)
lrwxrwxrwx root root -- : userdata -> /dev/block/mmcblk0p28

Then, dump the whole thing into a file:

$ adb shell "su -c dd if=/dev/block/mmcblk0p19 of=/data/local/tmp/boot.img"
$ adb pull /data/local/tmp/boot.img

Next, extract the ramdisk as well as some information about the structure of the boot image. There are various tools that can do this - I used Gilles Grandou's abootimg tool. Install the tool and run the following command on your boot image:

$ abootimg -x boot.img 

This should create the files bootimg.cfg, initrd.img and zImage (your original kernel) in the local directory.

You can now use fastboot to test the new kernel. The "fastboot boot" command allows you to run the kernel without actually flashing it (once you’re sure everything works, you can make the changes permanent with fastboot flash - but you don't have to). Restart the device in fastboot mode with the following command:

$ adb reboot bootloader

Then, use the "fastboot boot" command to boot Android with the new kernel. In addition to the newly built kernel and the original ramdisk, specify the kernel offset, ramdisk offset, tags offset and commandline (use the values listed in your previously extracted bootimg.cfg).

$ fastboot boot zImage-dtb initrd.img --base  --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"

The system should now boot normally. To quickly verify that the correct kernel is running, navigate to Settings->About phone and check the “kernel version” field.

If everything went well, this should show the version string of your custom-built kernel. Pat yourself on the back / howl triumphantly, you're now ready for primetime!

Syscall Hooking using Kernel Modules

System call hooking allows us to attack any anti-reversing defenses that depend on functionality provided by the kernel. With our custom kernel in place, we can now use a LKM to load additional code into the kernel. We also have access to the /dev/kmem interface, which we can use to patch kernel memory on-the-fly. This is a classical Linux rootkit technique and has been described for Android by Dong-Hoon You [1].

The first piece of information we need is the address of sys_call_table. Fortunately, it is exported as a symbol in the Android kernel (iOS reversers are not so lucky). We can look up the address in the /proc/kallsyms file:

$ 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

This is the only memory address we need for writing our kernel module - everything else can be calculated using offsets taken from the Kernel headers (hopefully you didn't delete them yet?).

In this howto, we're going to use a Kernel module to hide a file. Let's create a file on the device so we can hide it later:

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

Finally it's time to write the kernel module. For file hiding purposes, we'll need to hook one of the system calls used to open (or check for the existence of) files. Actually, there many of those - open, openat, access, accessat, facessat, stat, fstat, and more. For now, we'll only hook the openat system call - this is the syscall used by the "/bin/cat" program when accessing a file, so it should be servicable enough for a demonstration.

You can find the function prototypes for all system calls in the kernel header file arch/arm/include/asm/unistd.h. Create a file called kernel_hook.c with the following code:

#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(,GFP_KERNEL);
len = strncpy_from_user(kbuf,pathname,); if (strcmp(kbuf, "/data/local/tmp/nowyouseeme") == ) {
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 ; }

To build the kernel module, you need the kernel sources and a working toolchain - since you already built a complete kernel before, you are all set. Create a Makefile with the following content:

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

Run "make" to compile the code – this should create the file kernel_hook.ko. Copy the kernel_hook.ko file to the device and load it with the insmod command. Verify with the lsmod command that the module has been loaded successfully.

 make
(...)
$ adb push kernel_hook.ko /data/local/tmp/
[%] /data/local/tmp/kernel_hook.ko
$ adb shell su -c insmod /data/local/tmp/kernel_hook.ko
$ adb shell lsmod
kernel_hook [permanent], Live 0xbf000000 (PO)

Patching the System Call Table

Now, we’ll access /dev/kmem to overwrite the original function pointer in sys_call_table with the address of our newly injected function (this could have been done directly in the kernel module as well, but using /dev/kmem gives us an easy way to toggle our hooks on and off). I have adapted the code from Dong-Hoon You’s Phrack article [1] for this purpose - however, I used the file interface instead of mmap(), as I found the latter to cause kernel panics for some reason. Create a file called kmem_util.c with the following code:

#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) == -) { perror("Write error");
exit();
}
return;
} int main(int argc, char *argv[]) { off_t sys_call_table;
unsigned int addr_ptr, sys_call_number; if (argc < ) {
return ;
} kmem=open("/dev/kmem",O_RDWR); if(kmem<){
perror("Error opening kmem"); return ;
} sscanf(argv[], "%x", &sys_call_table); sscanf(argv[], "%d", &sys_call_number);
sscanf(argv[], "%x", &addr_ptr); char buf[];
memset (buf, , ); read_kmem2(buf,sys_call_table+(sys_call_number*),);
printf("Original value: %02x%02x%02x%02x\n", buf[], buf[], buf[], buf[]);
write_kmem2((void*)&addr_ptr,sys_call_table+(sys_call_number*),);
read_kmem2(buf,sys_call_table+(sys_call_number*),);
printf("New value: %02x%02x%02x%02x\n", buf[], buf[], buf[], buf[]);
close(kmem); return ;
}

Build kmem_util.c using the prebuilt toolchain and copy it to the device. Note that from Android Lollipop, all executables must be compiled with PIE support:

$ /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 /data/local/tmp/kmem_util

Before we start messing with kernel memory we still need to know the correct offset into the system call table. The openat system call is defined in unistd.h which is found in the kernel sources:

$ grep -r "__NR_openat" arch/arm/include/asm/unistd.h
#define __NR_openat (__NR_SYSCALL_BASE+322)

The final piece of the puzzle is the address of our replacement-openat. Again, we can get this address from /proc/kallsyms.

$ adb shell cat /proc/kallsyms | grep new_openat
bf000000 t new_openat [kernel_hook]

Now we have everything we need to overwrite the sys_call_table entry. The syntax for kmem_util is:

./kmem_util <syscall_table_base_address> <offset> <func_addr>

The following command patches the openat system call table to point to our new function.

berndt@osboxes:~/Host/Research/SoftToken/Android/Kernel/msm$ adb shell su -c /data/local/tmp/kmem_util c000f984  bf000000
Original value: c017a390
New value: bf000000

Assuming that everything worked, /bin/cat should now be unable to "see" the file.

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

Voilá! The file "nowyouseeme" is now somewhat hidden from the view of all usermode processes (note that there's a lot more you need to do to properly hide a file, including hooking stat(), access(), and other system calls, as well as hiding the file in directory listings).

File hiding is of course only the tip of the iceberg: You can accomplish a whole lot of things, including bypassing many root detection measures, integrity checks, and anti-debugging tricks. You can find some additional examples in the "case studies" section of my recent paper.

https://www.vantagepoint.sg/blog/82-hooking-android-system-calls-for-pleasure-and-benefit

http://www.phrack.org/issues/68/6.html#article

Hooking Android System Calls for Pleasure and Benefit的更多相关文章

  1. MIT 6.828 JOS学习笔记18. Lab 3.2 Part B: Page Faults, Breakpoints Exceptions, and System Calls

    现在你的操作系统内核已经具备一定的异常处理能力了,在这部分实验中,我们将会进一步完善它,使它能够处理不同类型的中断/异常. Handling Page Fault 缺页中断是一个非常重要的中断,因为我 ...

  2. The behavior of App killed or restored by Android System or by users

    What's the behavior of App killed or restored by Android System or by users? First, user kills the a ...

  3. 解决appium自带的Chromedriver版本和设备Android System Webview版本不一致的问题

    报错信息 selenium.common.exceptions.WebDriverException: Message: An unknown server-side error occurred w ...

  4. Linux System Calls Hooking Method Summary

    http://www.cnblogs.com/LittleHann/p/3854977.html http://www.cnblogs.com/cozy/articles/3175615.html h ...

  5. android system.img

    哥们要我做些模拟包,给过来的是mtk的底包,需要从system.img中提取部分文件. 网上一找资料,说是yaffs2文件系统,同时以前做linux的时候也是用yaffs2,感觉碰到老朋友了,不管三七 ...

  6. 图解Android - System Service 概论 和 Android GUI 系统

    通过 图解Android - Binder 和 Service 一文中,我们已经分析了Binder 和 Service的工作原理.接下来,我们来简要分析Android 系统里面都有哪些重要的Servi ...

  7. Android Capture Android System Audio

    项目需要获取播放视频的实时音量值,最简捷的方法是监听音频输出端,取得音频输出流,再进行转换. 调查时,首先找到这篇博客: http://blog.csdn.net/jinzhuojun/article ...

  8. Android System Property 解析

    一 System Property       今天在折腾HDMI 显示,为Setting提供接口,遇到非常多跟Android系统属性相关的问题.因此,顺便分析和总结一些. android的代码中大量 ...

  9. 安卓系统广播暴露设备信息-Android System Broadcasts Expose Device Information

    Android device details are being exposed to running applications via Wi-Fi broadcasts in the mobile ...

随机推荐

  1. UVALive 2323 Modular Multiplication of Polynomials(模拟)

    这是一个相对简单的模拟,因为运算规则已经告诉了我们,并且比较简单,不要被吓到…… 思路:多项式除以另外一个多项式,如果能除,那么他的最高次一定被降低了,如果最高次不能被降低,那说明已经无法被除,就是题 ...

  2. CentOS下编译安装Apache2(新)

    官网下载apache,apr, apr-util,pcre httpd-2.4.16.tar.gz http://httpd.apache.org/download.cgi#apache24 apr- ...

  3. PAT (Advanced Level) 1102. Invert a Binary Tree (25)

    简单题. #include<cstdio> #include<cstring> #include<cmath> #include<vector> #in ...

  4. script 两则

    script1: PATH=/usr/local/bin:/usr/bin:$PATH:. . $HOME/utility/macro/macro.env OVO_DIR=/tmp LOGFILE=$ ...

  5. applicationContext.xml文件配置模板

    <?xml version="1.0" encoding="gb2312"?><!--  Spring配置文件的DTD定义-->< ...

  6. python WEB接口自动化测试之requests库详解

    由于web接口自动化测试需要用到python的第三方库--requests库,运用requests库可以模拟发送http请求,再结合unittest测试框架,就能完成web接口自动化测试. 所以笔者今 ...

  7. Python3基础 list(enumerate()) 将一个列表的每一个元素转换成 带索引值的元组

    镇场诗: 诚听如来语,顿舍世间名与利.愿做地藏徒,广演是经阎浮提. 愿尽吾所学,成就一良心博客.愿诸后来人,重现智慧清净体.-------------------------------------- ...

  8. Codeforces Round #367 (Div. 2)D. Vasiliy's Multiset (字典树)

    D. Vasiliy's Multiset time limit per test 4 seconds memory limit per test 256 megabytes input standa ...

  9. 《JavaScript语言精髓与编程实践》读书笔记

    JavaScript语言精髓与编程实践读书笔记 function v1(v1){ v1 = 100; alert('v1:'+v1); } function v2(name){ v1.apply(th ...

  10. DBNull.value

    判断某一个datarow的cell是否有值需要进行两步判断 1.dr["field_name"]!=DBNull.value&&dr["field_nam ...