绕过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
- 为了下载最新的JDK, 可以现在Ubuntu的命令行里面先输入
javac
, 会显示下面的内容, 按照它提供的命令即可下载最新的JDK。

- 很不快乐的是Java 6和Java 7需要有Oracle的账号, 所以只要去Orcle注册一个账号, 就可以下载Java 7和Java 6了(Java 7是压缩包, Java 6是一个二进制文件)。文章末尾附有两个jdk文件的链接。
安装JDK
因为先安装了Java 8在路径/usr/lib/jvm
目录下, 所以将文件文件jdk-6u45-linux-x64.bin
和jdk-7u80-linux-x64.tar.gz
都用mv命令移到上述目录下。
root@vultr:~/[jdk 6存放的位置]
root@vultr:~/[jdk 7存放的位置]
解压jdk 6, 进入到/usr/lib/jvm
目录下, 先给该文件读写的权限, 之后运行该二进制文件就会在当前目录下生成一个新的文件夹。
root@vultr:~/[jdk 7存放的位置]
root@vultr:/usr/lib/jvm
root@vultr:/usr/lib/jvm
解压jdk 7
root@vultr:/usr/lib/jvm
为了我们能够在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 id
为cf10b7e
。

因为内核版本比较旧, 所以按照旧版的官方内核编译手册来, 而不是按照新版的内核编译手册来。如果内核比较新的, 还是直接用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.c
和array.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 209
和line 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
中双击跳转。

将原来状态表中的T
和t
都修改为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)",
137 "S (sleeping)",
138 "D (disk sleep)",
139 "T (stopped)",
140 "t (tracing stop)",
141 "Z (zombie)",
142 "X (dead)",
143 "x (dead)",
144 "K (wakekill)",
145 "W (waking)",
146 };
修改之后的结果如下
134 static const char * const task_state_array[] 135 = {
136 "R (running)",
137 "S (sleeping)",
138 "D (disk sleep)",
139 "S (sleeping)",
140 "S (sleeping)",
141 "Z (zombie)",
142 "X (dead)",
143 "x (dead)",
144 "K (wakekill)",
145 "W (waking)",
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
$ export SUBARCH=arm
$ cd msm
$ make hammerhead_defconfig
#指定使用的交叉编译器的前缀
$ make ARCH=arm CROSS_COMPILE=arm-eabi- -j4
可以从编译内核这篇文章中找到相应的设备名。
在编译的过程中, 遇到了下面的报错。

这时候需要修改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进行备份。
因为我之前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