尝试绕过TracePID反调试——从源码入手

绕过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命令移到上述目录下。
    1
    2
    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目录下, 先给该文件读写的权限, 之后运行该二进制文件就会在当前目录下生成一个新的文件夹。

1
2
3
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

1
root@vultr:/usr/lib/jvm# tar -zxvf jdk-7u80-linux-x64.tar.gz

为了我们能够在Ubuntu里面自由自在地切换Java版本, 我们可以先写个脚本将jdk-6和jdk-7添加到候选项中。先输入命令vim alternativeJava.sh, 并将下面的内容直接复制到alternativsjava.sh文件里。

1
2
3
4
5
6
7
8
9
10
11
12
#!/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

输入下述命令。

1
$ 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这一个分支, 这时候我们需要做的事就是在远程分支的基础上再分一个本地分支。

1
$ 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也会在出现很多很麻烦的报错。所以我这里为了匹配, 用的是旧的交叉编译器。

1
$ 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 镜像使用帮助

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

添加环境变量

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

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

1
$ 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也是通过这个文件中获取到的。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
>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 都无法检测出。修改的结果如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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这样就避免了该状态位反映出被监测的状态。

1
2
3
4
5
6
7
8
9
10
11
12
13
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行左右)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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 }

改成下面的内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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进行编辑, 先修改第一处

1
2
3
4
5
6
7
8
9
10
11
12
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 };

修改之后的结果如下

1
2
3
4
5
6
7
8
9
10
11
12
13
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的第二处

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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);

修改的结果为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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中。

按照下面来进行配置

1
2
3
4
5
6
$ 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编辑该文件, 定位到下述代码。

1
2
3
4
5
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环境中, 输入下面命令就准备完成了

1
2
3
$ 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的时候要修改一下才能用。

1
2
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存放的目录输入下述命令。

1
2
3
$ adb reboot bootloader
$ fastboot 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