-
-
[原创]静态InlineHook的脚本实现
-
2021-9-30 18:42 24867
-
前言
Use lief, keystone and capstone to manually inline hook elf(libil2cpp.so) file
前文说到使用基于LIEF的InlineHook实现 ,在这里我们再借助 keystone 和 capstone 来完善一下这个想法,解决一些比较枯燥且容易出错的事,比如 地址偏移的计算,指令备份还原 ...
功能
- 合并编译出的so中的指定节到我们待修改的so(MergeUtils中recordSymbols屏蔽掉合并后函数地址的变化)
- UnityJumper.addHook 后当前pc已经reset在待写代码的位置,写完代码调用 UnityJumper.endHook
- addHook中也支持查看hook位置的Regs值(printRegs=True),具体实现是读取进入hook代码前对所有用户态寄存器保存在堆栈的值,如果后续修改参数(setArg/getArg)记得保存好FP
- Hook InitArray 的第一个函数(Unity是hook il2cpp_init),在此处获取基址,并将GOT_TABLE中的每一项加上这个基址重新写回
- LDR 指令修复,其他PC相关指令后续再说 TODO .....
- 封装一些常用的函数 android_log_print mprotect callFunction Unity.JNI(解决多参数问题,仅支持int,bool)
小提示
- ins.addGOT() 和 ins.addPtr() :
- addGOT 添加到 GOT_TABLE 启动时候会加基地址,用作blx Rx
- addPtr 添加到 GLOBAL_TABLE ,仅用作存储,但是在 recordSymbol中也会用到addPtr,此处的调用会调用 addGOT
- 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
进入到trampolines
做一次中转,保存上下文同时跳转到hook代码
进入到textCodes
真实的hook代码位置,使用的每一个小功能代码连续且独立成块,用一条b跳过相对寻址占用的位置
GLOBAL_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
继承关系
AsmPatch -> AndroidPatch -> UnityPatch
使用
- ins.addPtr(100) 是在往 GLOBAL_TABLE 添加一项
- ins.getStr("this is a test string!") 是再往 STR_TABLE 添加一项,同时也会添加到 GLOBAL_TABLE
- ins.addHook hook指定的地址并将上下文的状态保存好(trampolines),跳转到 textCodes 同时 setPC 到这个位置,这个位置我们就可以写自己的汇编代码逻辑
- ins.endHook() 结束一个hook,主要是从 textCodes 跳回到 trampolines
- 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
杂谈
- 继(基于LIEF的InlineHook实现)的想法落实。当时是还觉得没啥用,毕竟手动去计算偏移修改容易出错,没有效率就没有生产力
- 虽然目前这个版本只是个初版,一个简单的想法的实现,一定程度的脱离 Dobby 的使用
- 还有很多需要完善的,比如类似dobby核心的指令修复,目前也就只修复了一个ldr
- 尽量将一些可能用到的功能模块化以提升汇编的可读性,以及修改的便利性
- 等arm32搞得差不多了再去搞搞arm64版的
- 欢迎大家一起完善
https://github.com/axhlzy/PyAsmPatch
[手动狗头].png
恭喜ID[飞翔的猫咪]获看雪安卓应用安全能力认证高级安全工程师!!
最后于 2021-10-27 14:33
被唱过阡陌编辑
,原因:
赞赏
他的文章