这篇文章上次修改于 801 天前,可能其部分内容已经发生变化,如有疑问可询问作者。
本文属于错误示范,请参考eBPF on Android之打补丁和编译内核修正版进行内核修改与编译
前言
在eBPF on Android之bcc编译与体验末尾提到可以修改内核,打补丁以获取更好的eBPF体验
本文记录了给Pixel 4XL内核打补丁,添加KPROBES
支持,编译内核并刷入的过程
对libssl.so
的SSL_write
和SSL_read
进行hook,对用户态进程实现无感抓包
环境
- Pixel 4XL
- coral-sq3a.220605.009.a1 最新版系统,Android 12
- 4.14.261-g84ae96329b23-ab8487884
- Ubuntu 20.04
如果想动手操作,请先看完文章再动手,不要直接复制粘贴运行,有些问题可以提前处理
记录
官方推荐的编译系统较新的是ubuntu 18.04,我这里选择的是20.04
准备环境和编译
那么先安装必要的软件等
sudo apt 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
创建一个工作目录,后面的文件都会放这下面
mkdir /home/kali/Desktop/p4xl
export WORK_DIR=/home/kali/Desktop/p4xl
cd ${WORK_DIR}
先创建放代码的文件夹,然后同步内核源代码
根据官方的说明,我这里选择Pixel 4XL对应的分支android-msm-coral-4.14-android12L
为了能对应上手机的内核版本,这里没有加上--depth=1
,后面需要checkout
mkdir kernel && cd kernel
repo init -u https://android.googlesource.com/kernel/manifest -b android-msm-coral-4.14-android12L
repo sync
最新版的内核编译脚本会使用自带的编译工具,基本上不需要自己设置
如果你同步的内核源代码没有编译工具,需要自己手动同步一下,如下
cd prebuilts
git clone https://android.googlesource.com/kernel/prebuilts/build-tools
mv build-tools kernel-build-tools
export PATH=${WORK_DIR}/kernel/prebuilts/kernel-build-tools/linux-x86/bin:$PATH
这里的prebuilts
是前面同步完内核源码出现的文件夹~
查看下手机的内核版本信息
adb shell uname -r
得到的是4.14.261-g84ae96329b23-ab8487884
,其中g
后面的84ae96329b23
就是内核的short commit id
如果要看内核编译时间等详细信息,用下面的命令
adb shell uname -a
进入内核源码目录,切换到与当前手机内核一致的分支
cd private/msm-google
git checkout 84ae96329b23
现在切回到repo同步时创建的kernel目录,通过ls -al
可以看到几个软链接,但是都是红的
这是因为指向的是不存在的文件,手动删掉;并且这里bonito
不是Pixel 4XL的代码,所以肯定也不符合我们的要求
kali@sfx:~/Desktop/p4xl/kernel$ ls -al /home/kali/Desktop/p4xl/kernel/build.config
lrwxrwxrwx 1 kali kali 38 6月 25 17:48 /home/kali/Desktop/p4xl/kernel/build.config -> private/msm-google/build.config.bonito
然后创建我们自己的软链接
ln -s private/msm-google/build.config.floral build.config
现在的文件夹结构
.
├── build
│ ├── abi
│ ├── build_abi.sh
│ ├── build.config -> ../build.config
│ ├── build.config.net_test
│ ├── build.sh
│ ├── build_test.sh
│ ├── build-tools
│ ├── envsetup.sh
│ ├── gki
│ ├── hermetic
│ ├── LICENSE
│ ├── METADATA
│ ├── MODULE_LICENSE_APACHE2
│ ├── multi-switcher.sh
│ ├── NOTICE -> LICENSE
│ ├── _setup_env.sh
│ ├── static_analysis
│ └── upstream
├── build.config -> private/msm-google/build.config.floral
├── prebuilts
│ └── gcc
├── prebuilts-master
│ ├── clang
│ └── misc
└── private
├── msm-google
└── msm-google-modules
现在可以运行build/build.sh
命令进行编译了
编译之后在out/android-msm-pixel-4.14/private/msm-google/arch/arm64/boot
生成了下面这些文件
-rw-rw-r-- 1 kali kali 3397238 Jun 30 07:21 dtbo.img
-rw-rw-r-- 1 kali kali 781 Jun 30 07:21 .dtbo.img.cmd
drwxrwxr-x 28 kali kali 4096 Jun 30 07:21 dts
-rw-rw-r-- 1 kali kali 50219024 Jun 30 07:35 Image
-rw-rw-r-- 1 kali kali 146 Jun 30 07:35 .Image.cmd
-rw-rw-r-- 1 kali kali 24198300 Jun 30 07:35 Image.lz4
-rw-rw-r-- 1 kali kali 144 Jun 30 07:35 .Image.lz4.cmd
-rw-rw-r-- 1 kali kali 25246584 Jun 30 07:35 Image.lz4-dtb
-rw-rw-r-- 1 kali kali 248 Jun 30 07:35 .Image.lz4-dtb.cmd
以.cmd
结尾的文件是文本文件,其实就是表明对应的文件是怎么来的
以.Image.lz4-dtb.cmd
为例,其内容如下
cmd_arch/arm64/boot/Image.lz4-dtb := (cat arch/arm64/boot/Image.lz4 arch/arm64/boot/dts/google/qcom-base/sm8150-v2.dtb arch/arm64/boot/dts/google/qcom-base/sm8150.dtb > arch/arm64/boot/Image.lz4-dtb) || (rm -f arch/arm64/boot/Image.lz4-dtb; false)
可以看到Image.lz4-dtb
是几个文件合并得到的,现在看到的Image.lz4-dtb
已经是合并好之后的文件了
这里需要的是Image.lz4-dtb
,那有的地方说是Image.lz4
,怎么判断呢
我是这样判断的,首先解包手机当前的boot.img,这里我使用的是Android-Image-Kitchen-3.8
- https://forum.xda-developers.com/t/tool-android-image-kitchen-unpack-repack-kernel-ramdisk-win-android-linux-mac.2073775/
- https://forum.xda-developers.com/attachments/android-image-kitchen-v3-8-win32-zip.5300919/
解压Android-Image-Kitchen-3.8.zip
,把手机原始的boot.img
复制到解压的目录
然后将boot.img
拖放到unpackimg.bat
脚本上,然后就会解包
得到ramdisk
和split_img
文件夹,然后进入split_img
文件夹,有一个boot.img-kernel
文件,用16进制工具打开
可以看到末尾有不少可读的内容,而编译生成的Image.lz4
文件末尾没有这些,而Image.lz4-dtb
有
所以这里要用Image.lz4-dtb
替换boot.img-kernel
文件
替换之后双击运行unpackimg.bat
,会自动重新打包生成image-new.img
现在就可以重新刷入这个新内核了
adb reboot bootloader
fastboot flash boot image-new.img
fastboot reboot
如果不确定内核一定没问题,可以临时刷入,这样再次重启的时候会使用之前的内核
fastboot boot image-new.img
注意,请提前配好adb,这样万一屏幕失灵还可以通过scrcpy操作,当然直接刷回刷机包的boot.img一般是可以恢复的
遇到的问题
Terminal崩溃
最开始尝试的时候,在LTO vmlinux.o
的时候Terminal总是会在某个时候崩掉,完全找不到有用的信息
后续尝试了很多次,以为是swap不够,于是增加了swap
sudo dd if=/dev/zero of=/swapfile bs=1k count=16384000
sudo mkswap /swapfile
sudo swapon /swapfile
如果提示文件繁忙,先关闭
sudo swapoff /swapfile
但仍然崩溃
最后是把虚拟机从原来的处理器数量 12 + 每个处理器的内核数量 1
变更为处理器数量 6 + 每个处理器的内核数量 2
才能编译成功
另外还有问题试试把虚拟化打开?(虽然这好像没有什么关系...)
当然swap还是要加的,内存我给了28G,外加16G的swap,最后根据观察,打开能吃完内存,然后用掉4-5G左右的swap
如果条件有限,要么把swap设置大点,要么关闭LTO,也就是去掉CONFIG_LTO
刷机卡谷歌LOGO
当时就纳闷了,这啥也没动啊,怎么回事,怎么就卡谷歌LOGO了呢
后面分析了下编译的脚本,发现build.config.floral
配置里面还会先过几个配置
挨个查看,发现build.config.common
里面的DEFCONFIG
配置的是sunfish_defconfig
难怪不开机,这里应该改成floral_defconfig
这个对应的文件是private/msm-google/arch/arm64/configs/floral_defconfig
或者在build.config.floral
里面设置DEFCONFIG=floral_defconfig
,这样可以覆盖掉前面设置的变量
修改之后再次编译,可以正常开机,触摸和WIFI等功能一切正常
添加内核编译项
通过zcat /proc/config.gz | grep PROBE
查看内核配置
可以发现没有配置CONFIG_KPROBES
# CONFIG_KPROBES is not set
在之前的文章中提到,如果要有更良好的bcc使用体验,那得有这个才行
修改build.config.floral
配置如下
KERNEL_DIR=private/msm-google
. ${ROOT_DIR}/${KERNEL_DIR}/build.config.floral.common.clang
POST_DEFCONFIG_CMDS="check_defconfig && update_my_config"
UNSTRIPPED_MODULES="
wlan.ko
"
function update_my_config() {
${KERNEL_DIR}/scripts/config --file ${OUT_DIR}/.config \
-e CONFIG_IKHEADERS \
-e CONFIG_HAVE_KPROBES \
-e CONFIG_KPROBES \
-e CONFIG_KPROBE_EVENT
(cd ${OUT_DIR} && \
make ${CC_LD_ARG} O=${OUT_DIR} olddefconfig)
}
这里添加了CONFIG_IKHEADERS
和KPROBES
的支持,CONFIG_IKHEADERS
是为了方便bcc可以取到头文件信息,不然得自己手动整理内核源代码,还得放到rootfs编译环境下去
注意这里千万不要直接复制过去使用
请手动修改你的配置文件
补充小姿势
如果要给指定选项设定一个值,那么应该用--set-val
,如果要编译为模块那么应该用-m
这些信息在下面这个文件中可以看到
- private/msm-google/scripts/config
commands:
--enable|-e option Enable option
--disable|-d option Disable option
--module|-m option Turn option into a module
--set-str option string
Set option to "string"
--set-val option value
Set option to value
--undefine|-u option Undefine option
--state|-s option Print state of option (n,y,m,undef)
--enable-after|-E beforeopt option
Enable option directly after other option
--disable-after|-D beforeopt option
Disable option directly after other option
--module-after|-M beforeopt option
Turn option into module directly after other option
commands can be repeated multiple times
options:
--file config-file .config file to change (default .config)
--keep-case|-k Keep next symbols' case (dont' upper-case it)
再次执行build/build.sh
编译内核,重打包boot.img步骤和前面的一样
刷入手机后开机,可以看到现在的内核配置如下,有CONFIG_KPROBES
和CONFIG_IKHEADERS
了(PS:CONFIG_UPROBES
默认开着的,所以前面没有提)
coral:/ $ zcat /proc/config.gz | grep PROBE
CONFIG_ARCH_SUPPORTS_UPROBES=y
CONFIG_GENERIC_IRQ_PROBE=y
CONFIG_KPROBES=y
CONFIG_UPROBES=y
CONFIG_HAVE_KPROBES=y
CONFIG_HAVE_KRETPROBES=y
# CONFIG_NET_TCPPROBE is not set
# CONFIG_BUILTINS_ASYNC_PROBE is not set
# CONFIG_TEST_ASYNC_DRIVER_PROBE is not set
CONFIG_GENERIC_CPU_AUTOPROBE=y
CONFIG_TIMER_PROBE=y
CONFIG_KPROBE_EVENTS=y
CONFIG_UPROBE_EVENTS=y
CONFIG_PROBE_EVENTS=y
# CONFIG_KPROBES_SANITY_TEST is not set
coral:/ $ zcat /proc/config.gz | grep IKH
CONFIG_IKHEADERS=y
添加bpf_probe_read_user
根据bcc的issue
要添加读取用户空间数据的功能,可以参考下面这个补丁
这个看着不清晰,可以看下面这个链接
这里没有几行,加上对git打补丁不太熟,就手动找到对应的代码位置,添加了代码
- private/msm-google/include/uapi/linux/bpf.h
- private/msm-google/kernel/trace/bpf_trace.c
- private/msm-google/tools/include/uapi/linux/bpf.h
注意原patch中有两处FN(probe_read_user),
,但是我截图没有,这是因为我这个版本的源代码已经有这个了
主要是缺少BPF_CALL_3(bpf_probe_read_user, void *, dst, u32, size, const void *, unsafe_ptr)
函数
所以请打补丁的时候仔细确定,看好了再修改,否则编译会出问题,提示重定义什么的
再次编译内核,刷入手机
查看下内核的符号信息
cat /proc/kallsyms | grep probe_read
完整体验sslsniff.py
根据issue的信息,完成内核的patch后,bcc脚本中bpf_probe_read_user
还是失败了
这是因为bcc的代码中,找不到bpf_probe_read_kernel
符号,然后直接回退到bpf_probe_read
了,而不是bpf_probe_read_user
,所以实际上还是通过读内核的函数去读取用户态数据
issue中提到了一些方案,比如修改内核源码的bpf_probe_read
,如果probe_kernel_read
不成功就调用probe_user_read
去读
不过我觉得还是改bcc的代码方便点,我这里简单把check_bpf_probe_read_kernel
返回bpf_probe_read
改为bpf_probe_read_user
然后重新编译bcc并安装即可
- src/cc/frontends/clang/b_frontend_action.cc
但这样的话就只能读用户态数据了,更好的兼容方案最好还是分析下bcc的逻辑才行,这里是临时操作
如果你改了这个,不要忘记了
又看了下代码,修改DoMiscWorkAround
里面的定义最合适,如下图
也就是把#define bpf_probe_read_user bpf_probe_read
改为#define bpf_probe_read_user bpf_probe_read_user
即可
前面刷的内核是没有root权限的,所以记得用magisk给新编译出来的内核patch,再刷到手机上
如果之前准备过了bcc编译和使用环境,那么只是重新刷入了新的内核是不需要再准备一遍的,因为环境还在的
脚本是bcc目录下的tools/sslsniff.py
这个脚本需要做出两处修改
probe_SSL_do_handshake_enter
函数中添加u32 uid = bpf_get_current_uid_gid();
SSL_exit
中将buf_copy_size
改为固定值
添加一句日志打印
bpf_trace_printk("[bpf_probe_read_user] ret:%d len:%d\\n", ret, len);
先找一个APP,看下pid,然后执行下面的命令
python sslsniff.py -p 17507 --no-openssl --no-gnutls --no-nss --hexdump -x --extra-lib openssl:/apex/com.android.conscrypt/lib64/libssl.so
APP滑动下,触发https请求,现在就能得到带有数据的输出了
这说明bpf_probe_read_user
的补丁确实有效,现在可以实现用户态数据的读取了
查看trace_pipe
,可以看到bpf_probe_read_user
返回值为0而不是-14
,确实是读取正常的
echo 1 > /sys/kernel/debug/tracing/tracing_on
cat /sys/kernel/debug/tracing/trace_pipe
关闭trace记录
echo 0 > /sys/kernel/debug/tracing/tracing_on
bcc自带的脚本很多依赖于KRETPROBES
,实际上只有KPROBES
也能做不少事情了
如果无法开启KRETPROBES
那就只能再自己写脚本来获取数据了
我测试添加下面的特性,但是编译出来的内核刷入后,触摸和WIFI都会失效,尝试了很久还是没有解决,最终还是放弃折腾了
-e CONFIG_KRETPROBES \
-e CONFIG_HAVE_KRETPROBES \
-d CONFIG_SHADOW_CALL_STACK \
-e CONFIG_ROP_PROTECTION_NONE \
以上就是整个过程,现在能实现对用户态的程序进行hook,并读取用户态数据了~~
结合bpf_probe_write_user
则可以对用户态数据进行修改了
后面将结合对sslsniff.py
的理解,尝试写一个hook任意so与修改数据的demo
这是他人已有的示例
参考
- https://github.com/iovisor/bcc/issues/3070
- https://forum.xda-developers.com/t/how-to-specify-a-custom-defconfig-file-in-android-10-kernel-compilation.4080241/
- https://evilpan.com/2022/01/03/kernel-tracing/#tracepoints
- https://blog.kyrios.cn/2021-07-android-11-building-on-pixel-3/
- https://blog.shijy16.cn/2021/09/14/%E9%85%8D%E7%BD%AE/android%E6%BA%90%E7%A0%81%E7%BC%96%E8%AF%91%E5%8F%8A%E7%9C%9F%E6%9C%BA%E5%86%85%E6%A0%B8%E9%A9%B1%E5%8A%A8%E8%B0%83%E8%AF%95%E8%AE%B0%E5%BD%95/
- https://blog.csdn.net/weixin_42271802/article/details/113526375
- https://bbs.pediy.com/thread-273148.htm
已有 5 条评论
pixel3 可以打补丁使用ebpf吗
@anylayer 不打补丁也是可以使用的,你可以搜一下simpleperf,补丁的目的是为了更好的扩展性
博主内核编译选项的问题最后有解决吗, 我用的Pixel5 + Android12.0.0 + 4.19, 耶稣参考了senyuuri的博客, 但是只要删除CONFIG_LTO或者是使用: -d CONFIG_SHADOW_CALL_STACK \
-e CONFIG_ROP_PROTECTION_NONE \
就会卡在GOOGLE LOGO然后直接回到bootloader
@Jiryu 请通过可视化界面,也就是menuconfig来调整编译选项。pixel每一代手机内核编译差异挺大的,只能你自己读多多尝试了
sunfish 和 floral 是啥意思?