luckfollowme arm 学习篇 08 - dobby function inline hook
# luckfollowme arm 学习篇 08 - dobby function inline hook接下来才是真正的进入主题,也是最关键的地方。
## 查看 dobby inline hook 变化
首先,我们得分析 它在我们 **sum** 方法做了什么,导致跳转到 **fake_sum** 的 **hook** 方法。
又能够通过 **orig_sum** 执行到原方法。
1.首先我们启动下项目,卡在 没有**hook** 的地方
```
adb shell "/data/local/tmp/dobby_test"
输入任意键开始hook...
```
2.然后用ida pro 先分析一遍没有 hook 时候的字节码
</p>
</p>
摘抄出来是这样的,由于之前是分析过了的,您直接看我的注释就可以了
``` asm
# 开辟局部变量空间
SUB SP, SP, #0x40
# 取出 a9 a10
LDR X9,
LDR X8,
# 将 a1 - a8 放入局部变量中
STR W0,
STR W1,
STR W2,
STR W3,
STR W4,
STR W5,
STR W6,
STR W7,
STR X9,
STR X8,
# sum = a1 + a2 + a3 + a4 + a5 + a6 + a7+ a8
LDR W8,
LDR W9,
ADD W8, W8, W9
LDR W9,
ADD W8, W8, W9
LDR W9,
ADD W8, W8, W9
LDR W9,
ADD W8, W8, W9
LDR W9,
ADD W8, W8, W9
LDR W9,
ADD W8, W8, W9
LDR W9,
ADD W8, W8, W9
# 储存 sum 变量
STR W8,
# sum2 = a9 + a10
LDR X8,
LDR X9,
ADD X8, X8, X9
STR X8,
# if (sum > 10)
LDR W8,
SUBS W8, W8, #0xA
# if (sum > 10) 条件不成立
B.LE loc_753EDCE934
# if (sum > 10) 条件成立
B loc_753EDCE91C
; ---------------------------------------------------------------------------
loc_753EDCE91C
# 读取 并 转换 64 位 的 sum
LDRSW X8,
# 读取 sum2
LDR X9,
# sum + sum2
ADD X8, X8, X9
# 获取 _global_sum 全局变量
ADRP X9, #_global_sum@PAGE
# _global_sum = sum + sum2
STR X8,
B loc_753EDCE934
; ---------------------------------------------------------------------------
loc_753EDCE934
# 读取 全局变量
ADRP X8, #_global_sum@PAGE
LDR X8,
# 放入 x0 转换 int32
MOV W0, W8
# 释放局部变量空间
ADD SP, SP, #0x40 ; '@'
# 返回 x0
RET
```
接下来我们输入任意键 完成 **hook**。
```
输入任意键开始hook...
hook sum:0x753edce888
进入到 hook 方法,参数:1,2,3,4,5,6,7,8,10,20
调用 原方法获取的结果:66
sum:66
```
您在看 **ida pro**可能您并不会看到改变,但是您如果在 **sum** 方法的 **0xC** 字节后下个断点
</p>
</p>
此时您就可以发现到变化了
可以发现它修改了我们的 **0XC** 个字节。
其余的并没有任何的变化。
所以我们分析下这 **12** 个字节
```asm
11 00 00 90 31 A2 28 91 ADRL X17, _ZL8fake_sumiiiiiiiimm
20 02 1F D6 BR X17
```
## Dobby inline asm 分析
dobby 生成的 **ADRL** 明显是个伪指令 。 因为他有着 **8个** 字节的长度 ,ida pro 给我们分析成了符号 **_ZL8fake_sumiiiiiiiimm**
所以这明显就是将 **fake_sum** hook 的方法的 **地址** 赋值给了 **X17**
后面的**BR** 是一个绝对跳转。 相较于 **B #立即数** 绝对跳转指令 **BR(branch register)**可以根据寄存器中的地址 跳转到任意地方,寻址更大。
稍微翻译一下就是
```asm
# 后期 pc 当前内存对齐的地址
adrp x17, =当前PC内存对齐地址
# 添加 fake_sum 的偏移
add x17, x17, #0xa28
# 绝对跳转
BR X17
```
### fake_sum arrch64
我们通过 **ADRL X17, _ZL8fake_sumiiiiiiiimm** 进去看看 **fake_sum** 生成的汇编指令:
```asm
FF 83 01 D1 SUB SP, SP, #0x60
FD 7B 05 A9 STP X29, X30,
FD 43 01 91 ADD X29, SP, #0x50 ; 'P'
A9 0B 40 F9 LDR X9,
A8 0F 40 F9 LDR X8,
A0 C3 1F B8 STUR W0,
A1 83 1F B8 STUR W1,
A2 43 1F B8 STUR W2,
A3 03 1F B8 STUR W3,
A4 C3 1E B8 STUR W4,
A5 83 1E B8 STUR W5,
A6 43 1E B8 STUR W6,
A7 03 1E B8 STUR W7,
E9 17 00 F9 STR X9,
E8 13 00 F9 STR X8,
A1 C3 5F B8 LDUR W1,
A2 83 5F B8 LDUR W2,
A3 43 5F B8 LDUR W3,
A4 03 5F B8 LDUR W4,
A5 C3 5E B8 LDUR W5,
A6 83 5E B8 LDUR W6,
A7 43 5E B8 LDUR W7,
AB 03 5E B8 LDUR W11,
EA 17 40 F9 LDR X10,
E8 13 40 F9 LDR X8,
E9 03 00 91 MOV X9, SP
2B 01 00 B9 STR W11,
2A 05 00 F9 STR X10,
28 09 00 F9 STR X8,
C0 FF FF 90 00 08 3B 91 ADRL X0, unk_753EDC6EC2
8B 79 00 94 BL unk_753EDED0D0
08 01 00 F0 ADRP X8, #_ZL8orig_sum@page; orig_sum
08 11 47 F9 LDR X8, ; orig_sum
A0 C3 5F B8 LDUR W0,
A1 83 5F B8 LDUR W1,
A2 43 5F B8 LDUR W2,
A3 03 5F B8 LDUR W3,
A4 C3 5E B8 LDUR W4,
A5 83 5E B8 LDUR W5,
A6 43 5E B8 LDUR W6,
A7 03 5E B8 LDUR W7,
EB 17 40 F9 LDR X11,
E9 13 40 F9 LDR X9,
EA 03 00 91 MOV X10, SP
4B 01 00 F9 STR X11,
49 05 00 F9 STR X9,
00 01 3F D6 BLR X8
E0 1F 00 B9 STR W0,
E1 1F 40 B9 LDR W1,
C0 FF FF 90 00 EC 27 91 ADRL X0, unk_753EDC69FB
76 79 00 94 BL unk_753EDED0D0
E0 1F 40 B9 LDR W0,
FD 7B 45 A9 LDP X29, X30,
FF 83 01 91 ADD SP, SP, #0x60 ; '`'
C0 03 5F D6 RET
```
代码有点多,我们逐步分析,在此之前你先记住 **fake_name** 方法体内部是啥样子,到时候我们一一对比。
```c
install_hook_name(sum,int, int a1, int a2,int a3,int a4,int a5,int a6,int a7,int a8,uint64_t a9,uint64_t a10){
printf("进入到 hook 方法,参数:%d,%d,%d,%d,%d,%d,%d,%d,%llu,%llu \n",a1,a2,a3,a4,a5,a6,a7,a8,a9,a10);
int ret = orig_sum(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10);
printf("调用 原方法获取的结果:%d\n",ret);
return ret;
}
```
### 栈
首先第一行指令是 **SUB SP, SP, #0x60** 表示 局部变量使用的空间
```
|-60 <=== SP 指针位置
|-5C
|-58
|-54
|-50
|-4C
|-48
|-44
|-40
|-3C
|-38
|-34
|-30
|-2C
|-28
|-24
|-20
|-1C
|-18
|-14
|-10
|-C
|-8
|-4
| 0
```
**STP**
**STP (Store Pair)** 用于储存一对寄存器到 寄存器中
所以 下面这条指令执行后
```asm
STP X29, X30,
```
栈应该是这样的:
```
|-60 <=== SP 指针位置
|-5C
|-58
|-54
|-50
|-4C
|-48
|-44
|-40
|-3C
|-38
|-34
|-30
|-2C
|-28
|-24
|-20
|-1C
|-18
|-14
|-10 x29
|-C
|-8 x30
|-4
| 0
```
**x29 和 x30 **是两个 关键的寄存器。
它们分别代表着 **帧指针 FP**和 **链接指针 LR**
LR 各位肯定是知道的。 FP 按照网上的意思是说 它代表着**栈低**SP 代表着**栈顶**
### 参数
下面就是传过来的参数 保存在 栈中,只不过要注意的是,它并不是直接使用 **SP** 而是从栈的 **-10** 的地方开始计算。 这样可以很方便获取栈中 **a9-a10** 的参数
```asm
# 从 sp -10 的地方开算
FD 43 01 91 ADD X29, SP, #0x50 ; 'P'
# 根据调用约定 x0-x7 额外的参数放入栈中
# 所以 x9 = a10
A9 0B 40 F9 LDR X9,
# x8 = a9
A8 0F 40 F9 LDR X8,
# stur 貌似是有符号的 str 指令
# 所以下面储存a1-a8 的参数
A0 C3 1F B8 STUR W0,
A1 83 1F B8 STUR W1,
A2 43 1F B8 STUR W2,
A3 03 1F B8 STUR W3,
A4 C3 1E B8 STUR W4,
A5 83 1E B8 STUR W5,
A6 43 1E B8 STUR W6,
A7 03 1E B8 STUR W7,
# 储存 a10 和 a9
E9 17 00 F9 STR X9,
E8 13 00 F9 STR X8,
```
那么现在换算栈中的样子:
```asm
|-60 <=== SP 指针位置
|-5C
|-58
|-54
|-50
|-4C
|-48
|-44
|-40 a10
|-3C
|-38 a9
|-34
|-30 a8
|-2C a7
|-28 a6
|-24 a5
|-20 a4
|-1C a3
|-18 a2
|-14 a1
|-10 x29
|-C
|-8 x30
|-4
| 0 a10
| 4
| 8 a9
```
### 调用方法
之前的指令应该都只是对标着传参
```c
int fake_sum(sum,int, int a1, int a2,int a3,int a4,int a5,int a6,int a7,int a8,uint64_t a9,uint64_t a10)
```
后面的代码是:
```c
printf("进入到 hook 方法,参数:%d,%d,%d,%d,%d,%d,%d,%d,%llu,%llu \n",a1,a2,a3,a4,a5,a6,a7,a8,a9,a10);
```
来看看对应的 **arm** 段
```asm
# 首先准备x1 ~ x7 的参数 也就是 a1,a2,a3,a4,a5,a6,a7
LDUR W1,
LDUR W2,
LDUR W3,
LDUR W4,
LDUR W5,
LDUR W6,
LDUR W7,
# x11 = a8
LDUR W11,
# 获取 a9 和 a10
LDR X10,
LDR X8,
# 获取栈顶 x9 = sp
MOV X9, SP
# 多余的参数储存在栈中
# = a8
STR W11,
# = a9
STR X10,
# = a10
STR X8,
# 获取 字符串的地址 (进入到 hook 方法,参数...)
ADRL X0, aHookDDDDDDDDLl ; "进入到 hook 方法,参数:%d,%d,%d,%d,%d,%d,%d,%d,"...
# 调用 printf 并 记录 LR
BL printf
```
此时调用后栈应该变成了:
```
|-60 a8 <=== SP 指针位置
|-5C
|-58 a9
|-54
|-50 a10
|-4C
|-48
|-44
|-40 a10
|-3C
|-38 a9
|-34
|-30 a8
|-2C a7
|-28 a6
|-24 a5
|-20 a4
|-1C a3
|-18 a2
|-14 a1
|-10 x29
|-C
|-8 x30
|-4
| 0 a10
| 4
| 8 a9
```
### orig_sum
接下来是重点。我们分析下 **orig_sum** 是如何调用原有的方法。
首先我们看下传参调用的过程:
```asm
# 获取 orig_sum 方法地址放入到 x8 中
ADRP X8, #_ZL8orig_sum@PAGE; orig_sum
LDR X8, ; orig_sum
# 准备 x0-x7 的参数
LDUR W0,
LDUR W1,
LDUR W2,
LDUR W3,
LDUR W4,
LDUR W5,
LDUR W6,
LDUR W7,
# 其余的参数放入到 栈中
# x11 = a9
LDR X11,
# x9 = a10
LDR X9,
# x10 = sp
MOV X10, SP
# = a9
STR X11,
# = a10
STR X9,
# 调用 orig_sum
BLR X8
```
接下来我们进入 **orig_sum** 看看它做了什么:
</p>
</p>
不知道各位熟悉这个代码么?
如果大伙不记得看下面的图, **(只看前12个字节)**
</p>
</p>
这个图就是之前 **没有hook前** sum方法的图。
由于之前的忘记 按 Q转换 并且没有显示字节码。 但我可以肯定告诉你,它们是一样的
我把 **orig_sum** 截取出来大伙看看
```asm
FF 03 01 D1 SUB SP, SP, #0x40 ; '@'
E9 23 40 F9 LDR X9,
E8 27 40 F9 LDR X8,
_753EDCE894
51 00 00 58 LDR X17, =0x753EDCE894
20 02 1F D6 BR X17
```
**前面12 个字节**都好说,就是之前 **sum** 被替换成 跳转指令之前的样子
下面是目前被替换成的代码:
```asm
# 获取 fake_sum 地址 放入 x17
11 00 00 90 31 A2 28 91 ADRL X17, _ZL8fake_sumiiiiiiiimm
# 绝对地址跳转 到 fake_sum
20 02 1F D6 BR X17
```
那后面的两条指令是跳到哪里呢?
```asm
51 00 00 58 LDR X17, =0x753EDCE894
20 02 1F D6 BR X17
```
我们在 **ida pro** 按 **G** 跳转下:
</p>
</p>
可以很明显的发现**=0x753EDCE894** 地址就是 原来 **sum**的 **12字节** 后面的地方。
那么当执行完 **sum** 方法后,又如何跳到 我们的 **fake_sum** 接着往下执行呢?
之前说过 , **RET** 指令会根据 **x30(LR)** 链接寄存器的值来跳转。
所以这也是为什么 **fake_sum** 在调用 **orig_sum** 的时候 选择了 **BLR** 指令。
而**orig_sum** 则只使用 **BR** 指令
下面是摘取的 **fake_sum** 和 **orig_sum** 的片段:
```asm
# fake_sum 调用 orig_sum 的片段
ADRP X8, #_ZL8orig_sum@PAGE; orig_sum
LDR X8, ; orig_sum
.....
BLR X8
```
```asm
# orig_sum 跳到 sum 的片段
LDR X17, =0x753EDCE894
BR X17
```
### RET
剩余 **fake_sum** 的返回也没啥好说的。 就是正常返回。
```asm
# 将返回值 放入 x0
E0 1F 00 B9 STR W0,
# 通过 printf 打印下返回值
E1 1F 40 B9 LDR W1,
C0 FF FF 90 00 EC 27 91 ADRL X0, unk_753EDC69FB
76 79 00 94 BL printf
E0 1F 40 B9 LDR W0,
# 还原 x29 x30 寄存器
FD 7B 45 A9 LDP X29, X30,
# 还原堆栈
FF 83 01 91 ADD SP, SP, #0x60 ; '`'
# 返回 x0
C0 03 5F D6 RET
```
## 总结
基于 dobby 对于 function inline hook 所做的事情简单来说就 2个 步骤
1. **创建 orig_sum 的内存 储存 sum 前12个 字节 并携带 BR 跳回到后12个字节的地方**
2. **修改 sum 的前 12 个字节 跳到 fake_sum 中**
这个就是套娃了,通过跳转指令,把入口转移到新函数中,新函数执行原来函数,执行完lr保存了最最开始的位置继续执行。
最近才有时间去细看跳转和返回,不然真的看不懂文章{:1_937:}
页:
[1]