本篇将实现一个劫持内核内BL指令跳转到自身模块函数执行的简单inline hook。
跳转到相对于PC的指定地址,并将下一条指令地址存入LR寄存器。
跳转范围:±128MB
imm26 负数使用补码表示
正数和0的补码就是该数字本身再补上符号位0。负数的补码则是将其对应正数按位取反再加1。
可能有人会有疑惑,明明立即数只有26位,跳转范围应该是±32MB才对,为什么会是±128MB呢?
因为arm64指令长度都是4字节,所以编码地址的时候除了4,比如跳转 0x4,imm26 是 1
以上代码输出:
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
见 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怎么办?摆!
解除内核符号限制
部分设备需要 echo 1 才行
获取符号地址
上代码:
底层原理:
使用 kallsyms_lookup_name 解析符号地址
需高版本内核!
如果该函数导出,可直接使用该函数定位,但是大部分内核中该函数并未导出。
特征码定位
通过一些特征汇编代码定位
根据导出函数,结合偏移辅助定位
比如 &printk - offsetof(printk) + offsetof(foo)
修改内核,将 rodata_enabled 改为 0
优点:简单方便,快捷高效
缺点:安全性降低
评价:开发机要什么安全,方便就完了!
修改pte,给权限加上可写
详见下面代码
https://github.com/null0333/aarch64_silent_syscall_hook/blob/master/set_page_flags.c#L48
目标:__arm64_sys_faccessat 的 BL do_faccessat
目的:/memfd:
今天必须给我存在
代码:
问题:
加载成功后没多久就寄掉了,日志中没有原因,就戛然而止,但是还是成功打印了hijack success!
使用kprobe对内核进行hook,更方便,而且问题也更少
特别注意:
kprobes回调函数的运行期间关闭了抢占,同时也可能关闭中断。因此不论在何种情况下,在回调函数中不能调用会放弃CPU的函数(如信号量、mutex锁等),否则会领取死机重启大礼包。
下期预告:
基于 DirtyPipe漏洞 实现一个简单的 kernel root
31
|
30
29
28
27
26
|
25
...
0
1
|
0
0
1
0
1
| imm26
31
|
30
29
28
27
26
|
25
...
0
1
|
0
0
1
0
1
| imm26
BL
0x4
0b100101
00000000000000000000000001
BL
-
0x4
0b100101
11111111111111111111111111
BL
0x4
0b100101
00000000000000000000000001
BL
-
0x4
0b100101
11111111111111111111111111
from
keystone
import
*
import
struct
ks
=
Ks(KS_ARCH_ARM64, KS_MODE_LITTLE_ENDIAN)
code, count
=
ks.asm(
"BL 0x4"
)
code
=
struct.unpack(
"<I"
, bytes(code))[
0
]
print
(
bin
(code))
from
keystone
import
*
import
struct
ks
=
Ks(KS_ARCH_ARM64, KS_MODE_LITTLE_ENDIAN)
code, count
=
ks.asm(
"BL 0x4"
)
code
=
struct.unpack(
"<I"
, bytes(code))[
0
]
print
(
bin
(code))
from
capstone
import
*
from
keystone
import
*
from
unicorn
import
*
test_code
=
def
hook_code(_mu, address, size, user_data):
instruction
=
_mu.mem_read(address, size)
for
i
in
cs.disasm(instruction,
0x0
):
print
(
'[0x%08X] %s\t%s'
%
(address, i.mnemonic, i.op_str))
ks
=
Ks(KS_ARCH_ARM64, KS_MODE_LITTLE_ENDIAN)
cs
=
Cs(CS_ARCH_ARM64, CS_MODE_ARM)
code, code_count
=
ks.asm(test_code)
mu
=
Uc(UC_ARCH_ARM64, UC_MODE_ARM)
mu.mem_map(
0x0
,
0x1000
, UC_PROT_ALL)
mu.mem_write(
0x0
, bytes(code))
mu.hook_add(UC_HOOK_CODE, hook_code)
mu.emu_start(
0x0
, code_count
*
4
)
from
capstone
import
*
from
keystone
import
*
from
unicorn
import
*
test_code
=
def
hook_code(_mu, address, size, user_data):
instruction
=
_mu.mem_read(address, size)
for
i
in
cs.disasm(instruction,
0x0
):
print
(
'[0x%08X] %s\t%s'
%
(address, i.mnemonic, i.op_str))
ks
=
Ks(KS_ARCH_ARM64, KS_MODE_LITTLE_ENDIAN)
cs
=
Cs(CS_ARCH_ARM64, CS_MODE_ARM)
code, code_count
=
ks.asm(test_code)
mu
=
Uc(UC_ARCH_ARM64, UC_MODE_ARM)
mu.mem_map(
0x0
,
0x1000
, UC_PROT_ALL)
mu.mem_write(
0x0
, bytes(code))
mu.hook_add(UC_HOOK_CODE, hook_code)
mu.emu_start(
0x0
, code_count
*
4
)
pgprot_t text_prot
=
rodata_enabled ? PAGE_KERNEL_ROX : PAGE_KERNEL_EXEC;
map_kernel_segment(pgdp, _text, _etext, text_prot, &vmlinux_text,
0
,
VM_NO_GUARD);
map_kernel_segment(pgdp, __start_rodata, __inittext_begin, PAGE_KERNEL,
&vmlinux_rodata, NO_CONT_MAPPINGS, VM_NO_GUARD);
map_kernel_segment(pgdp, __inittext_begin, __inittext_end, text_prot,
&vmlinux_inittext,
0
, VM_NO_GUARD);
map_kernel_segment(pgdp, __initdata_begin, __initdata_end, PAGE_KERNEL,
&vmlinux_initdata,
0
, VM_NO_GUARD);
map_kernel_segment(pgdp, _data, _end, PAGE_KERNEL, &vmlinux_data,
0
,
0
);
pgprot_t text_prot
=
rodata_enabled ? PAGE_KERNEL_ROX : PAGE_KERNEL_EXEC;
map_kernel_segment(pgdp, _text, _etext, text_prot, &vmlinux_text,
0
,
VM_NO_GUARD);
map_kernel_segment(pgdp, __start_rodata, __inittext_begin, PAGE_KERNEL,
&vmlinux_rodata, NO_CONT_MAPPINGS, VM_NO_GUARD);
map_kernel_segment(pgdp, __inittext_begin, __inittext_end, text_prot,
&vmlinux_inittext,
0
, VM_NO_GUARD);
map_kernel_segment(pgdp, __initdata_begin, __initdata_end, PAGE_KERNEL,
&vmlinux_initdata,
0
, VM_NO_GUARD);
map_kernel_segment(pgdp, _data, _end, PAGE_KERNEL, &vmlinux_data,
0
,
0
);
echo
0
>
/
proc
/
sys
/
kernel
/
kptr_restrict
echo
0
>
/
proc
/
sys
/
kernel
/
kptr_restrict
cat
/
proc
/
kallsyms | grep xxxxx
cat
/
proc
/
kallsyms | grep xxxxx
uintptr_t kprobe_get_addr(const char
*
symbol_name) {
int
ret;
struct kprobe kp;
uintptr_t tmp
=
0
;
kp.addr
=
0
;
kp.symbol_name
=
symbol_name;
ret
=
register_kprobe(&kp);
tmp
=
kp.addr;
if
(ret <
0
) {
goto out;
/
/
not
function, maybe symbol
}
unregister_kprobe(&kp);
out:
return
tmp;
}
uintptr_t kprobe_get_addr(const char
*
symbol_name) {
int
ret;
struct kprobe kp;
uintptr_t tmp
=
0
;
kp.addr
=
0
;
kp.symbol_name
=
symbol_name;
ret
=
register_kprobe(&kp);
tmp
=
kp.addr;
if
(ret <
0
) {
goto out;
/
/
not
function, maybe symbol
}
unregister_kprobe(&kp);
out:
return
tmp;
}
__arm64_sys_faccessat
var_s0
=
0
HINT
STR
X30, [X18],
STP X29, X30, [SP,
MOV X29, SP
LDR W8, [X0]
LDR X1, [X0,
LDR W2, [X0,
MOV W3, WZR
MOV W0, W8
BL do_faccessat
LDP X29, X30, [SP
+
var_s0],
LDR X30, [X18,
HINT
RET
; End of function __arm64_sys_faccessat
__arm64_sys_faccessat
var_s0
=
0
HINT
STR
X30, [X18],
STP X29, X30, [SP,
MOV X29, SP
LDR W8, [X0]
LDR X1, [X0,
LDR W2, [X0,
MOV W3, WZR
MOV W0, W8
BL do_faccessat
LDP X29, X30, [SP
+
var_s0],
LDR X30, [X18,
HINT
RET
; End of function __arm64_sys_faccessat
static const uint32_t mbits
=
6u
;
static const uint32_t mask
=
0xfc000000u
;
/
/
0b11111100000000000000000000000000
static const uint32_t rmask
=
0x03ffffffu
;
/
/
0b00000011111111111111111111111111
static const uint32_t op_bl
=
0x94000000u
;
/
/
"bl"
ADDR_PCREL26
typedef
long
(
*
do_faccessat_t)(
int
, const char __user
*
,
int
,
int
) ;
static do_faccessat_t my_do_faccessat;
unsigned
int
orig_insn, hijack_insn;
unsigned
long
func_addr, insn_addr
=
0
;
uintptr_t kprobe_get_addr(const char
*
symbol_name) {
int
ret;
struct kprobe kp;
uintptr_t tmp
=
0
;
kp.addr
=
0
;
kp.symbol_name
=
symbol_name;
ret
=
register_kprobe(&kp);
tmp
=
(uintptr_t)kp.addr;
if
(ret <
0
) {
goto out;
/
/
not
function, maybe symbol
}
unregister_kprobe(&kp);
out:
return
tmp;
}
bool
is_bl_insn(unsigned
long
addr){
uint32_t insn
=
*
(uint32_t
*
)addr;
const uint32_t opc
=
insn & mask;
if
(opc
=
=
op_bl) {
return
true;
}
return
false;
}
uint64_t get_bl_target(unsigned
long
addr){
uint32_t insn
=
*
(uint32_t
*
)addr;
int64_t absolute_addr
=
(int64_t)(addr)
+
((int32_t)(insn << mbits) >> (mbits
-
2u
));
/
/
sign
-
extended
return
(uint64_t)absolute_addr;
}
uint32_t build_bl_insn(unsigned
long
addr, unsigned
long
target){
uint32_t insn
=
*
(uint32_t
*
)addr;
const uint32_t opc
=
insn & mask;
int64_t new_pc_offset
=
((int64_t)target
-
(int64_t)(addr)) >>
2
;
/
/
shifted
uint32_t new_insn
=
opc | (new_pc_offset & ~mask);
return
new_insn;
}
uint32_t get_insn(unsigned
long
addr){
return
*
(unsigned
int
*
)addr;
}
void set_insn(unsigned
long
addr, unsigned
int
insn){
cpus_read_lock();
*
(unsigned
int
*
)addr
=
insn;
cpus_read_unlock();
}
long
hijack_do_faccessat(
int
dfd, const char __user
*
filename,
int
mode,
int
flags){
char prefix[
8
];
pr_emerg(
"hijack success!"
);
copy_from_user(prefix, filename,
8
);
prefix[
7
]
=
0
;
pr_emerg(
"access: %s"
, prefix);
if
(strcmp(prefix,
"/memfd:"
)
=
=
0
) {
pr_emerg(
"magic!"
);
return
0
;
}
return
my_do_faccessat(dfd, filename, mode, flags);
}
int
ihd_init(void){
int
i;
/
/
获取函数地址
func_addr
=
kprobe_get_addr(
"__arm64_sys_faccessat"
);
pr_emerg(
"func_addr:%lX, "
, func_addr);
/
/
遍历内存找到BL指令地址
for
(i
=
0
; i <
0x100
; i
+
+
){
if
(is_bl_insn(func_addr
+
i
*
0x4
)) {
insn_addr
=
func_addr
+
i
*
0x4
;
break
;
}
}
if
(insn_addr
=
=
0
) {
/
/
未找到BL指令
return
-
ENOENT;
}
orig_insn
=
get_insn(insn_addr);
my_do_faccessat
=
(do_faccessat_t)insn_addr;
pr_emerg(
"insn_addr:%lX, "
, insn_addr);
pr_emerg(
"orig_insn:%X orig_target_addr:%lX"
, orig_insn, get_bl_target(insn_addr));
hijack_insn
=
build_bl_insn(insn_addr, (unsigned
long
)&hijack_do_faccessat);
set_insn(insn_addr, hijack_insn);
pr_emerg(
"new_insn:%X new_target_addr:%lX"
, hijack_insn, get_bl_target(insn_addr));
return
0
;
}
void ihd_exit(void){
/
/
恢复修改
set_insn(insn_addr, orig_insn);
}
module_init(ihd_init);
module_exit(ihd_exit);
MODULE_LICENSE(
"GPL"
);
MODULE_AUTHOR(
"Ylarod"
);
MODULE_DESCRIPTION(
"A simple inline hook demo"
);
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)
最后于 2022-11-28 16:39
被Ylarod编辑
,原因: