Hooking Android System Calls for Pleasure and Benefit
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的更多相关文章
- MIT 6.828 JOS学习笔记18. Lab 3.2 Part B: Page Faults, Breakpoints Exceptions, and System Calls
现在你的操作系统内核已经具备一定的异常处理能力了,在这部分实验中,我们将会进一步完善它,使它能够处理不同类型的中断/异常. Handling Page Fault 缺页中断是一个非常重要的中断,因为我 ...
- 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 ...
- 解决appium自带的Chromedriver版本和设备Android System Webview版本不一致的问题
报错信息 selenium.common.exceptions.WebDriverException: Message: An unknown server-side error occurred w ...
- Linux System Calls Hooking Method Summary
http://www.cnblogs.com/LittleHann/p/3854977.html http://www.cnblogs.com/cozy/articles/3175615.html h ...
- android system.img
哥们要我做些模拟包,给过来的是mtk的底包,需要从system.img中提取部分文件. 网上一找资料,说是yaffs2文件系统,同时以前做linux的时候也是用yaffs2,感觉碰到老朋友了,不管三七 ...
- 图解Android - System Service 概论 和 Android GUI 系统
通过 图解Android - Binder 和 Service 一文中,我们已经分析了Binder 和 Service的工作原理.接下来,我们来简要分析Android 系统里面都有哪些重要的Servi ...
- Android Capture Android System Audio
项目需要获取播放视频的实时音量值,最简捷的方法是监听音频输出端,取得音频输出流,再进行转换. 调查时,首先找到这篇博客: http://blog.csdn.net/jinzhuojun/article ...
- Android System Property 解析
一 System Property 今天在折腾HDMI 显示,为Setting提供接口,遇到非常多跟Android系统属性相关的问题.因此,顺便分析和总结一些. android的代码中大量 ...
- 安卓系统广播暴露设备信息-Android System Broadcasts Expose Device Information
Android device details are being exposed to running applications via Wi-Fi broadcasts in the mobile ...
随机推荐
- Json解析异常处理方式(JSONException: Value of type java.lang.String cannot be converted to JSONObject)
有一次从服务器解析获取到的Json字符串突然报了这个异常,由于这个json是从 php页面上推送过来的,当时就查是不是由于编码问题引起的,所以就上网搜了,网上大部分都是说由于utf-8的bom头引起的 ...
- R语言——绘图函数深入学习
利用R自带数据集 通过data()函数可以查看R自带数据集. > data() 返回以下结果,每一条记录都是一个数据,键入相应的数据名称可以查看具体信息. Data sets in packag ...
- centos和ubuntu下使用cron设置定时任务
1.启动cron工具[ps:使用root权限] centos启动cron两种方式 a) /etc/init.d/crond start b) service crond start ubuntu启动c ...
- mysql建库
CREATE DATABASE db_name DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci; CREATE DATABASE 的语法: CRE ...
- 关于Bean\Entity\Model\POJO的一些个人理解
本文没有长篇累牍的,严格的,标准的表述,只是我在开发过程中,读书过程中的一些个人理解,可能不太准备,但是我觉得应该是最方便初学者理解的吧? 一.Bean 对于Bean而言,我的理解是只要是Java的类 ...
- Fastjson简单使用方法
一.简单数据的序列化 pubic class UserInfo implements Serializable{ private String name; private int age; publi ...
- 基于Redis的在线用户列表解决方案
前言: 由于项目需求,需要在集群环境下实现在线用户列表的功能,并依靠在线列表实现用户单一登陆(同一账户只能一处登陆)功能: 在单机环境下,在线列表的实现方案可以采用SessionListener来完成 ...
- java正则表达式获取指定HTML标签的指定属性值
package com.mmq.regex; import java.util.ArrayList; import java.util.List; import java.util.regex.Mat ...
- Socket.io官方聊天室DEMO的学习笔记
照着Socket.io官方的聊天室代码敲了一遍,遇到了一个奇怪的问题: 每次点击SEND按钮的时候,都会重新刷新页面. 在点击页面的一瞬间,看到了正在加载jquery的提示, 然后以为是jquery用 ...
- CodeForces 616A Comparing Two Long Integers
水题 #include<cstdio> #include<cstring> #include<algorithm> using namespace std; +; ...