Android反调试——从源码入手

2019-04-25 约 6060 字 预计阅读 13 分钟

声明:本文 【Android反调试——从源码入手】 由作者 se**** 于 2019-04-25 08:50:00 首发 先知社区 曾经 浏览数 119 次

感谢 se**** 的辛苦付出!

绕过TracePid反调试二

第一篇文章是直接修改二进制文件尝试绕过TracerPID反调试

前言

接受了评论的建议, 但是因为之前手机还没好加上没试过直接修改kernel的源码, 所以花了很多时间(都是环境惹的祸)。还有因为这个接触了shell code, 真的是一言难尽。事先说明, 下面的环境准备都是在国外的服务器上直接运行的, 难免有一些命令是需要翻墙的, 所以你实际上要用的命令可能跟我的有点不同(如果可以直接用代理之类的, 应该没多大影响)。

开发环境

Ubuntu 18.10(建议用Ubuntu 16.04, 至少2MB内存)
Android 6.0.1
Nexus 5

Ubuntu环境搭建

Java环境准备

下文Java环境搭建都是基于Ubuntu 18.10的, 如果你尝试过不能在自己的Ubuntu环境下使用, 可以到google上找找看, 应该能找到你想要的。如果不是为了之后Android源码调试, 只是为了修改kernel文件可以先不搭建Java环境。

下载JDK

  1. 为了下载最新的JDK, 可以现在Ubuntu的命令行里面先输入javac, 会显示下面的内容, 按照它提供的命令即可下载最新的JDK。
  2. 很不快乐的是Java 6和Java 7需要有Oracle的账号, 所以只要去Orcle注册一个账号, 就可以下载Java 7Java 6了(Java 7是压缩包, Java 6是一个二进制文件)。文章末尾附有两个jdk文件的链接
    #### 安装JDK
    因为先安装了Java 8在路径/usr/lib/jvm目录下, 所以将文件文件jdk-6u45-linux-x64.binjdk-7u80-linux-x64.tar.gz都用mv命令移到上述目录下。
    root@vultr:~/[jdk 6存放的位置]# mv jdk-6u45-linux-x64.bin /usr/lib/jvm/
    root@vultr:~/[jdk 7存放的位置]# mv jdk-7u80-linux-x64.tar.gz /usr/lib/jvm/
    解压jdk 6, 进入到/usr/lib/jvm目录下, 先给该文件读写的权限, 之后运行该二进制文件就会在当前目录下生成一个新的文件夹。
    root@vultr:~/[jdk 7存放的位置]# cd /usr/lib/jvm
    root@vultr:/usr/lib/jvm# chmod +x jdk-6u45-linux-x64.bin
    root@vultr:/usr/lib/jvm# ./jdk-6u45-linux-x64.bin
    解压jdk 7
    root@vultr:/usr/lib/jvm# tar -zxvf jdk-7u80-linux-x64.tar.gz
    为了我们能够在Ubuntu里面自由自在地切换Java版本, 我们可以先写个脚本将jdk-6和jdk-7添加到候选项中。先输入命令vim alternativeJava.sh, 并将下面的内容直接复制到alternativsjava.sh文件里。
    #!/bin/sh
    JAVAHOME=$1 
    if [ -d $JAVAHOME ];then
         sudo update-alternatives --install /usr/bin/java java $JAVAHOME/bin/java 300
         sudo update-alternatives --install /usr/bin/javac javac $JAVAHOME/bin/javac 300
         sudo update-alternatives --install /usr/bin/jar jar $JAVAHOME/bin/jar 300
         sudo update-alternatives --install /usr/bin/javah javah $JAVAHOME/bin/javah 300
         sudo update-alternatives --install /usr/bin/javap javap $JAVAHOME/bin/javap 300
    else
         echo "Wrong input"
         exit 0
    fi
    用命令chmod+x alternativsjava.sh, 给脚本添加权限, 否则脚本会不能运行。输入命令./alternativsjava.sh /usr/lib/jvm/jdk1.7.0_80之后(脚本后面添加的路径是你jdk解压后的文件路径),用sudo update-alternatives --config java(切换java版本命令)进行检验。

    ### 准备Android源码运行环境
    以下内容仅编译内核, 并假设你还没有下载整个 AOSP源。因为我要编译的内核版本过旧, 所以用的都是旧的教程, 如果有要编译新的内核的要求的话, 可以看看这两篇文章, Compiling an Android kernel with Clang编译内核
    #### 安装所需的软件包
    在Ubuntu 14.04中如果下载git出问题, 可以看看这篇文章How To Install Git on Ubuntu 14.04

输入下述命令。

$ sudo apt-get install git-core gnupg flex bison gperf build-essential zip curl zlib1g-dev gcc-multilib g++-multilib libc6-dev-i386 lib32ncurses5-dev x11proto-core-dev libx11-dev lib32z-dev libgl1-mesa-dev libxml2-utils xsltproc unzip

下载源码

在下载之前先获取手机的内核版本, 从下面的信息可知道手机内核的git short commit idcf10b7e

因为内核版本比较旧, 所以按照旧版的官方内核编译手册来, 而不是按照新版的内核编译手册来。如果内核比较新的, 还是直接用repo吧!接下来可以从官方手册上看到, 我需要的kernel源代码位于哪个branch, 然后从github上clone下来。

输入命令, 先将msm这个项目clone下来。(这一步花的时间可能会有一点点长)
$ git clone https://android.googlesource.com/kernel/msm.git

因为我用的是国外的服务器, 所以可以直接从google服务器下下来。如果是自己搭建的机器且觉得开代理太麻烦的话, 可以换成下面的命令。
$ git clone https://aosp.tuna.tsinghua.edu.cn/kernel/msm.git

将msm从github上clone下来之后, 会发现里面是个空的, 只有一个.git仓库。进入msm目录下, 用git branch -a查看分支。(我的文件路径跟图片下的不符, 实际上应该是/AndroidKernel/msm)

现在就要用到我们之前获取的short commit id(显示的是实际的commit id的前7位)了, 直接检出我们需要的代码的分支。
git branch -r --contains <your short commit id>

从上面的图片我们可以知道, 本地实际上只有master这一个分支, 这时候我们需要做的事就是在远程分支的基础上再分一个本地分支。

$ git checkout -b android-msm-hammerhead-3.4-marshmallow-mr3 origin/android-msm-hammerhead-3.4-marshmallow-mr3

安装GCC交叉编译器

之前不是很能理解为什么官方网站没说要下载这个东西, 之后在How to Build a Custom Android Kernel这篇文章里面看到。因为一般我们需要编译的kernel源代码都是基于arm架构编译运行的, 所以直接放在我们64位的Ubuntu里面是不合适的。也可以跟官方一样直接通过USB连接手机直接进行调试。

~/AndroidKernel执行如下命令(下载现在Linux环境下的arm编译接口)。这个编译接口尽量别尝试arm-eabi-4.8以上的, 因为旧的内核和交叉编译器不匹配会出现很多麻烦, 例如现在Google已经弃用了gcc, 在最新的交叉编译器里面只能用clang, 即使make操作加了参数CC=clang也会在出现很多很麻烦的报错。所以我这里为了匹配, 用的是旧的交叉编译器。

$ git clone https://android.googlesource.com/platform/prebuilts/gcc/linux-x86/arm/arm-eabi-4.6

在这里尝试了一下清华的AOSP源, 也是可以直接用的。参考贴出来的google的url, 直接将里面的https://android.googlesource.com/全部改成https://aosp.tuna.tsinghua.edu.cn/即可。详情可参考Android 镜像使用帮助

$ git clone https://aosp.tuna.tsinghua.edu.cn/platform/prebuilts/gcc/linux-x86/arm/arm-eabi-4.6

添加环境变量

添加环境变量总共有两种方法, 一种是短期的, 开机重启之后就会失效, 一种是长期的。

第一种:在~/AndroidKernel目录下执行以下命令。

$ export PATH=~/AndroidKernel/arm-eabi-4.6/bin:$PATH

第二种:在~/.bashrc中添加环境变量
$ vim ~/.bashrc

之后在文件末尾添加export PATH=<交叉编译API存放的文件根目录>/arm-linux-androideabi-4.9/bin:$PATH

为了让这个配置立马生效, 我们可以用下面的命令
$ source ~/.bashrc

修改源码

修改前准备+修改源码

如果觉得不想知道为什么要修改base.carray.c文件, 可以跳过现在这一段, 直接从下一段“修改msm/fs/proc/base.c文件”开始看就好了。

我将msm/fs/proc目录下的文件都下载到本地(都是先修改完成的), 安装了Source Insight来分析源码。proc文件, 是以文件系统的方式为访问系统内核的操作提供接口, 动态从系统内核中读出所需信息的。这也就说明, 我们想要修改的TracePid也是通过这个文件中获取到的。

我们想获取进程信息的时候, 一般会输出下述内容。

>cat /proc/self/status
  Name:   cat
  State:  R (running)
  Tgid:   5452
  Pid:    5452
  PPid:   743
  TracerPid:      0                     (2.4)
  Uid:    501     501     501     501
  Gid:    100     100     100     100
  FDSize: 256
  Groups: 100 14 16
  VmPeak:     5004 kB
  VmSize:     5004 kB
  VmLck:         0 kB
  VmHWM:       476 kB
  VmRSS:       476 kB
  VmData:      156 kB
  VmStk:        88 kB
  VmExe:        68 kB
  VmLib:      1412 kB
  VmPTE:        20 kb
  VmSwap:        0 kB
  Threads:        1
  SigQ:   0/28578
  SigPnd: 0000000000000000
  ShdPnd: 0000000000000000
  SigBlk: 0000000000000000
  SigIgn: 0000000000000000
  SigCgt: 0000000000000000
  CapInh: 00000000fffffeff
  CapPrm: 0000000000000000
  CapEff: 0000000000000000
  CapBnd: ffffffffffffffff
  Seccomp:        0
  voluntary_ctxt_switches:        0
  nonvoluntary_ctxt_switches:     1

在上述Status信息中我们需要关注的两个部分, 一个是State字段, 一个是TracePid字段。因为这两个字段都可反映出进程是否被监测。详情可参考 proc.txt line 209line 215

proc手册查找/proc/[pid]/stat, 我们可以知道Status是在fs/proc/array.c定义的, 我们就先从array.c入手。

先打开查看调用关系的窗口, View->Panels->Relation Windows

array.c文件中搜索status, 找到函数proc_pid_status, 之后查看该函数调用与被调用的信息。

Relation Window中双击get_task_state函数, 就找到了我们想找的TracePid。这个就是我们要修改的第一处了。

TracePid 通常都是对父进程 pid 进行检测, 这里将 ppid 改为 0, 这样不管是否为调试状态, TracePid 都无法检测出。修改的结果如下

180 seq_printf(m,
181            "State:\t%s\n"
182            "Tgid:\t%d\n"
183            "Pid:\t%d\n"
184            "PPid:\t%d\n"
185            "TracerPid:\t%d\n"
186            "Uid:\t%d\t%d\t%d\t%d\n"
187            "Gid:\t%d\t%d\t%d\t%d\n",
188            get_task_state(p),
189            task_tgid_nr_ns(p, ns),
190            pid_nr_ns(pid, ns),
               //修改部分
191            ppid, 0,
               //修改结束
192            cred->uid, cred->euid, cred->suid, cred->fsuid,
193            cred->gid, cred->egid, cred->sgid, cred->fsgid);

上面的代码段中的get_task_state()函数引起了我的注意, 这个函数应该是获取state的函数。用鼠标选中该函数之后, 右手边的Relation Window会显示该函数所在的位置, 在该窗口双击之后跳转。

在上图中, 看到了明显用来存放状态的数组task_state_array, 选中该数组之后, 同样的在Relation Window中双击跳转。

将原来状态表中的Tt都修改为S这样就避免了该状态位反映出被监测的状态。

R  Running
S  Sleeping in an interruptible wait
D  Waiting in uninterruptible disk sleep
Z  Zombie
T  Stopped (on a signal) or (before Linux 2.6.33)
   trace stopped
t  Tracing stop (Linux 2.6.33 onward)
W  Paging (only before Linux 2.6.0)
X  Dead (from Linux 2.6.0 onward
x  Dead (Linux 2.6.33 to 3.13 only)
K  Wakekill (Linux 2.6.33 to 3.13 only)
W  Waking (Linux 2.6.33 to 3.13 only)
P  Parked (Linux 3.9 to 3.13 only)

array.c我们已经修改完毕了, 这时候我们就要修改其他部分了。在导入Project之后, 我们在整个proc文件中搜索关键词trace。先按照下图打开Project Search Bar, 并在其中输入trace

我们会发现搜索的结果都是在base.c文件中(下图出现的第一个包含trace关键词的函数是我已经修改过的)。

在检查完有trace关键词的代码没发现有用的, 就在base.c文件中搜索关键词status

Ctrl+F输入关键词之后没找到, 就通过下图的向下搜索的功能一个个定位, 前面的部分都没找到自己想要找的函数段。

直到找到了关键的部分, 选中函数proc_pid_status, 在右边Relation Window中继续找我们想要的关键函数。

但是很遗憾, 在proc_pid_status函数中跟了很多相关的函数仍然没找到我们想要的。那我们就回到我们最开始的地方。这部分最上面的标识是pid_entry。顺着这个部分往下看, 我们就找到了proc_tid_stat函数, 选中该函数之后我们可以找到do_task_stat函数。

接下来, 我们就好好看看这个函数里面有什么。在右边的Relation Window中关注到一个有state关键词的函数, 双击之后跳转到该函数调用的位置。

定位到上图那一行之后, 分别跟了state关键词和get_task_state函数, 都没有发现什么(base.c是进程运行之前要做的准备工作, 从get_task_state函数可直接回到之前修改的array.c文件。但因为已修改完成, 所以就留在base.c文件中没有继续定位了)。

现在看到这段函数之中大部分都用到了变量task, 所以只好将task作为关键词用笨办法来一个一个定位。最后找到了wchan, 真的眼泪都掉下来。(因为事先知道要改这个部分)

看了Android反调试技术整理与实践这篇文章才知道为什么要修改带有wchan关键词的函数。因为/proc/pid/wchan/proc/pid/task/pid/wchan在调试状态下,里面内容为ptrace_stop, 非调试的状态下为ep_poll。所以也可能会泄露正在被调试的信息, 所以我们直接在Project中查找wchan关键词, 就定位到函数proc_pid_wchan

定位结束之后我们进行如下修改, 到这里我们的修改就彻底结束了。

修改msm/fs/proc/base.c文件

在Ubuntu中编辑文件vim msm/fs/proc/base.c, 定位函数proc_pid_wchan(大概在268行左右)

267 static int proc_pid_wchan(struct task_struct *task, char *buffer)
268 {
269        unsigned long wchan;
270        char symname[KSYM_NAME_LEN];
271
272        wchan = get_wchan(task);
273
274        if (lookup_symbol_name(wchan, symname) < 0)
275                if (!ptrace_may_access(task, PTRACE_MODE_READ))
276                        return 0;
277                else
278                        return sprintf(buffer, "%lu", wchan);
279        else
280                return sprintf(buffer, "%s", symname);
281 }

改成下面的内容

static int proc_pid_wchan(struct task_struct *task, char *buffer)
{
    unsigned long wchan;
    char symname[KSYM_NAME_LEN];

    wchan = get_wchan(task);

    if (lookup_symbol_name(wchan, symname) < 0)
        if (!ptrace_may_access(task, PTRACE_MODE_READ))
            return 0;
        else
            return sprintf(buffer, "%lu", wchan);
    else
    {   // 更改的内容
        if(strstr(symname,"trace"))
            return sprintf(buffer, "%s", "sys_epoll_wait");
        return sprintf(buffer, "%s", symname);
    }
}

修改msm/fs/proc/array.c文件

用vim对msm/fs/proc/array.c进行编辑, 先修改第一处

134 static const char * const task_state_array[] 135 = {
136        "R (running)",          /*   0 */
137        "S (sleeping)",         /*   1 */
138        "D (disk sleep)",       /*   2 */
139        "T (stopped)",          /*   4 */
140        "t (tracing stop)",     /*   8 */
141        "Z (zombie)",           /*  16 */
142        "X (dead)",             /*  32 */
143        "x (dead)",             /*  64 */
144        "K (wakekill)",         /* 128 */
145        "W (waking)",           /* 256 */
146 };

修改之后的结果如下

134 static const char * const task_state_array[] 135 = {
136        "R (running)",          /*   0 */
137        "S (sleeping)",         /*   1 */
138        "D (disk sleep)",       /*   2 */
            //修改的部分
139        "S (sleeping)",         /*   4 */
140        "S (sleeping)",         /*   8 */
141        "Z (zombie)",           /*  16 */
142        "X (dead)",             /*  32 */
143        "x (dead)",             /*  64 */
144        "K (wakekill)",         /* 128 */
145        "W (waking)",           /* 256 */
146 };

修改array.c的第二处

180 seq_printf(m,
181            "State:\t%s\n"
182            "Tgid:\t%d\n"
183            "Pid:\t%d\n"
184            "PPid:\t%d\n"
185            "TracerPid:\t%d\n"
186            "Uid:\t%d\t%d\t%d\t%d\n"
187            "Gid:\t%d\t%d\t%d\t%d\n",
188            get_task_state(p),
189            task_tgid_nr_ns(p, ns),
190            pid_nr_ns(pid, ns),
191            ppid, tpid,
192            cred->uid, cred->euid, cred->suid, cred->fsuid,
193            cred->gid, cred->egid, cred->sgid, cred->fsgid);

修改的结果为

180 seq_printf(m,
181            "State:\t%s\n"
182            "Tgid:\t%d\n"
183            "Pid:\t%d\n"
184            "PPid:\t%d\n"
185            "TracerPid:\t%d\n"
186            "Uid:\t%d\t%d\t%d\t%d\n"
187            "Gid:\t%d\t%d\t%d\t%d\n",
188            get_task_state(p),
189            task_tgid_nr_ns(p, ns),
190            pid_nr_ns(pid, ns),
               //修改部分
191            ppid, 0,
192            cred->uid, cred->euid, cred->suid, cred->fsuid,
193            cred->gid, cred->egid, cred->sgid, cred->fsgid);

源码的编译运行

在编译运行之前, 我们需要先用echo $PATH确认交叉编译器在PATH中。

按照下面来进行配置

$ export ARCH=arm #指明目标体系架构,arm、x86、arm64
$ export SUBARCH=arm
$ cd msm #进入内核所在目录
$ make hammerhead_defconfig # 设备名_defconfig
#指定使用的交叉编译器的前缀
$ make ARCH=arm CROSS_COMPILE=arm-eabi- -j4 ##如果没有gcc的环境, 就增加了CC=clang

可以从编译内核这篇文章中找到相应的设备名。

在编译的过程中, 遇到了下面的报错。

这时候需要修改kernel/timeconst.pl文件, 用vim kernel/timeconst.pl编辑该文件, 定位到下述代码。

372     @val = @{$canned_values{$hz}};           
373     if (!defined(@val)) {                     
374         @val = compute_values($hz);           
375     }                                         
376     output($hz, @val);

if (!defined(@val))改为if (!@val), 再编译一次就可以了。

接下来, 就按照上图提示进入目录arch/arm/boot

重打包boot.img

为了防止发生不可挽回的刷砖错误, 在刷机之前, 一定要按照尝试绕过TracePid反调试将boot.img进行备份。

准备好bootimg-tools工具

因为我之前Windows环境是准备好了的, 就直接在本地解决下面的任务。

在Ubuntu环境中, 输入下面命令就准备完成了

$ git clone https://github.com/pbatard/bootimg-tools.git
$ make
$ cd mkbootimg

Windows环境下进入[MinGW安装的目录]]\MinGW\msys\1.0目录下, 双击msys.bat

提取出来的boot.img放到mkbootimg文件夹下, 之后的步骤不管是哪个环境下都是相同的。

用unmkbootimg解包

在MinGW输入命令./unmkbootimg -i boot.img, 如果是Ubuntu, 直接去掉前面的./执行命令。

我们获得了rebuild需要输入的指令, 之后要rebuild的时候要修改一下才能用。

To rebuild this boot image, you can use the command:
  mkbootimg --base 0 --pagesize 2048 --kernel_offset 0x00008000 --ramdisk_offset 0x02900000 --second_offset 0x00f00000 --tags_offset 0x02700000 --cmdline 'console=ttyHSL0,115200,n8 androidboot.hardware=hammerhead user_debug=31 maxcpus=2 msm_watchdog_v2.enable=1' --kernel kernel --ramdisk ramdisk.cpio.gz -o boot.img

替换kernel重新打包

刷入bootnew.img

在手机开机的情况下, 进入bootnew.img存放的目录输入下述命令。

$ adb reboot bootloader
$ astboot flash boot bootnew.img
$ fastboot reboot

测试

现在到了见证奇迹的时刻了

参考文章或其他链接

Ubuntu 安装 JDK 7 / JDK8 的两种方式
在Ubuntu中通过update-alternatives切换java版本
编译Android 9.0内核源码并刷入手机
Android系统内核编译及刷机实战 (修改反调试标志位)
搭建编译环境
How to Build a Custom Android Kernel
Android源码定制添加反反调试机制
Java6+Java7链接 提取码:ma3i

关键词:[‘安全技术’, ‘移动安全’]


author

旭达网络

旭达网络技术博客,曾记录各种技术问题,一贴搞定.
本文采用知识共享署名 4.0 国际许可协议进行许可。

We notice you're using an adblocker. If you like our webite please keep us running by whitelisting this site in your ad blocker. We’re serving quality, related ads only. Thank you!

I've whitelisted your website.

Not now