记录一下
kprobe 类探针都是可以取到进入内核之前的 userspace regs 的
从结构体的定义中可以发现自带了一个所谓的 pt_regs,然而直接打印这个 pt_regs 可以发现,好像得到的 FF 开头的地址实际上都是内核中的地址。因此,我们不能直接访问这个 pt_regs
取 userspace reg
经过一番查找,我在 stack overflow 上发现了一个帖子,其中提到如果需要访问用户寄存器需要先取到 task
可以使用 helper 实现取当前 task
let task = bpf_get_current_task() as *const u8;
至于这个结构体定义,可以参照之前使用 btf 取结构体的方法
使用 map 来储存结构体
然后使用 probe read 把这个结构体读到程序中,然而这个结构体非常大,会导致爆栈
所以要创建一个 map,这里直接使用 array map 来储存
#[map]
static mut TASK_STRUCT_BUF_ARR: Array<[u8; 2048]> = Array::with_max_entries(2, 0);
然后取这个 map 元素的指针,使用 probe read 就可以了
读取寄存器
拿到 task_struct 之后,要读取寄存器,还得了解一下用户层进行 syscall 调用之后,内核是如何保存 context 的
根据 kernel 带师 a3 的说法,是在进入内核态之前压到了内核栈底。但是如果要从 task 取到 pt_regs, 需要一些别的操作。
linux 内核提供了一个宏,用于完成这个操作,位于 arch/{ARCH}/include/asm/processor.h 里面,对于 Android,内核 5.10 版本,简单翻一下就能翻到
#define task_pt_regs(p) \
((struct pt_regs *)(THREAD_SIZE + task_stack_page(p)) - 1)
这个 task_stack_page 只是一个简单的取结构体字段
主要的是这个 THREAD_SIZE 与 -1
事实上就是兜兜转转拿到了内核栈栈底
这个 THREAD_SIZE 也可以通过简单的搜索搜到,事实上就是 1 << 14 (aarch64)
接下来就能用 rust 来实现了
if let Some(task_ptr) = tsk_st {
let tsk_struct_ptr = task_ptr as *mut task_struct;
let stack_ptr = (*tsk_struct_ptr).stack as u64;
let pt_regs_ptr = (stack_ptr + PAGE_SIZE - size_of::<pt_regs>() as u64) as *mut pt_regs;
let pt_regs = bpf_probe_read(pt_regs_ptr).unwrap();
pc = pt_regs.__bindgen_anon_1.user_regs.pc;
}