The quiter you become,the more you are able to hear!

在 Android平台使用Linux kernel硬件断点

Author: geneblue

Blog: https://geneblue.github.io/

在一些场景下,我们可以借助硬件断点技术分析kernel的某处地址是如何被更改的(读,写)。

何为硬件断点

程序员一般都熟知软件断点,所谓软件断点是指CPU通过指令来触发中断的行为,常见的调试器如gdb等,在对调试进程的某个函数下断点时,实际上是将该函数起始处的原始指令更改成了CPU的中断指令,当CPU执行到该中断指令时,进程处于停止状态,CPU会根据中断号发送给对应的中断处理例程,中断处理例程再发送信号给父进程(调试器)。调试器获取信号后,会还原断点处的原始指令,再使用一些系统调用获取子进程(被调试进程)的信息(寄存器等),以此达到调试的目的。这就是软件断点的简单过程。硬件断点不是在指令层面的监控,而是监控内存地址来触发断点的。所以支持在FLASH,ROM,RAM设断,只要是可访问的地址都行,只要监控地址的状态(读,写,执行)发生改变,就会触发断点,断点信息会以内核日志的形式呈现。

ARM Linux 硬件断点

现在 ARMV5 以上的 CPU都是支持硬件断点的,cpu提供了专用的硬件断点寄存器在来不断的匹配执行到的地址,当特定地址发生预设行为(读,写,执行)时,就会触发断点。在设定时可以设置监控的地址bit位,如按8位,16位,32位触发。

让 Linux Kernel 支持硬件断点,在编译内核时需要 CONFIG_HAVE_HW_BREAKPOIN配置。 在 Android kernel 中,该配置不存在于menuconfig,需要手工加入到 arch/arm64/Kconfig.debug 中

1
2
3
4
5
6
config HAVE_HW_BREAKPOINT
bool "Hardware Breakpoint support"
default y
help
If this option is hardware breakpoint
If in doubt, say N.

在 menuconfig 中,将模块签名关闭

1
Main Menu/Enable loadable module support - Require modules to be validly signed
编译好内核之后,刷入手机。

再编译内核模块 data_breakpoint,我们就是通过该模块实现对监控地址的设定,代码位于 samples/hw_breakpoint 处将其中的 Makefile 更改如下:

Makefile

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
obj-m := data_breakpoint.o

KERNELDIR := /home/geneblue/Android/Source/kernel/android_msm_3.18_syzkaller/msm
PWD :=$(shell pwd)
ARCH=arm64
CROSS_COMPILE=/home/geneblue/tmp/android_kernel_build_toolchains/aarch64-linux-android-4.9/bin/aarch64-linux-android-
CC=$(CROSS_COMPILE)gcc
LD=$(CROSS_COMPILE)ld
CFLAGS_MODULE=-fno-pic

build:
make -C $(KERNELDIR) ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) M=$(PWD) modules
push:
adb push data_breakpoint.ko /data/local/tmp/
install:
adb push data_breakpoint.ko /data/local/tmp/
adb shell su -c "insmod /data/local/tmp/data_breakpoint.ko ksym=selinux_enforcing"
# adb shell su -c "chmod 777 /dev/vuldev"
uninstall:
adb shell su -c "rmmod data_breakpoint"
clean:
rm -rf .tmp_versions
rm *.o *.ko *.mod.c *.order *.symvers
rm .*
如上,在 install 时就会监控 selinux_enforcing 处的值

可以根据自己需求更改 data_breakpoint.c,下面是简单分析

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
// 回调函数,当触发硬件断点时会执行该处
static void sample_hbp_handler(struct perf_event *bp,
struct perf_sample_data *data,
struct pt_regs *regs)
{
printk(KERN_INFO "%s value is changed\n", ksym_name);
dump_stack();
printk(KERN_INFO "Dump stack from sample_hbp_handler\n");
}

static int __init hw_break_module_init(void)
{
int ret;
struct perf_event_attr attr; //硬件断点是借助perf来实现的

hw_breakpoint_init(&attr); // 初始化
attr.bp_addr = kallsyms_lookup_name(ksym_name); // 获取符号对应的内存地址
attr.bp_len = HW_BREAKPOINT_LEN_8; // 设置监控的内存位
attr.bp_type = HW_BREAKPOINT_W; // 设置欲监控的内存状态

// 注册回调函数
sample_hbp = register_wide_hw_breakpoint(&attr, sample_hbp_handler, NULL);
if (IS_ERR((void __force *)sample_hbp)) {
ret = PTR_ERR((void __force *)sample_hbp);
goto fail;
}

printk(KERN_INFO "HW Breakpoint for %s write installed, address = 0x%lx\n", ksym_name, attr.bp_addr);

return 0;

fail:
printk(KERN_INFO "Breakpoint registration failed\n");

return ret;
}

hw_breakpoint demo

参考