首页
论坛
课程
招聘
[原创]静态InlineHook的脚本实现
2021-9-30 18:42 23463

[原创]静态InlineHook的脚本实现

2021-9-30 18:42
23463

前言

Use lief, keystone and capstone to manually inline hook elf(libil2cpp.so) file

 

前文说到使用基于LIEF的InlineHook实现 ,在这里我们再借助 keystonecapstone 来完善一下这个想法,解决一些比较枯燥且容易出错的事,比如 地址偏移的计算,指令备份还原 ...


功能

  1. 合并编译出的so中的指定节到我们待修改的so(MergeUtils中recordSymbols屏蔽掉合并后函数地址的变化)
  2. UnityJumper.addHook 后当前pc已经reset在待写代码的位置,写完代码调用 UnityJumper.endHook
  3. addHook中也支持查看hook位置的Regs值(printRegs=True),具体实现是读取进入hook代码前对所有用户态寄存器保存在堆栈的值,如果后续修改参数(setArg/getArg)记得保存好FP
  4. Hook InitArray 的第一个函数(Unity是hook il2cpp_init),在此处获取基址,并将GOT_TABLE中的每一项加上这个基址重新写回
  5. LDR 指令修复,其他PC相关指令后续再说 TODO .....
  6. 封装一些常用的函数 android_log_print mprotect callFunction Unity.JNI(解决多参数问题,仅支持int,bool)

小提示

  1. ins.addGOT() 和 ins.addPtr() :
    • addGOT 添加到 GOT_TABLE 启动时候会加基地址,用作blx Rx
    • addPtr 添加到 GLOBAL_TABLE ,仅用作存储,但是在 recordSymbol中也会用到addPtr,此处的调用会调用 addGOT
  2. ins.addBP() 添加一个死循环,IDA调式BUG的时候使用 (或者ins.resetPC(0x12345678) 后使用)

简介

初始化

记录下用到的五个起始位置

  • GLOBAL_TABLE 用于存放我们需要初始化的一些值,可以理解我们自己写代码的.data
  • STR_TABLE 字符串后期使用可能会比较常用,所以我就单独列出来了一个字符串表
  • GOT_TABLE 初始化时会将该表每一项都给他加上一个soAddr(libil2cpp.so的基地址),用作后续可能的blx Rx
  • trampolines 跳板代码存放位置(1.环境的保存 2.跳转到textCodes 3.textCodes返回时跳转回原代码)
  • textCodes 真实执行的代码位置 (hook代码存放位置)

跳转逻辑

原来的样子
这里在+-32MB范围内建议直接用bl会比较简洁一些
脚本中提供了三种方式的 hook:ldr(3)/b(1)/bl(1)
原来的样子

 

hook之后使用三条指令跳转到 trampolines
hook之后

 

进入到trampolines
做一次中转,保存上下文同时跳转到hook代码
进入到trampolines

 

进入到textCodes
真实的hook代码位置,使用的每一个小功能代码连续且独立成块,用一条b跳过相对寻址占用的位置
进入到textCodes

 

GLOBAL_TABLE
GLOBAL_TABLE

 

STR_TABLE
STR_TABLE

 

(目前有一个已知的问题是对CallStaticVoidMethod参数的构建,具体JValueArray的内存实现大概是猜对了,基本数据类型是没啥问题,主要是引用数据类型不能正确读取)
具体实现如下:

关键代码


fixGot

1
2
3
4
5
def fixGot(self, log):
    self.mprotect(mPtr=functionsMap.get("GOT_TABLE"), size=1024 * 8, log=log)
    self.loadBaseToReg(reg="R9", log=True)
    self.relocationGot(reg="R9")
    self.endHook()
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
# 使用 mprotect 修改内存区域的读写权限
def mprotect(self, mPtr=None, size=4096, prot=7, log=False):
    if mPtr is None:
        self.patchASM("MOV R2,PC")
    else:
        self.loadToReg(self.addPtr(mPtr), reg="R2")
    self.prepareStack(3)
    self.patchASM("MOV R1,R2,LSR#12")
    self.patchASM("MOV R0,R1,LSL#12")
    self.saveRegToStack(reg="R0", index=0)
    self.patchASM("MOV R1,#{}".format(size))
    self.saveRegToStack(reg="R1", index=1)
    self.patchASM("MOV R2,#{}".format(prot))
    self.saveRegToStack(reg="R2", index=2)
    self.jumpTo(self.getRelocation("mprotect"), jmpType="REL", reg="R3", resetPC=False)
    self.patchASM("MOV R3,R0")
    if log:
        self.android_log_print_reg(formart="mprotect ret = %d  args : %p %p %p")
    self.restoreStack(3)
 
# 获取 il2cpp 基地址
# 代码执行到这里的时候我们知道当前的pc值以及当前代码静态的地址,所以我们相减即可得到当前的so基地址
def loadBaseToReg(self, reg="R4", log=False):
    self.loadToReg(self.addPtr(self.currentPC + 7 * self._pSize), reg="R1", fix=1)
    self.patchASM("LDR R2,[R1]")
    self.patchASM("SUB R0,PC,R2")
    self.patchASM("MOV {},R0".format(reg))
    if log:
        self.patchASM("MOV R3,R0")
        self.android_log_print_reg(formart="soAddr -> %p")
 
# 直接理解为用汇编实现的一下代码即可
# while(GOT_TABLE[index]!=0x0){
#       GOT_TABLE[index] += soAddr
# }
def relocationGot(self, reg="R9"):
    self.prepareStack(2)
    self.loadToReg(functionsMap.get("GOT_TABLE"), reg="R5", fix=1)
    self.patchASM("MOV R7,#0")
    self.patchASM("MOV R10,#0")
    # R5:存放指针 R6:存放具体值 R7:存放偏移 R8:CurrentPtr
    self.patchASM("ADD R8,R5,R7")
    self.patchASM("LDR R6,[R8]")
    self.patchASM("CMP R6,#0")
    # 标识结束,直接跳转到 endHook
    self.jumpTo(self.currentPC + self._pSize * 24, jmpType="BEQ", resetPC=False)
    self.patchASM("MOV R3,R8")
    self.patchASM("ADD R10,#1")
    self.saveRegToStack(reg="R6", index=0)
    self.patchASM("ADD R6,R6,{}".format(reg))
    self.saveRegToStack(reg="R6", index=1)
    self.android_log_print_reg(formart="GOT relocation %p ---> %p ---> %p")
    self.patchASM("STR R6,[R8]")
    self.patchASM("ADD R7,R7,#4")
    self.jumpTo(self.currentPC - self._pSize * 26, jmpType="B", resetPC=False)
    self.patchASM("MOV R3,R10")
    self.android_log_print_reg(formart="Finished GOT relocation all:%d")
    self.restoreStack(2)
mprotect

loadBaseToReg

relocationGot

relocationGot


 

继承关系
AsmPatch -> AndroidPatch -> UnityPatch


使用

  1. ins.addPtr(100) 是在往 GLOBAL_TABLE 添加一项
  2. ins.getStr("this is a test string!") 是再往 STR_TABLE 添加一项,同时也会添加到 GLOBAL_TABLE
  3. ins.addHook hook指定的地址并将上下文的状态保存好(trampolines),跳转到 textCodes 同时 setPC 到这个位置,这个位置我们就可以写自己的汇编代码逻辑
  4. ins.endHook() 结束一个hook,主要是从 textCodes 跳回到 trampolines
  5. ins.android_log_print(msg="called this function") 编写的汇编代码调用 log 的 demo ,这里后续可以去拓展到 strcmp strcat 再或者是一些我们常用到的一些其他函数

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
from Hook import MergeUtils, UnityJumper
 
if __name__ == '__main__':
    ins = MergeUtils(r"C:\xxxxxxx\libil2cpp_cp32.so")
    newSoPath = ins.mergeSection(".inject")
 
    # 如果要用到JNI 这些函数是必填项
    ins.recordSymbols({"il2cpp_string_new": 0x2BE988, "FindClass": 0xEE5684, "GetStaticMethodID": 0xEE60EC,
                       "CallStaticVoidMethod": 0xEE74E4})
 
    # 自己需要的Hook的函数名以及地址(记得recordSymbol在UnityJumper构造之前添加)
    ins.recordSymbol("ShowSettings", 0xB69D4C)
    ins.recordSymbol("UI_Splash", 0x41c4c8)
 
    ins = UnityJumper(newSoPath)
 
    ins.addHook(ins.getSymbolByName("UI_Splash"), printRegs=False)
    ins.android_log_print_msg(msg="描述 : called this function")
    # android_log_print_reg 从R3开始,多余的参数使用堆栈传参 prepareStack saveRegToStack restoreStack
    # 以下为一个简单的demo 几句话即可完成 log 带堆栈传参的调用   
    ins.loadToReg(ins.addPtr(ins.getSymbolByName("UI_Splash")), reg="R3")
    ins.patchASM("LDR R3,[R3]")
    ins.loadToReg(ins.getStr("UI_Splash"), reg="R4")
    ins.prepareStack(1)
    ins.saveRegToStack(reg="R4", index=0)
    ins.android_log_print_reg(formart="called from %p (%s)")
    ins.restoreStack(1)
    ins.CallStaticVoidMethod("com/ironsource/unity/androidbridge/AndroidBridge", "onResume", "()V", 0)
    ins.endHook()
 
    ins.save("libil2cpp_final.so")

 

然后就得到了这种 arm32 hook

杂谈

  1. (基于LIEF的InlineHook实现)的想法落实。当时是还觉得没啥用,毕竟手动去计算偏移修改容易出错,没有效率就没有生产力
  2. 虽然目前这个版本只是个初版,一个简单的想法的实现,一定程度的脱离 Dobby 的使用
  3. 还有很多需要完善的,比如类似dobby核心的指令修复,目前也就只修复了一个ldr
  4. 尽量将一些可能用到的功能模块化以提升汇编的可读性,以及修改的便利性
  5. 等arm32搞得差不多了再去搞搞arm64版的
  6. 欢迎大家一起完善

 

https://github.com/axhlzy/PyAsmPatch

 

[手动狗头].png


【公告】欢迎大家踊跃尝试高研班11月试题,挑战自己的极限!

最后于 2021-10-27 14:33 被唱过阡陌编辑 ,原因:
收藏
点赞4
打赏
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回