为了做出腾讯游戏安全竞赛初赛的这道安卓题,开始学习rwProcMem33
的使用来打硬件断点了
在juice4fun师傅做腾讯游戏安全竞赛初赛的安卓题的writeup时,使用了rwProcMem33来对安卓手机打下硬件断点分析反调试,我也对在安卓手机中打硬件断点的工具很感兴趣,所以就学习一下编译和使用的方法啦
要想使用rwProcMem33,编译环境(即AOSP安卓内核源码环境)的搭建过程是必不可少的,因为最终内核模块是运行在安卓手机的linux内核中,而非虚拟机的linux内核中,所以内核源码是有必要下载的
所以这篇文章不仅是硬件断点工具的编译和使用笔记,也是安卓内核的编译笔记,用来记录我在编译内核的过程中遇到的困难,以及如何克服的
为了更快的下载速度,可以选择配置代理,也可以手动切换下载源,只要不出现网络问题导致下载失败就行
docker的使用完全是因为我的虚拟机shell环境崩溃,从而导致无法编译,如果对自己虚拟机的shell环境足够自信,不使用docker也是可以的
编译环境搭建
虚拟机配置
我使用的虚拟机为Ubuntu22.04
,虚拟机的参考配置如下

为虚拟机配置代理
查看虚拟机代理地址
打开clash for windows
,并打开Allow LAN
的开关,随后点击network interfaces

请注意我的虚拟机使用的网络连接方式为NAT模式,所以需要关注VMnet8
的地址,所以对于该虚拟机,代理地址为192.168.27.1
,端口就是clash
中Port
选项所显示的端口

启用虚拟机代理
依次点击如下选项进入代理配置

输入代理地址保存即可

配置docker ubuntu镜像
本人因为不小心运行了一个命令source _setup_env.sh
,导致虚拟机的shell环境整个崩掉了,build.sh
也屡屡运行失败,看了眼_setup_env.sh
我真是只能苦涩的笑...

所以不得不用docker了,不过用下来发现竟然意外的好用
安装docker
1 | sudo apt install docker.io
|
docker pull代理配置
1 2 | sudo mkdir -p /etc/systemd/system/docker .service.d
sudo gedit /etc/systemd/system/docker .service.d /proxy .conf
|
输入以下代理服务器内容
1 2 3 4 | [Service]
Environment= "HTTP_PROXY=http://192.168.27.1:7890/"
Environment= "HTTPS_PROXY=http://192.168.27.1:7890/"
Environment= "NO_PROXY=localhost,127.0.0.1"
|
刷新配置并重启 docker 服务
1 2 | sudo systemctl daemon-reload
sudo systemctl restart docker
|
docker镜像代理配置
1 2 | sudo mkdir -p ~/.docker/
sudo gedit ~/.docker /config .json
|
输入以下内容
1 2 3 4 5 6 7 8 9 10 11 | {
"proxies" :
{
"default" :
{
"httpProxy" : "http://192.168.27.1:7890/" ,
"httpsProxy" : "http://192.168.27.1:7890/" ,
"noProxy" : "localhost,127.0.0.1"
}
}
}
|
下载Ubuntu镜像
运行Ubuntu镜像
1 | docker run -it --net host --name Akernel ubuntu /bin/bash
|
安装sudo,vim
1 2 3 | apt-get update
apt-get install vim
apt-get install sudo
|
修改apt-get的软件源为阿里源
1 2 | sudo cp /etc/apt/sources .list /etc/apt/sources .list_backup
sudo vim /etc/apt/sources .list
|
替换为如下内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | deb http://mirrors.aliyun.com/ubuntu/ jammy main restricted universe multiverse
deb-src http://mirrors.aliyun.com/ubuntu/ jammy main restricted universe multiverse
deb http://mirrors.aliyun.com/ubuntu/ jammy-security main restricted universe multiverse
deb-src http://mirrors.aliyun.com/ubuntu/ jammy-security main restricted universe multiverse
deb http://mirrors.aliyun.com/ubuntu/ jammy-updates main restricted universe multiverse
deb-src http://mirrors.aliyun.com/ubuntu/ jammy-updates main restricted universe multiverse
# deb http://mirrors.aliyun.com/ubuntu/ jammy-proposed main restricted universe multiverse
# deb-src http://mirrors.aliyun.com/ubuntu/ jammy-proposed main restricted universe multiverse
deb http://mirrors.aliyun.com/ubuntu/ jammy-backports main restricted universe multiverse
deb-src http://mirrors.aliyun.com/ubuntu/ jammy-backports main restricted universe multiverse
|
随后将apt-get
更新至最新版本
1 2 3 | sudo apt-get update
sudo apt-get upgrade
sudo apt-get install build-essential
|
安装必要的库
1 | sudo apt-get install git-core gnupg flex bison build-essential zip curl zlib1g-dev gcc-multilib g++-multilib libc6-dev-i386 libncurses5 lib32ncurses5-dev x11proto-core-dev libx11-dev lib32z1-dev libgl1-mesa-dev libxml2-utils xsltproc unzip fontconfig libssl-dev bc kmod cpio git curl
|
为git配置基本信息
1 2 3 | git config --global user.email "xxx@gmail.com"
git config --global user.name "xxx"
git config --global http.proxy 192.168.27.1:7890
|
安装repo
1 2 3 | mkdir ~ /bin
curl https: //storage .googleapis.com /git-repo-downloads/repo > ~ /bin/repo
chmod a+x ~ /bin/repo
|
修改repo的下载源为清华源,并添加repo至全局变量
打开全局变量配置文件
添加全局变量
在末尾添加这三行并保存
1 2 3 | export REPO_URL= 'https://mirrors.tuna.tsinghua.edu.cn/git/git-repo/'
export PATH= "~/bin:$PATH"
|
使配置文件生效
安装python
如果使用python --version
有打印python版本的话,那么这一步就不需要了,如果docker中没有安装python
,在docker内使用如下命令安装
1 2 3 4 | sudo apt-get install software-properties-common
add-apt-repository ppa:deadsnakes /ppa
sudo apt install python3.9
sudo ln -s /usr/bin/python3 /usr/bin/python
|
修改交换区大小
为了防止编译源码的过程中由于交换区不足而失败,所以我们需要去修改虚拟机的交换区的大小
1 2 3 4 5 6 7 8 | sudo swapoff /swapfile
sudo rm /swapfile
sudo dd if = /dev/zero of= /swapfile bs=1GB count=32
sudo chmod 600 /swapfile
sudo mkswap -f /swapfile
sudo swapon /swapfile
|
下载内核源码
查看内核版本号
这里我的pixel3
的内核版本为Linux version 4.9.270-g862f51bac900-ab7613625
1 2 | adb shell cat /proc/version
Linux version 4.9.270-g862f51bac900-ab7613625 (android-build@abfarm-east4-101) (Android (7284624, based on r416183b) clang version 12.0.5 (https: //android .googlesource.com /toolchain/llvm-project c935d99d7cf2016289302412d708641d52d2f7ee))
|
查看内核源码分支
进入building-kernels中查看自己的源码分支,如图我的手机型号为pixel3
,并且内核版本为4.9,于是就知道内核源码的代号是android-msm-crosshatch-4.9

之后进入安卓内核源码列表中,搜索内核源码代号android-msm-crosshatch-4.9

我的手机是安卓12,所以我下载的内核版本为android-msm-crosshatch-4.9-android12
这里的qpr1
和qpr2
,我们先看一下QPR
的定义,简单来说就是QPR
后面跟的数字越高,内核版本就越新
QPR, of course, is short for the Quarterly Platform Release, which Google first introduced with Android 12. These are not full system updates, but they bring a few select changes to the Pixels and other great high-end phones that opt to receive them.
注意一点就是例如你想要下载android-msm-crosshatch-4.9-android10,请先进入你想要下载的AOSP的地址,看看仓库中的default.xml
文件,重点关注<project path="build" name="kernel/build"
,如果revision
的值为main
,请千万不要下载,否则你就会发现下载下来之后根本无法build!!,这是由于build仓库和内核源码仓库不同步导致的

当然你也可以选择进入kernel/build中找到适合你的build file
,不过我还是建议能一键编译就一键编译,比如我下载的android-msm-crosshatch-4.9-android12
,build
和kernel source code
就是同步的(惨痛的教训,头铁想要用安卓9的内核源码编译,结果根本无法build...最后还是妥协把手机刷成安卓12了)
下载安装内核源码
1 2 3 | mkdir android-kernel && cd android-kernel
repo init -u https: //android .googlesource.com /kernel/manifest -b android-msm-crosshatch-4.9-android12
repo sync -j4
|
切换git分支
我的手机的Linux内核版本为Linux version 4.9.270-g862f51bac900-ab7613625
,g
后面跟的是git分支,所以切换的分支为862f51bac900
1 2 | cd private /msm-google
git checkout 862f51bac900
|
编译内核源码
解包boot.img
首先下载android-image-kitchen
然后将boot.img
放在工具的根目录下,这里的boot.img
就是网上下载的刷机包解压之后其中的boot.img

然后运行unpackimg.bat
,运行之后的窗口请不要关闭,因为输出中有需要后续使用到的参数,当然也可以将输出的内容复制下来到txt中
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 37 38 39 40 | Android Image Kitchen - UnpackImg Script
by osm0sis @ xda-developers
Supplied image: boot.img
Removing old work folders and files . . .
Setting up work folders . . .
Image type : AOSP
Signature with "AVBv2" type detected.
Splitting image to "split_img/" . . .
ANDROID! magic found at: 0
BOARD_KERNEL_CMDLINE console=ttyMSM0,115200n8 androidboot.console=ttyMSM0 printk.devkmsg=on msm_rtb.filter=0x237 ehci-hcd.park=3 service_locator. enable =1 cgroup.memory=nokmem lpm_levels.sleep_disabled=1 usbcore.autosuspend=7 loop.max_part=7 androidboot.boot_devices=soc /1d84000 .ufshc androidboot.super_partition=system buildvariant=user
BOARD_KERNEL_BASE 0x00000000
BOARD_NAME
BOARD_PAGE_SIZE 4096
BOARD_HASH_TYPE sha1
BOARD_KERNEL_OFFSET 0x00008000
BOARD_RAMDISK_OFFSET 0x01000000
BOARD_SECOND_OFFSET 0x00000000
BOARD_TAGS_OFFSET 0x00000100
BOARD_OS_VERSION 12.0.0
BOARD_OS_PATCH_LEVEL 2021-10
BOARD_HEADER_VERSION 2
BOARD_HEADER_SIZE 1660
BOARD_DTB_SIZE 863100
BOARD_DTB_OFFSET 0x01f00000
Unpacking ramdisk to "ramdisk/" . . .
Compression used: gzip
56773 blocks
Done!
请按任意键继续. . .
|
当unpackimg.bat
运行完毕后,我们进入split_img/
,然后解压其中的boot.img-ramdisk.cpio.gz
,并将解压后的boot.img-ramdisk.cpio
文件复制到内核源码根目录中
1 | root@oacia-virtual-machine: /home/oacia/Desktop/
|
下载mkbootimg.py
我们还需要下载mkbootimg.py,并将其复制到放到内核源码根目录
1 | docker cp mkbootimg.py Akernel: /android-kernel/
|
修改build.sh
在内核源码根目录,进入build/build.sh
,找到下方代码的位置
1 2 | echo "========================================================"
echo " Files copied to ${DIST_DIR}"
|
并在这两行代码之前加上下列命令
1 2 3 | if [ -f "${VENDOR_RAMDISK_BINARY}" ]; then
cp ${VENDOR_RAMDISK_BINARY} ${DIST_DIR}
fi
|

下载rwProcMem33
下载地址
1 | https: //github .com /abcz316/rwProcMem33
|
然后将解压后的文件夹复制到docker中内核源码目录下的private/msm-google/drivers/
中
1 | docker cp rwProcMem33 Akernel: /android-kernel/private/msm-google/drivers/
|
修改rwProcMem33
ver_control.h
将MY_LINUX_VERSION_CODE
切换到对应的安卓内核版本,我们在下载内核源码阶段已经通过cat /proc/version
知道了内核的版本号为Linux version 4.9.270-g862f51bac900-ab7613625
,所以在private/msm-google/drivers/rwProcMem33/ver_control.h
和private/msm-google/drivers/rwProcMem33/hwBreakpointProcModule/hwBreakpointProc/ver_control.h
中我们也将内核切换到对应的4.9
版本,选择MY_LINUX_VERSION_CODE
的原则选这里出现的版本号中越接近自己手机内核版本的版本号

在Linux 4.11
前,Linux内核把页表分为4级
- 页全局目录(Page Global Directory,PGD)
- 页上层目录(Page Upper Directory,PUD)
- 页中间目录(Page Middle Directory,PMD)
- 直接页表(Page Table,PT)
所以对于Linux version 4.11
以下的内核版本,并不支持五级页表,选择启用读取pagemap文件来计算物理内存的地址
,同时注释掉启用页表计算物理内存的地址
如图所示

Linux version 4.11
版本把页表扩展到五级,在页全局目录和页上层目录之间增加了页四级目录(Page 4th Directory,P4D)
所以对于Linux version 4.11
及以上的内核版本,选择启用页表计算物理内存的地址
,同时注释掉启用读取pagemap文件来计算物理内存的地址
如图所示

linux_kernel_api文件夹
在rwProcMem33/linux_kernel_api
文件夹中新建一个头文件linux_kernel_api.h

并写入如下内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | #ifndef LINUX_KERNEL_API_H_
#define LINUX_KERNEL_API_H_
#include "../ver_control.h"
#if MY_LINUX_VERSION_CODE < KERNEL_VERSION(5,8,0)
long probe_kernel_read( void * dst, const void * src, size_t size);
MY_STATIC long x_probe_kernel_read( void * bounce, const char * ptr, size_t sz) {
return probe_kernel_read(bounce, ptr, sz);
}
#endif
#if MY_LINUX_VERSION_CODE >= KERNEL_VERSION(5,8,0)
long copy_from_kernel_nofault( void * dst, const void * src, size_t size);
MY_STATIC long x_probe_kernel_read( void * bounce, const char * ptr, size_t sz) {
return copy_from_kernel_nofault(bounce, ptr, sz);
}
#endif
#endif /* LINUX_KERNEL_API_H_ */
|
api_proxy.h
为了包含linux_kernel_api.h
头文件,我们在api_proxy.h
的前几行中加入#include "linux_kernel_api/linux_kernel_api.h"
如图

修改drivers的Makefile
在private/msm-google/drivers/Makefile
的开头加入下列命令
1 2 | obj-y += rwProcMem33 /hwBreakpointProcModule/hwBreakpointProc/
obj-y += rwProcMem33/
|
修改msm-google的Makefile
在编译rwProcMem33
内核模块时,由于内核编译时会将警告视为错误导致编译内核停止,所以我们要修改Makefile来忽视warning
在private/msm-google/Makefile
找到如下位置,在-Wno-format-security
后加上一个-w
参数

开始编译
在安卓内核源码的根目录打开终端使用如下命令开始编译
命令的参数为使用android-image-kitchen
解包boot.img
之后,控制台所打印的参数请务必替换为相对应的参数!!!
参数对应关系为
android-image-kitchen 解包参数名称 |
值 |
编译命令参数名称 |
BOARD_KERNEL_CMDLINE |
console=ttyMSM0,115200n8 androidboot.console=ttyMSM0 printk.devkmsg=on msm_rtb.filter=0x237 ehci-hcd.park=3 service_locator.enable=1 cgroup.memory=nokmem lpm_levels.sleep_disabled=1 usbcore.autosuspend=7 loop.max_part=7 androidboot.boot_devices=soc/1d84000.ufshc androidboot.super_partition=system buildvariant=user |
KERNEL_CMDLINE |
BOARD_KERNEL_BASE |
0x00000000 |
BASE_ADDRESS |
BOARD_PAGE_SIZE |
4096 |
PAGE_SIZE |
BOARD_HEADER_VERSION |
2 |
BOOT_IMAGE_HEADER_VERSION |
编译命令中的BUILD_CONFIG
为AOSP源码根目录的build.config
的软连接所指向的配置文件

所以最终的编译命令为
1 | BUILD_CONFIG=private /msm-google/build .config.bluecross BUILD_BOOT_IMG=1 MKBOOTIMG_PATH=mkbootimg.py VENDOR_RAMDISK_BINARY=boot.img-ramdisk.cpio KERNEL_BINARY=Image.lz4 BOOT_IMAGE_HEADER_VERSION=2 KERNEL_CMDLINE= "console=ttyMSM0,115200n8 androidboot.console=ttyMSM0 printk.devkmsg=on msm_rtb.filter=0x237 ehci-hcd.park=3 service_locator.enable=1 cgroup.memory=nokmem lpm_levels.sleep_disabled=1 usbcore.autosuspend=7 loop.max_part=7 androidboot.boot_devices=soc/1d84000.ufshc androidboot.super_partition=system buildvariant=user" BASE_ADDRESS=0x00000000 PAGE_SIZE=4096 build /build .sh
|
经过一段时间的等待,编译成功!

生成的boot.img
的路径为/android-kernel/out/android-msm-pixel-4.9/dist/boot.img
使用Magisk修补boot.img实现root
安装Magisk 下载地址
1 | adb install "D:\TOOLS\pixel3\Magisk-v26.1.apk"
|
将由内核源码编译出来的boot.img
上传到手机上
1 | adb push boot.img /sdcard/
|
然后在手机上打开Magisk
,依次点击安装->选择并修补一个文件->/sdcard/boot.img->开始
待修补完成后,将修补后的boot.img
传到电脑上
1 | adb pull /storage/emulated/0/Download/magisk_patched-xxxxx_xxxxx .img
|
刷入内核
1 2 3 | adb reboot bootloader
fastboot flash magisk_patched-xxxxx_xxxxx.img
fastboot reboot
|
编译HwBpClient客户端
进入rwProcMem33\hwBreakpointProcModule\testHwBpClient
文件夹,双击testHwBpClient.vcxproj
在visual studio
中打开
编译的程序位数应为64位
对于Windows平台编译的HwBpClient
,并且需要在testHwBpClientDlg.cpp
的这个位置进行修改,将这个位置的llX
改为I64X
,%zu
改成I64d
,否则将无法正确输入内容

原因在于C/C++输出64位数在window下和linux下是不一样的
linux
1 2 3 | printf ( "%lld/n" ,a);
printf ( "%llu/n" ,a);
printf ( "%llx/n" ,a);
|
windows
1 2 3 | printf ( "%I64d/n" ,a);
printf ( "%I64u/n" ,a);
printf ( "%I64x/n" ,a);
|
修改完成后如图所示

接下来按下ctrl+B生成即可

编译HwBpServer服务端
编译HwBpServer服务端需要NDK,并将NDK引入环境变量中
NDK可以在android studio
中进行安装,依次点击File->Project Structure->SDK Location
,找到Android NDK location
,点击Download
即可开始安装,我这里使用的ndk的版本为ndk25.2.9519653
如果没有安装Android studio
,NDK的安装方法可以在谷歌上找到
NDK安装完成后,进入到rwProcMem33\hwBreakpointProcModule\testHwBpServer\jni
,打开cmd运行命令
即可完成编译,编译后的文件在rwProcMem33\hwBreakpointProcModule\testHwBpServer\libs
中,选择对应手机架构的ELF,push
到手机中运行即可
获取手机的IPv4地址
将电脑和我们的测试手机连接同一个手机热点,然后在测试手机中打开设置->WLAN
点击我们连接的热点的高级选项,来查看手机的IP地址
运行HwBpServer服务端
将编译出来的程序复制到手机中并运行
1 2 3 4 5 6 | adb push rwProcMem33\hwBreakpointProcModule\testHwBpServer\libs\arm64-v8a\testHwBpServer.out /data/local/tmp
adb shell
su
cd /data/local/tmp
chmod 777 testHwBpServer.out
. /testHwBpServer .out
|
查看打印出来的端口号3170

运行HwBpClient客户端
在电脑中运行编译完成的HwBpClient客户端,填入手机的IP地址以及由testHwBpServer.out
打印出来的端口号

点击连接即可开始打硬件断点

查询进程PID
查询目标so的基址
可以使用命令来查询
也可以使用下面的frida脚本查询so的基址
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | function dump_so(so_name) {
Java.perform( function () {
var currentApplication = Java.use( "android.app.ActivityThread" ).currentApplication();
var dir = currentApplication.getApplicationContext().getFilesDir().getPath();
var libso = Process.getModuleByName(so_name);
console.log( "[name]:" , libso.name);
console.log( "[base]:" , libso.base);
console.log( "[size]:" , ptr(libso.size));
console.log( "[path]:" , libso.path);
}
});
}
rpc.exports = {
dump_so: dump_so
};
|
接下来就可以愉快的打硬件断点啦~
参考资料
[培训]《安卓高级研修班(网课)》月薪三万计划
最后于 2023-9-13 17:55
被oacia编辑
,原因: 按照abcz316大佬的建议,不对x_probe_kernel_read进行修改