-
-
[原创]一个烂尾的Android内联hook框架
-
2021-5-23 14:47
10460
-
1.引言
之前因为有某些需求想找一个arm平台的inline hook框架,但是没能找到一个既精简、又支持修改多参数、修改返回值、支持用自定义函数替换掉原函数的框架。所以就决定花点时间自己实现一个,写了几天以后被告知不用做了,于是便有了这个烂尾项目。今天整理文件的时候看到了,与其留着发霉不如分享给坛友作为学习的例子。此代码为没用的草稿,不合规范的地方就不用对其提意见了。
2.需求分析与初步设计
2.1 精简
使用一个结构体和一段shellcode维护一个hook
2.2 支持多线程hook
每个hook维护一段只属于自己的shellcode
2.3 支持修改参数
参数数量小于4的修改r0-r3寄存器,大于4的再修改栈空间
2.4 支持修改返回值
覆盖r0寄存器的值
2.5 支持函数替换
hook函数执行完以后加载原函数下一条指令地址到lr寄存器
总体设计为,每个hook维持一个结构体与一段自己的shellcode,初始化hook时直接修改shellcode中的变量值,备份并修改原函数的起始汇编代码,使其直接跳到shellcode执行,完成其余的工作。所有的hook保存在一个链表中进行统一管理。
3.具体实现
以下为维护hook的结构体:
1 2 3 4 5 6 7 8 9 10 11 12 | typedef struct inline_item{
void * shell_code; / / shelcode的内存起始地址
int32_t shell_code_len; / / shellcode的长度
address_t old_fun; / / 原始函数的起始地址
address_t new_fun; / / hook函数的起始地址
void * back_code; / / 备份的代码
int32_t back_code_len; / / 备份代码的长度
void * reg; / / 保存寄存器空间
void * stack; / / 保存栈空间
list_node_t * param_list; / / 参数链表
void * ret_value; / / 返回值
} inline_item_t;
|
在初始化hook时,首先获取shellcode中变量的内存地址,分配一块内存空间给结构体中的shellcode_code字段,并记录长度,记录原始函数的地址与hook函数的地址,存储原函数的前几条指令作为备份代码,并记录备份代码的长度,param_list中存储有想要修改的参数值,ret_value中存储有想要修改成的返回值。
初始化hook的代码如下:
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 | inline_item_t * init_item(address_t * target_fun, address_t * hook_fun, list_node_t * param_list,address_t ret_value)
{
inline_item_t * p_item = 0 ;
address_t temp = 0 ;
p_item = (inline_item_t * )malloc( sizeof(inline_item_t) );
p_item - >shell_code_len = (int8_t * )&asm_shellcode_end - (int8_t * )&asm_shellcode_begin;
p_item - >shell_code = (int8_t * )malloc( p_item - >shell_code_len); / / 分配shellcode空间
p_item - >old_fun = target_fun; / / 保存原始函数地址
p_item - >new_fun = hook_fun; / / 存储hook函数地址
p_item - >back_code_len = 12 ; / / 备份 12 字节指令
p_item - >back_code = malloc(p_item - >back_code_len);
p_item - >reg = malloc( 0x10 * sizeof(address_t)); / / 分配备份寄存器的空间
p_item - >stack = malloc( 0x10 * sizeof(address_t)); / / 分配备份栈的空间
p_item - >param_list = 0 ;
p_item - >ret_value = 0 ;
if ( param_list ! = 0 ){
p_item - >param_list = param_list; / / 初始化要修改的参数列表
}
if ( ret_value! = 0 ){
p_item - >ret_value = (address_t * )malloc( sizeof(address_t) ); / / 初始化要修改的返回值
ret_value = 10 ;
memcpy( p_item - >ret_value, &ret_value, sizeof(address_t));
}
return p_item;
}
|
在备份原函数前几条指令时需要对指令类型进行判断,如果是thumb指令,则需要对地址减1再进行保存。
判断指令类型的代码如下:
1 2 3 4 5 6 7 8 | bool get_type(address_t addr)
{
return ((int64_t)addr & 0x1 );
return ((int32_t)addr & 0x1 );
}
|
备份指令的函数如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | void back_target(inline_item_t * p_item){
p_item - >back_code_len = BACKLEN;
/ / 保存old_fun前几条指令
p_item - >back_code = (int8_t * )malloc(p_item - >back_code_len);
memcpy(p_item - >back_code, (address_t * )p_item - >old_fun, p_item - >back_code_len);
memcpy(p_item - >shell_code + asm_pos.back_code_pos, p_item - >back_code, p_item - >back_code_len);
/ / 修改old_fun前几条指令
if ( !set_mem_permission(p_item - >old_fun,RWX) ){ / / 修改内存属性为可读可写可执行
return ;
}
memcpy((address_t * )p_item - >old_fun, p_item - >shell_code + asm_pos.load_pc_pos,sizeof(address_t));
memcpy((address_t * )p_item - >old_fun + 1 , &p_item - >shell_code, sizeof(address_t));
memcpy(p_item - >shell_code + asm_pos.load_pc_pos, p_item - >back_code, sizeof(address_t) * 3 );
}
|
指令地址为奇数是thumb指令
在shellcode开始执行之前,需要先对其中的变量进行初始化,以下为需要初始化的值:
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 | asm_old_fun_continue: / / 原函数的地址
.word 0x12345678
asm_new_fun: / / hook函数的地址
.word 0x12345678
asm_param_num: / / 参数的个数
.word 0x00000010
asm_reg_sp: / / 栈顶值
.word 0x12345678
asm_reg_lr: / / 返回地址
.word 0x12345678
callback_modify_param: / / callback_modify_param函数的地址
.word 0x00000000
callback_modify_return: / / callback_modify_return函数的地址
.word 0x00000000
asm_item_addr: / / 当前hook结构体地址
.word 0x12345678
asm_reg_addr:
.word 0x12345678
asm_stack_addr:
.word 0x12345678
asm_back_code: / / 备份的原始函数代码块
.word 0x12345678
.word 0x12345678
.word 0x12345678
ldr r15,asm_old_fun_continue / / 跳转到备份代码的下一条指令继续执行
|
shellcode在执行时,需要先保存原始上下文,由于r13,r14,r15寄存器比较特殊,在跳转到保存上下文代码块时,寄存器的值已经发生变化,因此需要单独对其进行保存
1 2 3 4 5 6 | str r13,asm_reg_sp
str r14,asm_reg_lr
/ / stmfd sp!,{r0} / / 这个需要注意,保存的pc应当是old_fun的下一条指令地址,
/ / sub r0,r15, 0x / / 所以执行到这里以后要保存当前pc减去已经执行的指令步数 * 4
/ / ldmfd sp!,{r0}
bl fun_save_context
|
再进行上下文环境保存,代码如下:
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 | fun_save_context:
stmfd sp!,{r12} / / - - - - - - - 开始保存寄存器
ldr r12,asm_reg_addr
stmea r12!,{r0 - r11} / / 保存r0 - - r11
mov r0,r12
ldmfd sp!,{r12} / / 取出r12
stmea r0!,{r12} / / 保存r12
mrs r1,cpsr / / 保存CPSR寄存器
stmea r0!,{r1}
/ / mov r1,sp / / 不能直接操作sp
/ / stmea r0!,{r1} / / - - - - - - - 寄存器保存完成
ldr r0,asm_stack_addr / / - - 开始保存栈空间
mov r2, 0x10 / / 需要保存的栈空间大小
mov r1,sp
_loop_save_stack:
cmp r2, 0x00
ble _loop_save_context_end
ldr r3,[r1]
str r3,[r0], 0x04
add r1,r1, 0x04
sub r2,r2, 0x01
b _loop_save_stack
_loop_save_context_end: / / - - 栈空间保存完成
bx r14
fun_save_context_end:
|
与保存上下文相对应的,以下是恢复上下文的代码块:
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 | fun_back_context:
ldr r0,asm_stack_addr / / 开始恢复栈空间
mov r1,sp
mov r2, 0x10
_loop_back_stack:
cmp r2, 0x00
ble _loop_back_stack_end
ldr r3,[r0]
str r3,[r1], 0x04
add r0,r0, 0x04
sub r2,r2, 0x01
b _loop_back_stack
_loop_back_stack_end: / / 恢复栈空间完成
ldr r12,asm_reg_addr
ldmfd r12!,{r0 - r11} / / 先取出r0 - r11
stmfd sp!,{r0 - r1}
mov r0,r12
ldmfd r0!,{r12} / / 取出r12
ldmfd r0!,{r1}
msr cpsr,r1
/ / ldmfd r0!,{r1}
/ / mov sp,r1
ldmfd sp!,{r0 - r1}
bx r14
fun_back_context_end:
|
执行hook的代码:
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 | ldr r13,asm_reg_sp
bl fun_back_context / / 保存上下文
ldr r0,asm_new_fun
blx r0 / / 带返回跳转到hook函数
stmfd sp!,{r0,r1}
ldr r1,callback_modify_param
cmp r1, 0x00
beq not_modofy_param / / 判断是否需要修改参数
ldr r0,asm_item_addr
blx r1 / / 如果需要修改参数,以hook结构体作为参数带返回跳转callback_modify_param
not_modofy_param:
ldmfd sp!,{r0,r1}
ldr r13,asm_reg_sp
bl fun_back_context
bl asm_back_code / / 执行原函数
stmfd sp!,{r0,r1}
ldr r1,callback_modify_return
cmp r1, 0x00
beq not_modofy_return / / 执行完原函数后,判断是否需要修改返回值
ldr r0,asm_item_addr / / 如果需要修改返回值,以hook结构体作为参数带返回跳转
blx r1
not_modofy_return:
ldmfd sp!,{r0,r1}
bl fun_back_context / / 恢复上下文
ldr r14,asm_reg_lr / / 将old_fun的lr地址填充回去
bx r14 / / 跳转回正常执行流程
|
以上代码中,callback_modify_param被填充为以下函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | void modify_param(inline_item_t * p_item ){
int n;
int index;
address_t data;
list_node_t * param_list ;
if (!p_item){
return ;
}
param_list = p_item - >param_list; / / 参数存储在结构体的一个链表中
while (param_list ! = 0 ){ / / 遍历整个链表
index = ((param_t * )param_list - >data) - >index;
data = ((param_t * )param_list - >data) - >data;
if ( index < = 3 ){ / / 如果参数小于 3 个,修改寄存器的值
memcpy(p_item - >reg + sizeof(address_t) * index, &data, sizeof(address_t) );
}
else { / / 参数大于 3 个,修改栈顶值
memcpy(p_item - >stack + sizeof(address_t) * (index - 4 ), &data, sizeof(address_t));
}
param_list = param_list - > next ;
}
}
|
callback_modify_return的值被填充为以下函数的地址:
1 2 3 4 5 6 7 | void modify_return(inline_item_t * p_item){
if (!p_item) {
return ;
}
/ / 覆盖r0的值修改返回值
memcpy(p_item - >reg, p_item - >ret_value, sizeof(address_t));
}
|
指令修复部分还没完成。
最后附上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 | NDK_ROOT = ~ / Android / Sdk / ndk / 21.0 . 6113669
TOOL_CHAIN = $(NDK_ROOT) / lone_toolchain_23
CC = $(TOOL_CHAIN) / bin / armv7a - linux - androideabi23 - clang
CFLAG = - g - m32
TARGET = sniper
RM = rm - f
DIR = .
OBJ_DIR = $( DIR ) / obj
SRC_DIR = $( DIR ) / src
SRC = $(wildcard $( DIR ) / * .c)
SRC + = $(wildcard $( DIR ) / * .asm)
OBJ = $(patsubst % .asm, % .o,$(patsubst % .c, % .o,$(SRC)))
all :$(TARGET)
$(TARGET):$(OBJ)
$(CC) $(CFLAG) - o $@ $^
$( DIR ) / % .o:$( DIR ) / % .c
$(CC) $(CFLAG) - c $<
$( DIR ) / % .o:$( DIR ) / % .asm
$(CC) $(CFLAG) - c $<
clean:
$(RM) * .o
$(RM) * .s
install:
adb push $(TARGET) / sdcard
|
4.结语
总体来讲arm上的inline hook框架要比x86难实现一点,传参方式使用寄存器加栈顶空间,还需要对两种不同类型的指令进行判断和修复。目前的代码只能说是写了一个开头,要想做一个完整的框架,还是需要一点工作量的。
[培训]《安卓高级研修班(网课)》月薪三万计划,掌握调试、分析还原ollvm、vmp的方法,定制art虚拟机自动化脱壳的方法
最后于 2021-5-23 14:53
被某警官编辑
,原因: