绕过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

点击收藏 | 1 关注 | 2
登录 后跟帖