频道栏目
首页 > 安全 > 系统安全 > 正文
系统安全之如何编译Android内核Hook系统调用
2017-02-18 14:27:20           
收藏   我要投稿

系统安全之如何编译Android内核Hook系统调用。Android内核是逆向工程师手上强大的武器。通常Android应用程序会被系统加以严格控制并沙箱化运行,但我们却可以通过修改操作系统内核使系统按照我们规定的方式运行。大多数完整性检查和防篡改功能最终都依赖于系统内核所提供的服务,恶意软件作者(或普通开发人员)可以通过部署一个自定义的内核,让针对性的逆向防护方法无计可施。

Android应用程序有多种方法可以与系统环境进行交互。常用方法是通过Android应用程序框架提供的API接口。在最底层,许多重要功能(如内存分配和文件访问)会被转换为纯Linux系统调用来实现。在ARM Linux系统中,系统调用是通过SVC指令所触发的软件中断来调用的,软件中断将系统调用号作为函数指针表(在Android上是sys_call_table表)的偏移量,调用vector_swi()内核函数来完成调用过程。

截断系统调用的最直接方法是将自己的代码注入内核内存空间,覆盖系统调用表中的原始函数空间以便重定向运行。然而目前Android内核对内存空间做了严格的限制,导致这种方式无法正常工作。具体来说,Lollipop和Marshmallow内核是在启用了CONFIG_STRICT_MEMORY_RWX选项的情况下编译构建的,这意味着任何对内核代码或者系统调用表的修改将会导致内存分段错误,最终导致系统重启。解决这个问题的办法之一是构建自定义内核,通过自定义内核,我们可以停用内存保护机制,并进行其他自定义配置,使Android上的逆向工程事半功倍。如果你经常逆向Android应用程序,那么构建自己的逆向工程沙箱应该不费吹灰之力。

注意:以下步骤已在安装了Android NDK 4.8环境的Ubuntu 14.04上测试成功,我在Mac OS系统上还未能成功复现,建议你使用Ubuntu虚拟机完成这些工作。

编译Android内核

我推荐使用AOSP支持的设备进行操作,谷歌的Nexus手机和平板是最合适的候选设备(基于AOSP构建的内核和系统组件可以在它们身上正常运行),索尼的Xperia系列也可以作为备选。为了构建AOSP内核,你需要一组工具链(用来交叉编译源代码)以及对应版本的内核源代码。请按照Google官方说明文档,为你所选定的设备选择正确的代码仓库分支以及正确的Android系统版本。

比如,要获取与Nexus 5兼容的Lollipop内核源代码,你需要克隆“msm”代码库,导出“android-msm-hammerhead”分支(hammerhead是Nexus 5的代号,找到正确的分支是相当费神的一个工作)。下载源码后,使用“make hammerhead_defconfig”命令(或者xxx_defconfig命令,这取决于你所选定的设备)来创建默认内核配置。

$ 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

要想启用系统调用hook功能,我建议添加可加载模块功能、开放/dev/kmem接口并导出全局内核符号,同时记得要停用内存保护功能。这些选项大部分都可以在配置文件中找到,将它们设置为如下的值即可。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

修改后保存.config文件。现在你可以创建一个独立工具链,用来完成后续任务或对内核进行交叉编译。要创建Android 5.1的工具链,请从Android NDK包中运行make-standalone-toolchain.sh脚本,如下所示:

$ 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”命令来编译内核。

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

$ make

当编译过程成功完成后,你可以在“arch/arm/boot/zImage-dtb”目录找到可引导的内核镜像。

引导自定义内核

在引导自定义内核之前,可以先从设备中备份原始引导镜像。进入引导分区位置,如下所示:

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

导出所有内容到一个文件中:

$ 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工具。安装该工具并在你的启动镜像上运行以下命令:

$ abootimg -x boot.img

这条命令会在本地目录中创建bootimg.cfg、initrd.img以及zImage(你的原始内核)文件。

现在你可以使用fastboot来测试新内核。“fastboot boot“命令允许你在未刷入内核前引导它(一旦你确认内核工作正常后,你可以使用”fastboot flash”命令刷入内核并保存更改)。使用以下命令以fastboot模式重启设备:

$ adb reboot bootloader

下一步,使用“fastboot boot“命令启动Android新内核。除了指定新的内核以及原始的ramdisk,你还需要指定内核偏移量、ramdisk偏移量、标签偏移量以及控制台信息(这些信息可以在之前提取的bootimg.cfg中获得)。

$ 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"

现在系统应该可以正常引导。要快速验证系统是否正在运行正确的内核,你可以到“设置—关于手机“中检查”内核版本“字段。

一切顺利的话,内核版本信息应该显示你定制的内核版本字符串。接下来是Hook的重头戏。

使用内核模块Hook系统调用

Hook系统调用可以让我们突破基于内核功能的反逆向保护机制。通过使用自定义内核,我们可以采用LKM将更多代码加载到内核空间,我们还可以访问/dev/kmem接口来实时修改内核内存空间。这是一项经典的Linux rootkit技术,在Android中已经有人进行了相关介绍[1]。

我们首先需要知道sys_call_table的地址,这可以从Android内核的符号表中找到(iOS逆向人员就没那么走运了)。我们可以在“/proc/kallsyms“文件中查找地址:

$ 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

这是我们在编写内核模块中所需要的唯一一个地址信息,其他的地址信息都可以从内核头部获取的偏移量计算得出。另外,你也可以从内核模块中动态获取到sys_call_table的地址(代码样例)

在本文中,我们将使用内核模块来隐藏一个文件。首先让我们在设备上创建一个文件:

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

$ adb shell cat /data/local/tmp/nowyouseeme

ABCD

接下来是编写内核模块工作。为了达到文件隐藏目的,我们需要hook用来打开文件(或者检查文件是否存在)的系统调用。很多调用可以完成这个工作,比如open/openat/accessat/facessat/stat/fstat等,为了演示方便,在这里我们只hook openat系统调用(它是“/bin/cat“程序在访问文件时使用的系统调用)。

你可以在内核头文件“arch/arm/include/asm/unistd.h“中找到所有的系统调用函数原型。使用以下代码创建一个名为”kernel_hook.c“的文件:

#include

#include

#include

#include

#include

#include

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;

}
						
点击复制链接 与好友分享!回本站首页
上一篇:探索Windows 10的CFG机制
下一篇:在Linux中使用C语言实现控制流保护(CFG)
相关文章
图文推荐
文章
推荐
点击排行

关于我们 | 联系我们 | 广告服务 | 投资合作 | 版权申明 | 在线帮助 | 网站地图 | 作品发布 | Vip技术培训 | 举报中心

版权所有: 红黑联盟--致力于做实用的IT技术学习网站