type
status
date
slug
summary
tags
category
icon
password
Property
Sep 7, 2023 11:52 AM
本篇将实现一个劫持内核内BL指令跳转到自身模块函数执行的简单inline hook。
ARM64 指令基础
BL 指令
跳转到相对于PC的指定地址,并将下一条指令地址存入LR寄存器。
跳转范围:±128MB
指令格式
地址编码
imm26 负数使用补码表示
正数和0的补码就是该数字本身再补上符号位0。负数的补码则是将其对应正数按位取反再加1。
可能有人会有疑惑,明明立即数只有26位,跳转范围应该是±32MB才对,为什么会是±128MB呢?
因为arm64指令长度都是4字节,所以编码地址的时候除了4,比如跳转 0x4,imm26 是 1
简单例子
使用keystone生成机器码
以上代码输出: 0b10010100000000000000000000000001
模拟运行一下试试吧
输出如下[0x00000000] nop [0x00000004] movz x0, #0x1 [0x00000008] b #0xc [0x00000014] add x0, x0, #1 [0x00000018] add x0, x0, #1
相关内核基础
内核分段及权限设定
一、分了哪些段
见 arch/arm64/kernel/vmlinux.lds.S
- _text → _etext 之间是代码段
- __start_rodata → __end_rodata 之间是只读数据段
- __init_begin → __init_end 之间是内核初始化相关的段,包括代码和数据
- _data → _end 之间是可读可写的数据段
二、各个段的权限设定
见 arch/arm64/mm/mmu.c 的 map_kernel 函数
三、结论
- 代码段:_text → _etext
rodata_enabled ? PAGE_KERNEL_ROX : PAGE_KERNEL_EXEC;
可读特权模式可执行,rodata_enabled为假时可写
- 只读数据段
PAGE_KERNEL
可读可写不可执行
- 初始化代码段:
rodata_enabled ? PAGE_KERNEL_ROX : PAGE_KERNEL_EXEC;
可读特权模式可执行,rodata_enabled为假时可写
- 初始化数据段:
PAGE_KERNEL
可读可写不可执行
- 数据段:
PAGE_KERNEL
可读可写不可执行
寻找对应符号地址
开了KASLR怎么办?摆!
方法一:kallsyms文件
解除内核符号限制
部分设备需要 echo 1 才行
获取符号地址
方法二:kprobe大法 (*推荐)
上代码:
底层原理:
使用 kallsyms_lookup_name 解析符号地址
需高版本内核!
方法三:kallsyms_lookup_name函数
如果该函数导出,可直接使用该函数定位,但是大部分内核中该函数并未导出。
方法四:奇门遁甲
- 将内核丢进IDA分析,依靠字符串和源码慢慢寻找位置
- 特征码定位
通过一些特征汇编代码定位
- 根据导出函数,结合偏移辅助定位
比如 &printk - offsetof(printk) + offsetof(foo)
让我们开始吧
绕过内核只读限制
方法一
修改内核,将 rodata_enabled 改为 0
优点:简单方便,快捷高效
缺点:安全性降低
评价:开发机要什么安全,方便就完了!
方法二
修改pte,给权限加上可写
详见下面代码
开始写hook咯
目标:__arm64_sys_faccessat 的 BL do_faccessat
目的:
/memfd:
今天必须给我存在代码:
问题:
加载成功后没多久就寄掉了,日志中没有原因,就戛然而止,但是还是成功打印了
hijack success!
更好的方法
使用kprobe对内核进行hook,更方便,而且问题也更少
特别注意:
kprobes回调函数的运行期间关闭了抢占,同时也可能关闭中断。因此不论在何种情况下,在回调函数中不能调用会放弃CPU的函数(如信号量、mutex锁等),否则会领取死机重启大礼包。
下期预告:
基于 DirtyPipe漏洞 实现一个简单的 kernel root