

[原创]一个烂尾的Android内联hook框架
source link: https://bbs.pediy.com/thread-267739.htm
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

之前因为有某些需求想找一个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的结构体:
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的代码如下:
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再进行保存。
判断指令类型的代码如下:
bool
get_type(address_t addr)
{
#if defined(__arm64__) || defined(__aarch64__)
return
((int64_t)addr &
0x1
);
#else
return
((int32_t)addr &
0x1
);
#endif
}
备份指令的函数如下:
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开始执行之前,需要先对其中的变量进行初始化,以下为需要初始化的值:
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寄存器比较特殊,在跳转到保存上下文代码块时,寄存器的值已经发生变化,因此需要单独对其进行保存
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
再进行上下文环境保存,代码如下:
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:
与保存上下文相对应的,以下是恢复上下文的代码块:
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的代码:
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被填充为以下函数:
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的值被填充为以下函数的地址:
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:
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
总体来讲arm上的inline hook框架要比x86难实现一点,传参方式使用寄存器加栈顶空间,还需要对两种不同类型的指令进行判断和修复。目前的代码只能说是写了一个开头,要想做一个完整的框架,还是需要一点工作量的。
[看雪官方培训] Unicorn Trace还原Ollvm算法!《安卓高级研修班》2021年6月班火热招生!!
Recommend
-
41
本次教各位小伙伴使用内联框架来实现页面滚动,除此之外还会有一些小案例 ~ 先来看看 天猫 首页 顶部栏 的效果 ~ (以上图片已是上传的极限, 各位看官要是看不清,请自行下载天猫,打开首页观看) 所以今天我们就拿天猫首页的顶部栏为例子,来讲解在 Axure 里
-
31
2018年,共享单车“至暗时刻”,曾经的两大巨头摩拜单车和ofo,迎来各自命运关口:摩拜单车“卖身”美团;ofo风雨飘摇,陷入各种传闻,并被指押金难退、债台高筑……◎记者蔡淑敏汪建君共享单车生产第一镇——天津市武清区王庆坨镇,早已从狂欢走向
-
63
毫无疑问,好的影视剧中,角色与演员是互相成就的,凭借某一角色一战成名的演员数不胜数,这都与他们演技的磨砺分不开关系。这也是国内影视剧制作必须学习的方面。
-
43
近日,有记者探访到,武汉弘芯半导体千亿级项目现场已烂尾。据报道,项目似乎因拖欠工程款而完全停工,现场也如烂尾楼一样凋敝。
-
45
武汉弘芯的烂尾,揭开了国产芯片乱象的冰山一角。 这家号称投资额高达1280亿元,拥有 “国...
-
31
芯片制造是一场持久的攻坚战,从来不是造富运动。
-
8
2021-04-30 11:53看了越多烂尾的漫画,就越感到《钢炼》的可贵本文来自微信公众号:游戏研究社(ID:yysaag),作者:Lushark,题图来源:《钢之炼金术师》剧照想...
-
9
本文作者:郑超最近团队升级静态代码检测能力,依赖的相关编译检测能力需要用到较新的agp,而且目前云音乐agp版本用的是...
-
4
警惕!烂尾暗涌 - 精选 - 商界网 | 商界APP-专注于商人-企业以及商业思维警惕!烂尾暗涌 崔小花 2021-10-08 13:47:48...
-
10
魔幻的石家庄楼市:官方通报44个烂尾项目,16家开发商被立案侦查 2021年...
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK