本文博客地址:http://blog.csdn.net/qq1084283172/article/details/71037182
一、环境条件
Ubuntukylin 14.04.5 x64bit
 Android 4.4.4
 Nexus 5
二、Android内核源码的下载
 执行下面的命令,获取 Nexus 5手机 设备使用的芯片即获取Nexus 5手机设备内核源码的版本信息。
$ adb shell  
  
# 查看移动设备使用的芯片信息  
$ ls /dev/block/platform 根据google官方的参考文档以及上面获取的Nexus 5手机设备芯片信息得到Nexus 5手机的内核源码的下载地址,具体的执行下面的命令:
$ git clone https://aosp.tuna.tsinghua.edu.cn/kernel/msm.git (清华的源)
# 或者  
$ git clone https://android.googlesource.com/kernel/msm.git   (或者谷歌官方的源需要翻墙)  
$ cd msm
# 查看可以下载的Linux内核源码的版本  
$ git branch -a $ adb shell
# 查看移动设备的内核版本
$ cat /proc/version
Linux version 3.4.0-gd59db4e (android-build@vpbs1.mtv.corp.google.com) (gcc version 4.7 (GCC) ) #1 SMP PREEMPT Mon Mar 17 15:16:36 PDT 2014
# 下载对应的Android内核源码
$ git checkout d59db4e
# 下载编译工具链  
$ git clone https://aosp.tuna.tsinghua.edu.cn/platform/prebuilts/gcc/linux-x86/arm/arm-eabi-4.7/  
# 或者  
$ git clone https://android.googlesource.com/platform/prebuilts/gcc/linux-x86/arm/arm-eabi-4.7/    
# 添加arm-eabi-4.7到系统环境变量中
$ sudo gedit /etc/profile      
    
# 添加到环境变量配置文件/etc/profile中的内容    
export ANDROID_TOOLCHAIN=/home/fly2016/Desktop/Android4.4.4r1/android-4.4.4_r1/kernel_d59db4e/msm/arm-eabi-4.7  
export PATH=$PATH:${ANDROID_TOOLCHAIN}/bin/  
  
# 更新系统环境变量    
$ source /etc/profile     
  
# 测试是否配置成功  
$ arm-eabi-gdb  执行下面的命令,进行Android内核编译的配置,具体如下所示:
# 配置编译环境变量
$ export CROSS_COMPILE=arm-eabi-     
$ export ARCH=arm    
$ export SUBARCH=arm    
# 生成编译配置文件.config 
$ make hammerhead_defconfig
# 编辑编译配置文件.config 
$ gedit .config 对Android内核编译配置文件 .config 的修改如下:
# 需要修改
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内核
$ make -j4有关Android内核支持自定义内核模块加载和卸载的设置,如下所示:
重要提示:
CONFIG_MODVERSIONS 和 CONFIG_MODULE_SRCVERSION_ALL 这两个选项一定不能配置,需要去掉,否则的话:在Android系统加载自定义内核模块时会对内核模块进行代码的校验和版本的检查,容易出现如下的错误。具体的出错原因可以参考博文《内核模块编译时怎样绕过insmod时的版本检查》。
有关Android内核内存空间读写情况的设置,如下所示:
Android内核编译配置文件 .config 经过修改后的结果如下图:
对自定义内核模块加载支持的设置
对Android内核内存空间可读可写支持的设置
Android内核源码编译成功以后会生成内核引导模块文件 /msm/arch/arm/boot/zImage-dtb ,操作结果如下图:
四、替换Nexus 5 手机的内核并启动新内核
由于Nexus 5手机是高通的设备,因此可以执行下面的命令查找到启动分区 boot 的镜像位置。
$ adb shell  
  
# msm 代表高通的芯片,msm_sdcc.1是外接的SD卡挂载的目录,by-name指的是这个sd卡分区的名称  
$ ls -al /dev/block/platform/msm_sdcc.1/by-name/  
在 root权限下 ,将boot镜像所有的内容转储到Nexus 5手机的 /sdcard/boot.img 文件夹下,然后导出到 Ubuntu 14.04.5 x64bit 系统主机上,具体的执行下面的命令:
$ adb shell "su -c dd if=/dev/block/mmcblk0p19 of=/sdcard/boot.img"  
  
$ adb pull /sdcard/boot.img  ./  使用 abootimg工具 对导出的 boot.img 镜像文件进行解包,然后替换替换掉Android内核镜像文件,重新打包生成新的 boot.img文件,使用 fastboot工具 将新的boot.img镜像文件刷入到Nexus 5设备上引导内核启动,执行的命令如下:
# 安装刷机工具fastboot和adb
$ sudo apt-get install android-tools-adb android-tools-fastboot  
# 安装boot.img文件解包和打包工具abootimg  
$ sudo apt-get install build-essential abootimg  
# 对boot.img文件进行解包
$ abootimg -x boot.img  将编译生成的 新内核文件 msm/arch/arm/boot/zImage-dtb 替换掉原来的Android内核镜像文件,重新打包生成新的boot.img文件,然后重启Nexus 5手机进入刷机模式,用 “fastboot boot” 命令引导Android的新内核,执行下面的命令:
# 拷贝编译生成的Android内核文件zImage-dt到当前目录下
$ cp  ~/msm/arch/arm/boot/zImage-dtb .  
# 重新打包生成新的boot.img文件
$ abootimg --create myboot.img -f bootimg.cfg -k zImage-dtb -r initrd.img  
# 重启手机设备进入刷机模式 
$ adb reboot bootloader  
# 刷新boot.img文件,引导新的Android内核 
$ fastboot boot myboot.img  五、自定义加载Android内核模块Hook系统调用
在我们自定义的内核中,能用LKM加载自定义的代码到内核中,也可以访问/dev/kmem接口,用来修改Android内核的内存,Hook Android内核系统的调用都是基于这些前提条件实现的。有关Hook Android系统调用的原理和详细描述,可以参考前面的博文《Hook android系统调用研究(一)》。
在进行Hook Android系统调用之前,需要 先找到的是 sys_call_table的地址 。root权限下,通过 /proc/kallsyms 可以寻找到 sys_call_table的地址,具体的执行下面的命令:
# 获取root权限
$ adb shell su
# 查看默认值    
$ cat /proc/sys/kernel/kptr_restrict 
# root权限下,关闭symbol符号屏蔽
# 将 /proc/sys/kernel/kptr_restrict 重置为0,就可以打印显示出来 
$ echo 0 > /proc/sys/kernel/kptr_restrict  
# 查看修改后的值    
$ cat /proc/sys/kernel/kptr_restrict  
  
# 获取 sys_call_table的内存地址  
$ cat /proc/kallsyms | grep sys_call_table   
c000f884 T sys_call_table执行操作的结果如下图:
sys_call_table=0xc000f884 即为需要找到的Android系统调用表的内存基址,后面很多Android系统调用的系统函数调用地址都需要通过这个基址加函数的偏移计算出来。下面以使用 Android内核模块隐藏一个文件 为例子进行学习,先在设备上创建一个文件,方便我们能在后面隐藏它:
$ adb shell su
# 创建文件nowyouseeme并输入内容HelloWorld
$ echo HelloWorld > /data/local/tmp/nowyouseeme             
  
# 显示新创建文件的内容  
$ cat /data/local/tmp/nowyouseeme  
HelloWorld  在Android内核源码的头文件(arch/arm/include/asm/unistd.h)中找到Android所有系统调用的函数原型,然后创建一个挂钩Android系统调用 openat 的代码文件 kernel_hook.c:
#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;    
    
// 替换Android系统调用的新的new_openat函数    
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);    
    
  // 过滤,隐藏掉/data/local/tmp/nowyouseeme文件    
  if (strcmp(kbuf, "/data/local/tmp/nowyouseeme") == 0)     
  {    
    printk("Hiding file!\n");    
        
    return -ENOENT;    
  }    
    
  // 释放申请的内存空间    
  kfree(kbuf);    
    
  // 调用Android系统原来的系统调用openat函数    
  return real_openat(dirfd, pathname, flags);    
}    
    
    
// ########### 将被加载的Android内核模块 ###############    
int init_module(void) {    
    
  // 前面查找的内存地址    
  sys_call_table = (void*)0xc000f884;    
      
  // 获取Android系统的openat函数的调用地址    
  real_openat = (void*)(sys_call_table[__NR_openat]);    
    
  return 0;    
}      为了编译 kernel_hook.c文件 需要配置Android内核源码文件路径和交叉编译工具链路径,Makefile文件 的编写如下:
KERNEL=/home/fly2016/Android4.4.4r1/android-4.4.4_r1/kernel_d59db4e/msm  
  
TOOLCHAIN=arm-eabi-  
  
obj-m := kernel_hook.o  
  
all:  
	make ARCH=arm CROSS_COMPILE=$(TOOLCHAIN) -C $(KERNEL) M=$(shell pwd) CFLAGS_MODULE=-fno-pic modules  
  
clean:  
	make -C $(KERNEL) M=$(shell pwd) clean  Android内核模块与内核是紧密联系的,由于内核模块也处于Android内核空间,一旦发生问题就会直接造成严重的系统崩溃,因此 编译Android自定义内核模块时需要Android内核的相关信息 。一般的流程是:首先编译内核,之后根据得到的内核配置、符号信息等再编译自定义内核模块。这也意味着,当系统内核更新后,外部模块往往需要重新编译以兼容新内核。Linux系统下可通过 Dynamic Kernel Module Support (DKMS) 自动重新编译内核模块。
在 前面Andorid内核源码的编译配置环境下,继续直接执行 make 命令就可以编译 kernel_hook.c文件生成 内核模块文件kernel_hook.ko 。如果前面的Android内核编译环境丢失,可以通过在Android内核源码的根目录下,执行下面的命令进行 kernel_hook.c文件的编译。和其他的Linux发行版类似,在编译自定义内核模块之前无需编译整个Android内核,只要生成编译内核模块必要的脚本和头文件就行。
# 配置编译环境
$ export ARCH=arm   
$ export SUBARCH=arm  
$ export CROSS_COMPILE=arm-eabi- 
# 生成编译配置文件.config  
$ make hammerhead_defconfig  
# 修改编译配置文件.config 
$ gedit .config  
  
###################################################  
# 修改.config编译配置文件,保存、关闭
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  
###################################################  
# 生成内核编译需要的脚本和头文件 
$ make prepare modules_prepare
# 或者
$ make prepare    
$ make scripts   
###################################################
  
# 切换到工作目录编译 kernel_hook.c 文件  
$ make# 查看编译的Android内核模块文件的版本信息
$ modinfo kernel_hook.ko
$ adb shell rm /data/local/tmp/kernel_hook.ko
# 拷贝 kernel_hook.ko文件 到移动设备的/data/local/tmp/目录下
$ adb push kernel_hook.ko /data/local/tmp/  
# root权限下,加载自定义的内核模块kernel_hook.ko
$ adb shell su -c insmod /data/local/tmp/kernel_hook.ko  
# 查看自定义内核模块是否加载成功
$ adb shell lsmod  六、修改Android的系统调用表
通过访问 /dev/kmem接口 将Hook的  新函数地址new_openat 来覆盖sys_call_table中的原始函数openat的调用地址(这也能直接在内核模块中做,但是用/dev/kmem更加简单)。在参考了Dong-Hoon You的文章后,决定使用文件接口代替nmap(),因为经过试验发现会引起一些内核警告。用下面代码创建文件 kmem_util.c:
#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;
  // 对传入的参数的个数进行校验,不能少于3个
  if (argc < 3)
  {
    return 0;
  }
  // 打开内核文件接口/dev/kmem
  kmem = open("/dev/kmem", O_RDWR);
  // 判断文件是否打开成功
  if(kmem < 0)
  {
    perror("Error opening kmem");
    return 0;
  }
  // 获取输入的sys_call_table地址
  sscanf(argv[1], "%x", &sys_call_table);
  // 获取Android系统调用openat函数的偏移值
  sscanf(argv[2], "%d", &sys_call_number);
  // 获取新的new_openat的调用地址
  sscanf(argv[3], "%x", &addr_ptr);
  char buf[256];
  // 内存清零
  memset(buf, 0, 256);
  // 获取Android系统调用openat函数的原始调用地址
  read_kmem2(buf, sys_call_table+(sys_call_number*4), 4);
  // 打印Android系统调用openat函数的原始调用地址
  printf("Original value: %02x%02x%02x%02x\n", buf[3], buf[2], buf[1], buf[0]);
  // 将Android系统调用的openat函数的原始调用地址替换为新的new_openat的调用地址
  write_kmem2((void*)&addr_ptr,sys_call_table+(sys_call_number*4), 4);
  // 获取替换后的新new_openat函数的调用地址
  read_kmem2(buf,sys_call_table+(sys_call_number*4), 4);
  // 打印替换后的new_openat函数的调用地址
  printf("New value: %02x%02x%02x%02x\n", buf[3], buf[2], buf[1], buf[0]);
  // 关闭文件
  close(kmem);
  return 0;
}LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE    := kmem_util
LOCAL_SRC_FILES := kmem_util.c
LOCAL_CFLAGS += -pie -fPIE  
LOCAL_LDFLAGS += -pie -fPIE
include $(BUILD_EXECUTABLE)执行下面的命令,拷贝文件 kmem_util 到Nexus 5手机设备上。
# 拷贝文件kmem_util到手机设备上
$ adb push kmem_util /data/local/tmp/  
# 赋予文件可执行权限0755
$ adb shell chmod 755 /data/local/tmp/kmem_util  
$ grep -r "__NR_openat" arch/arm/include/asm/unistd.h
#define __NR_openat			(__NR_SYSCALL_BASE+322)
$ adb shell cat /proc/kallsyms | grep new_openat  
bf000000 t new_openat	[kernel_hook]
现在可以 覆盖Android内核内存中系统调用函数的调用地址了 ,kmem_util可执行程序 的用法如下:
./kmem_util <syscall_table_base_address> <offset> <new_fun_addr>
$ adb shell su -c /data/local/tmp/kmem_util c000f884 322 bf000000 
Original value: c01734b4
New value: bf000000
在root权限下,执行 /bin/cat 检查是否Hook Android系统调用函数openat成功。如果成功的话,执行 /bin/cat 不会显示我们隐藏的文件/data/local/tmp/nowyouseeme。具体的执行下面的命令:
$ adb shell su -c cat /data/local/tmp/nowyouseeme  
  
tmp-mksh: cat: /data/local/tmp/nowyouseeme: No such file or directory
七、总结
本篇博文是在前面博文《Hook android系统调用研究(一)》的基础上进行实践和查错总结写出来的,非常遗憾的是在最后关键验证Hook是否成功的步骤上再一次出现错误,猜测可能还是前面的Hook代码或者操作步骤的细节上有问题,没有注意到,后面有时间会再进行研究。这篇博文最原始的参考还是文章《hook Android系统调用的乐趣和好处》,虽然原文中有不少的错误,但是还是值得研究和实践,前面也写过这篇博文的实践但是各种错误和问题,只实践了一半就继续不下去了,本篇博文中一一将前面的遇到的问题都给解决了,遗憾的是还是失败了~
来源:oschina
链接:https://my.oschina.net/u/4265475/blog/4462230