绕过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存放的位置]# 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 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)", /* 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进行备份。
因为我之前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