https://github.com/GToad/Android_Inline_Hook_ARM64
有32和64的实现,但是是分离的,要用的话还要自己把两份代码合并在一起。
缺点:
1、不支持函数替换(即hook后不执行原函数),现在只能修改参数寄存器,无法修改返回值。
2、不支持定义同类型的hook函数来接受处理参数,只能通过修改寄存器的方式修改参数。
多余4个/或者占两个字节的参数,那么参数还要自己从栈上捞取。所以issues中说的把mov r0,sp去掉用来接收参数也是有问题的,就是参数在栈上的情况,传过来的时候sp不是原来的sp了。
因为以上的两个缺点,想来没太多人用也是情理之中了,因为自己解析参数、不能修改返回值、不能不执行原函数,局限太大了。看来要想用起来,还得自己修改代码。。。
https://github.com/asLody/whale
移植好像比较简单,记得好像移植过,但是hook了某个系统函数回调原函数就崩溃了。所以用之前可能要先帮他找一遍bug、修复。
后记:art hook的之前看过,应该说是xposed/frida的另一份代码,frida也是用的xposed的方式,只不过不修改系统文件、通过动态调用系统函数实现。frida是js的代码,这个是c/c++的实现。
内联hook大概看了下其实也是一样的套路,32位采用ldr pc的方式跳到hook函数,64位使用x17寄存器跳到hook函数。剩下的修复原函数也是一样的。只是没时间完整看一遍定位bug了。
https://github.com/jmpews/HookZz
对于安卓程序员来说不太友好,编译需要cmake。
windows:
mkdir build_for_android_arm64 && cd build_for_android_arm64
set ANDROID_NDK=D:\android\NDK\android-ndk-r16b
C:\Users\EDZ\AppData\Local\Android\Sdk\cmake\3.10.2.4988404\bin\cmake .. -DCMAKE_TOOLCHAIN_FILE=%ANDROID_NDK%/build/cmake/android.toolchain.cmake -DCMAKE_BUILD_TYPE=Release -DANDROID_ABI="arm64-v8a" -DANDROID_NATIVE_API_LEVEL=android-21 -DSHARED=OFF -DHOOKZZ_DEBUG=OFF -G "Unix Makefiles" -D"CMAKE_MAKE_PROGRAM:PATH=D:\app\mingw-w64\x86_64-8.1.0-posix-seh-rt_v6-rev0\mingw64\bin\mingw32-make.exe"
需要指定-G "Unix Makefiles",默认的NMake Makefiles编译不过,指定make,因为未设置环境变量,-D"CMAKE_MAKE_PROGRAM:PATH="
编译动态库,-DSHARED=ON;编译静态库,-DSHARED=OFF。
接下来为了方便还是移植到Android工程中吧。
呃。。。基本不可用!Android:arm/arm64均crach,arm64可以修复,https://www.gitmemory.com/foundkey,在OneLib\stdcxx\LiteIterator.cc中函数initWithCollection添加inCollection->initIterator(innerIterator);
但是arm还是crash,
且通过arm64测试来看,open、fopen可以hook成功。__system_property_get函数,第二个参数既是入参也做返回参数的情况无法正确hook,可以hook到,但是回调原函数,无论是使用第二个参数还是new参数均无法得到值,所以肯定哪里存在bug。
一个函数只能被hook一次,再次hook调用原函数(备份的第一个hook函数)崩溃,所以两次hook不能再调用原函数。
基于以上种种情况,可能还是自己实现Android_Inline_Hook比较好,毕竟Android_Inline_Hook代码易懂,hookZz太多无关代码,没时间看架构了。
后记:后来大概看了一下,32位也是ldr pc实现,好像也做了保存寄存器等操作,和Android_Inline_Hook基本是一样的,64位好像也是使用x17寄存器。其他也都是大同小异。所以也是没时间完整看一遍定位bug了。
因为以上的问题,目前/或者之前用过的一些hook框架或多或少都有些较大的bug(hookzz之前的某个版本应该是可以的),而对其进行修复成本较高,还不如自己写一个。
首先统一一些概念。把覆盖被hook函数的指令称为跳板0,因为这部分指令越短越好,所以其大多数情况下只是一个跳板,跳到真正的用于hook指令处,这部分指令称为shellcode(shellcode不是必须的,比如跳板0直接跳到hook函数去执行,那么就不需要shellcode)。
这里假设都有一些arm指令的基础了,或者对hook已经有些理解了。后来我想了下我这篇更偏向于怎么写一个稳定可用hook框架,更偏向设计、编程,所以适合已经有些基础的,不是从0讲述hook到实现,虽然接下来的部分也会有很细节的部分,但是是针对一些特定的点。建议可以先看下其他人,比如ele7enxxh、GToad写的一些文章。
inline hook这种东西,我是感觉当你掌握汇编、自己有需求的情况下,不经过学习也是可以从0写出一个hook框架的,确实是原理很简单的。
最容易想到的一种实现方式,使用跳板0覆盖一个函数的指令,当执行到这个函数的时候其实就是执行跳板0,跳板0在不修改寄存器的情况跳到执行hook函数。如果在hook函数内不需要执行原函数是最简单的,到这hook就是一个完整的hook。
如果需要执行原函数,那么在跳板0覆盖指令之前先备份指令,执行原函数之前把备份的指令再覆盖回去,执行之后再覆盖回跳板0。确实是最简单的方法、也确实可以,但是也有一个很大的问题,就是多线程的问题,在把备份的指令覆盖回去之后,其他线程执行到这里不就hook不到了,甚至crash。
因为无法加锁(真正有效的锁),而暂停所有线程的太重了,所以基本上只有自己确定某个函数不存在多线程问题或者无需调用原函数才可用。写个demo,熟悉下hook还行,真的实际使用是不行的。
也是因为这个问题,基本上目前的inline hook都会避免再次覆盖指令。不能覆盖回去,那么就直接执行备份的指令,执行后再跳到跳板0之后再继续执行。
也确实是可行的,大部分指令是可以这么做的,但是也有例外。比如备份的指令中有b/bl指令跳到一个偏移地址,跳转到的地址等于当前地址+偏移。而备份后的指令取得当前地址肯定是不等于原来的当前地址的,所以就跳到错误的地址去执行了。
好在我们可以进行修复,计算出原来要跳到的绝对地址,把这条b指令替换成ldr pc指令。其他指令的一些修复也是类似的道理。
Android_Inline_Hook就是这样的实现,只能接收读写寄存器和堆栈,原函数还运行。那么其实这种方式主要作用就是读写参数寄存器、栈,不能读写函数返回值,如果是不受参数控制流程的函数就无能为力了(当然是可以逆向分析,在已经得到返回值的指令处hook,但是应用场景太小了)。
那么实现的核心就是,跳板0->dumpshellcode。dumpshellcode把寄存器以数组的形式存放(栈就是天然的数组),把这个数组传递给dump函数,dump函数接收处理寄存器(未生效)。dump函数返回shellcode,恢复数组数据到寄存器,完成恢复或者修改。执行backupshellcode(备份的原函数的开头部分,修复pc,跳回原函数之后的部分)完成原函数的执行流程。
最常用、最符合使用习惯的的方式。
实现的核心,跳板0->funshellcode。funshellcode可以在上面的dumpshellcode基础上实现也可以全新实现。
在dumpshellcode基础上可以取巧一些,前面的保存dump寄存器保留,把后面的执行backupshellcode换成执行hook函数。
全新实现就是不保存寄存器,那么就可以把hook函数放在跳板shellcode中,直接跳到hook函数;也可以跳板0->funshellcode,funshellcode中再跳转到hook函数。
之后就进入hook函数,如果不调用原函数,直接返回一个返回值或者void函数什么都不做即可(如果是参数也做返回值的情况就修改参数)。如果调用原函数,就通过一个结构体/map等查询被hook函数地址对应的backupshellcode,把backupshellcode转成函数指针,传参调用,即可完成原函数的调用、或者返回值。
在dump的基础上,执行backupshellcode(备份的原函数的开头部分,修复pc,跳回原函数之后的部分)完成原函数的执行流程之后返回到dumpshellcode,调用另一个dump函数,读写返回值(r0/x0,r1/x1寄存器)。不同于dump的地方在于如果要返回到dumpshellcode,那么在调用backupshellcode之前应该备份原来的lr寄存器。考虑到多线程的问题,肯定是不能用一个固定的变量/地址去存储lr寄存器的,可能被覆盖,同一个线程哪怕是递归调用函数也是有顺序的,所以每个线程的被hook函数使用一个顺序的容器保存lr,后进先出。
在dump的基础上,不执行backupshellcode(备份的原函数的开头部分,修复pc,跳回原函数之后的部分),直接修改r0/x0、r1/x1寄存器,返回。和dump函数很多地方是一致的,应用于只需要返回值,并不需要原函数执行的情况。
以上四种场景应该是足够满足hook的需要了。
因为无法直接操作pc,那么实现跳转(通用情况)需要占用一个寄存器。要么使用一个不会被使用的寄存器(哪有绝对不会被使用的寄存器),要么先保存这个寄存器,通过栈保存(之前就是忽略了这个问题在固定地址保存寄存器,那么多线程情况下就可能被覆盖),跳过去之后先恢复这个寄存器。例如:
对应shellcode我们很容易实现,但是如果是c/c++函数(我们的hook函数)就麻烦了,直接在函数开头插代码肯定是不行的。在源码中函数第一行内嵌汇编恢复寄存器?很可惜,这种只在无参、无返回值(空实现)、只有内嵌汇编的情况会成功,其他情况下源码中的第一行并不是汇编中的第一行。。。
所以似乎陷入了无解的状态,在llvm中函数开头插指令?似乎太重了。所以回到原点了,就要考虑真的没有不使用寄存器跳转的可能吗?其实还是有的,b或者bl到偏移
ARM64:
B:0x17向前跳转,0x14向后跳转
BL:0x97向前跳转 0x94向后跳转
偏移地址计算过程:
(目标地址 - 指令地址)/ 4 = 偏移
// 减8,指令流水造成。
// 除4,因为指令定长,存储指令个数差,而不是地址差。
完整指令:
.text:000000000008CC84 8D B3 FF 97 BL je_arena_malloc_hard
.text:0000000000079AB8 je_arena_malloc_hard
计算偏移:
(79AB8-8CC84) / 4 = FFFFFFFFFFFFB38D
FFB38D | 0x97000000 = 97FFB38D
所以理论上,如果被hook的函数和hook函数/跳板/shellcode的距离在正负67108860/64m的范围内,64m=67108864,还有0,比如BL 0=00000094。那么这样就可以省一个寄存器了,针对arm64不能直接操作pc的问题,这应该是一个解决方案。
那么单指令hook除了异常的方式是不是就是指的这种方式呢?只需要覆盖一条指令,关键是怎么确保被hook函数和hook函数地址在正负64m内呢。怎么能申请到这块地址的内存呢。
也许可以查看proc/pid/maps,在要hook的so附近寻找未使用的空间,然后使用mmap申请,不确定是否可行。
暂时可用的一些方式如下(最终未采用):
实际上到内存中还是被和代码段放在一起都是可读可执行,没有写的权限。
shellcode如下,
awx指定读写执行,在elf中(ida查看)确实是读写执行。如果这个section中仅包含变量,那么在内存中放在可读写的段;如果存在代码或者代码和变量均存在,都是和代码段一样仅可读执行。那么在单独的section中存放蹦床代码意义也不大了,还是需要调用mprotect修改内存权限。不过考虑到如果放在仅可读写的段中,那么万一映射后的rw-p和r-xp超过了64m,不就白瞎了,所以还是以代码或者至少这个section中存在一个函数,保证和被hook的函数都在r-xp。
而如果直接和.text一起,主要是怕对这块内存修改权限引发什么异常,不确定如果代码正在执行,修改权限是否会出问题,所以最好能仅修改蹦床的区间。而且不确定是否有只能修改为读写和读执行的系统,所以可能要先修改成读写,写了之后再修改成读执行?这样会不会也有几率触发问题,但是如果每个蹦床都分配一个页的内存也不现实。。。
这样定义一个无参无返回的函数,倒是可以使这个函数就是内嵌汇编,但是不确定如果ollvm混淆等是否会被拆分、加入垃圾代码等。
而且问题在于怎么自动生成一个蹦床函数。宏定义?那这个宏要在函数之外,不太容易自动化实现,包括蹦床的函数名称、b后面的函数名称。和上面的汇编中添加一个蹦床一样的问题,除非自己实现预处理之类的自动插入汇编代码、宏等,似乎不太现实。。。
似乎很难实现自动化,手动写代码太麻烦且和arm的部分接口、行为不一致。但是如果自己简单测试、不在乎这些也是可以的。
256个蹦床。动态生成蹦床代码,第一条指令固定,第二条指令计算生成。需要hkArry修改为读写执行。
so的r-xp中应该是有未使用的多余的内存的,为了对齐、页面等,所以怎么确定多余的空间大小和位置,然后蹦床代码存放其中。
因为种种限制,所以最后采用的还是保存一个寄存器,而使用哪个寄存器呢,我是选的lr寄存器。为什么不用x16、x17等不用保存的寄存器?这里就涉及到一个标准和规范的问题。
理论上编译器编译的c/c++函数是遵守这个规范的,要么不使用x16、x17寄存器,要么只是临时中转,不会在调用其他函数之后再从x16、x17寄存器取值(因为其他函数可能改变x16、x17),但是内嵌汇编(虽然指定不使用x16、x17寄存器,但还是被编译器使用了)或者人工写的汇编,虽然不常见,但确实存在。而最常见的是plt函数内都是使用x16、x17做中转。所以使用一个不一定会被保存的寄存器不如使用一个会被保存的寄存器。而因为lr寄存器的特殊性,一般使用的话都会先压栈保存,结束恢复(32位不一定恢复,64位是会恢复lr寄存器的,因为不能直接操作pc,多数都是恢复lr,再ret)
所以其实主要还是兼容性考虑,尽量采用一些绕弯的方式不改变任何寄存器,实在没办法的情况下或者标准c/c++函数、非函数任意位置hook的情况下才使用x16、x17寄存器。
难点在于:
1、不知道参数个数、参数类型。
2、需要确定各种类型参数占几个寄存器、可变参数等
其实在源码中定义hook函数的时候是有函数原型的,但是运行时拿不到。忽然想到"c++"函数名的规则,里面包含参数、返回值类型,似乎可行,但是很多情况并不希望导出函数,而且也并不一定都是c++实现的。
那么如果定义一个接口,传入函数原型也不是不行,基本类型就用相应的字符或者枚举标识,其他所有的都是void*指针。可还是怕碰见可变参数函数,不确定参数个数,参数类型,只有接收/实现函数才知道。所以似乎不太可行。
而libffi似乎不太适合这个情况,也是需要明确指令参数和返回类型,还要传输参数。
目前的实现没有经过自己解析参数,只是中转,通过定义一致的函数原型,让编译器帮助我们解析参数。
因为arm64限制条件最多,那么应该先实现arm64,这样才能更好的保证api的通用/一致性,因为arm64不能操作pc不得不这么做,arm32同样也可以这么做,但是如果先实现arm32,可能就先入为主的设计一些arm32可用的api。这算是一种架构思维吧,或者多思考一下就明白了。
其实很简单,注释基本都说明了,使用了24字节,所以如果能确定要hook的函数是标准的c/c++函数,不会使用x16、x17去保存值的话也可以这样,使用16字节,降低被hook函数太短失败的概率。
基本上注释已经说明了。_hk_inf为如下结构体的指针,onPreCallBack函数原型如下。
my_pt_regs对应在栈上存放的寄存器。这里面其他寄存器都容易保存,pc寄存器因为不能直接操作,所以要取巧一些。我是利用bl跳转之前会把下一条指令的地址存放到lr寄存器,那么再跳回读取lr寄存器即可。
当然这里保存pc寄存器其实也不是必须的。
自定义onPreCallBack修改寄存器即可。如果是一个函数,那么能控制的是参数,不能阻止原函数的调用。
和dump不同,在原函数执行后可以对返回值进行读写,且可以只处理参数或者只处理返回值。
因为要读写返回值,所以执行原函数之前需要修改lr寄存器,而读写返回值之后还要恢复正常的流程,那么lr寄存器是需要保存的。在这个shellcode或者结构体的一个字段存储lr都存在多线程覆盖的问题,所以使用和线程绑定的容器存储。那么何时保存呢?考虑到代码复用和最小的更改,那么可以在调用onPreCallBack函数内保存,但是这个函数是用户创建的,不应该让用户参与保存,而且这个onPreCallBack不一定存在。所以做一次中转,shellcode中先跳到一个一定存在的函数preCallBack,preCallBack内保存lr,并调用onPreCallBack(如果存在)。恢复lr也是同样的思路。
目前是使用"c++"容器实现的,考虑到有些项目可能不能用c++,有时间再用c实现map、vector。
和dump不同,不再执行原函数,可以直接修改x0、x1寄存器返回值或者什么都不做。这是四种方式中最简单的一种。
和dump_with_ret大部分是一致的,执行原函数换成执行hook函数。因为这个hook函数不是shellcode,所以没有好办法在开头加入恢复寄存器的指令,那么就要使用一个无关的寄存器,因为要返回所以lr寄存器被保存了也改变了,所以这里就使用lr寄存器即可,blr lr,既跳到lr保存的地址去执行,也会把下条指令的地址存到lr。
以上就是4种hook方式和对应的实现。提供给用户的api接口如下:
以上就是arm64的实现和定义的api接口。arm的只需要依葫芦画瓢即可,比arm64简单多了。
shellcode理论上是arm还是thumb都没有关系,而且Android的编译链中的汇编器不支持大部分的32位Thumb-2指令(默认情况下),
第四条指令编译报错:
Error: lo register required -- `str r14,[sp,#8]'
因为thumb16中能直接操作的寄存器是r0-r7;r8-r12、r14寄存器不能被str、ldr等,ldr pc寄存器也不行,但是可以ldr到r0-r7,再mov到sp寄存器。而thumb模式下的跳板0也是LDR PC, [PC, #0],是32位的thumb32/thumb-2指令,所以理论上armv5和以下的cpu应该是不支持指令的。
因为shellcode中汇编指令超出了thumb16的寄存器范围,使用thumb32/thumb-2指令,汇编器不支持:
所以不用在Android.mk中指定LOCAL_ARM_MODE := arm,因为这样整个module都是arm指令、4字节,浪费了空间,所以默认就行。奇怪的是编译出的文件包含arm和thumb32,混合的。没有细看是什么情况,编译c/c++使用的汇编器不一致吗?
呃,后来看到:经过Google工程师的提醒,对于ARM GCC的汇编器,在汇编文件最上面加入.syntax unified之后,Thumb-2 T3 encoding汇编也能正常使用了,比如:
所以在汇编文件中加入
可以把操作r0-r7寄存器之外的指令写成,ldr.w、str.w、add.w等,也可以不修改,编译器会自动转成thumb32/thumb-2指令。
编译后就是thumb32/thumb-2指令了,
因为这个shellcode变成thumb了,所以构建跳板shellcode时(LDR PC, [PC, #0]/hook thumb函数或者LDR PC, [PC, #-4]/hook arm函数,指令下面的地址要加1)。
所以hook arm函数也可以构建thumb的shellcode,同样的thumb函数也可以构建arm的shellcode。但是为了方便、不考虑四字节对齐,干脆还是都用arm的shellcode吧,只要不指定为thumb(默认可能就是)、不指定.syntax unified就始终编译出来的就是arm的shellcode。毕竟没有那么严重的强迫症,也占不了多少空间。
LDR PC, [PC, #0]/hook thumb函数或者LDR PC, [PC, #-4]/hook arm函数,理论上在armv5及以上就可以,所以无论是arm到thumb还是thumb到thumb,LDR到PC的地址+1就可以了。同样到arm就保证是非奇数的地址即可。
至于这个shellcode编译到.text还是.data都可以。不同的就是.data中是数据,.text中为函数。
只要cpu是armv5以上就行,因为目前很少armv5及以下的cpu了,所以不考虑支持问题。
ldr pc, [pc, #0/#4],可被编译成arm或者thumb32/thumb-2指令。这里可能有人会有些误解,比如我们要hook的一个so是armeabi的,那么thumb函数只支持thumb16,所以是没有ldr pc这样的指令的,那么对于这样的函数还能使用ldr pc覆盖吗?其实是可以的,因为这条指令能不能被正确解析执行是取决于cpu,只要cpu支持thumb32/thumb-2指令(armv5以上的cpu)即可。thumb32/thumb-2指令更像是thumb16的一个超集(16位的指令应该都是相同的,32位的指令会不同),所以不影响后面的thumb16的指令的解析执行。
所以这里我们不是对当前函数的thumb做兼容性适配而是针对cpu的,而如果要适配armv5以下,就多绕一下好了。
arm:
thumb32/thumb-2:
都是占用8条指令,如果thumb函数地址不是4字节对齐的话就加nop或者bx #-2占用10字节。
这里和arm64比有点不同的是多了一个标号/4个字节存放_oriFuc/原函数的地址,这么做的原因是比较无奈的,因为_hk_info取到的是HK_INFO,还需要再计算一次才能得到pOriFuncAddr,这样就需要占用一个寄存器去计算,pc寄存器是不能这么用的,所以还是在shellcode中存储吧。当然其实还可以让_hk_info存放的是pOriFuncAddr这个变量的地址,那么可以一次取到pc寄存器中,但是其他shellcode都要变且不如原来的直观/简单了,同样的方式还可以把pOriFuncAddr放在结构体头部,然后_hk_info存放pOriFuncAddr变量的地址/HK_INFO结构体的地址,也是可以的,但是也是要变动太多。而且其实只有这一个shellcode需要多定义一个_oriFuc,其他是不需要的,所以折衷后就这样了。
demo和arm64一致,api也是一致的。arm和thumb共用这一个shellcode,注意的一点就是如果被hook的函数是thumb,那么_oriFuc存放的地址+1即可。
demo和arm64一致,这里不用多定义一个_oriFuc,因为保存了lr,不是必须使用pc寄存器了。也是让lr寄存器做了很多事情。
demo和arm64一致。
demo和arm64一致。
框架已经搭好了且已经可以运行了,接下来就是一些细节的优化。
shellcode、备份/修复的原函数不能使用malloc等非内存对齐的函数,原因在下面的Android_Inline_Hook中较严重的一些bug有提到。但是每个shellcode都占用一页内存也太浪费了,理想情况是一页用完或者放不下一个shellcode再申请,但是考虑到解除hook释放shellcode等问题,这么做实现起来很复杂,要监听整个流程,暂时没精力写这么完美,所以就把shellcode和备份/修复的原函数放在同一页内存,当然还是用不满,但是至少节约了一些,这样解除hook直接释放这页内存都可以,当然还可以进一步把HK_INFO这个结构体也放在这一页内,但是不确定是否确实有些系统只允许内存权限为rw或者rp,暂时没有把HK_INFO混在一起。
为了测试不刷新缓存的情况,使用了循环,因为实测发现想触发缓存不刷新crash概率太低了。我能想到的就是循环执行一段指令,触发jit,提高指令被缓存的概率。然后修改指令后不刷新缓存,触发crash。关于如何刷新缓存在下面的Android_Inline_Hook中较严重的一些bug有提到。
只是确认下,因为代码写的没问题,所以应该是不会有问题的。
测试不存在hookzz的问题。
基本上上面已经是大部分的框架和部分细节,剩下的对用户api的具体实现都是很灵活的,只要保持和api函数原型和定义行为一致即可(已实现)。
跳板、shellcode、api都实现了,还剩下一个对备份的原函数进行修复。还没有重新开始写,暂时用的Android_Inline_Hook修复pc相关的指令。在写arm的vmp,到时抽取出指令解析和生成的代码就好了,加一些简单的语义分析。
线程暂停的问题,抽时间加上,因为一般自己常用的场景是fork一个进程还未执行应用代码的时候,基本上不会出现多线程导致的crach,不过还是应该提供接口,毕竟有些场景需要,但是怕是也不能百分百解决线程问题,还需要用户自己判断函数行为。
可变参数的传递,如果不修改只是调用原函数倒是有思路了,但是如果是对明确的参数进行修改了,还是要抽时间写解析构造参数的部分。
1、arm64指令修复
例如其中的BL指令并未修复,这个其实还容易修复,可以替换成LDR LR, 8(应该放到最后);BLR LR;因为既然是BL指令那么LR寄存器肯定是会被修改,那就说明LR寄存器已经保存了,所以我们就使用LR寄存器也是没有关系的。
如果是B指令才麻烦,因为不能操作pc,那么就要至少使用一个寄存器且还不能是LR,只能使用x16、x17了。
后面看了下Android_Inline_Hook写了B指令的修复(是错的),BL指令未实现,后来自己写了BL指令的实现,只是暂时用,不准备在这个基础上再写了,缺少的待修复的指令还有不少,且已实现的也有一些错误,待重构。
修复之后Android9.0 arm64的dlopen可以hook了。
2、arm64从备份/修复的原方法跳回跳板0之后执行,采用的方式是在备份/修复的原方法后面加入:
和跳板0是一样的,但是跳板0是一个函数的开头的概率很大,所以一般都是会栈平衡的,sp指向栈顶,所以暂时存放寄存器不会被覆盖。而放在后面就不太确定能保证sp指向栈顶了,所以可能要考虑解析前几条指令是否有操作sp,或者尽可能的把寄存器储存在sp-较大的值,或者使用x16、x17寄存器。。。
当然这个可能概率较小,可以先不管。
3、无法重复hook一个函数,当然这个是说在其他框架或者本框架的另一个版本(或者无法维护保存hook的容器,如果是本框架内是可以重复hook同一个函数的,比如解除hook再hook,或者再扩展成链表的形式,依次调用hook/dump函数),其实就是指令修复不完整,arm的应该是ldr pc没有正确修复,这个事情排在后面,不是特别重要。
也可以参考Cydia Substrate框架,不过有点坑,只处理thumb的情况,thumb可以多次hook,arm下第二次hook不生效。直接判断第一条指令是不是ldr pc, [pc, #-4];是的话就认为是已经被hook过的,直接返回。
可见只有第一次hook生效。
而thumb模式,把上次的shellcode赋给result,再把跳板中的地址替换为新的地址。
感觉是不是写代码的忘了处理arm的,因为arm也是可以同样处理的,呃,无语的bug。
例如这么修复即可,前面再加一行SubstrateHookMemory code(process, symbol, used);修改权限/刷新缓存。
应该VirtualApp hook系统函数之后使用syscall调用,就是因为梆梆等一些壳hook了函数,而VirtualApp使用Cydia Substrate未修复该bug,所以导致hook失败。但是一推理应该是错误的,VirtualApp是先hook的,壳应该是后hook的,所以只能是壳hook失败,所以应该是某些系统函数修复指令不正确导致调用原函数失败,和本篇其实没多大关系,只是推理下。
其实以上bug根本原因还是指令的修复,这是最麻烦的,也是暂时没办法达到百分百修复的,尤其是arm64,只能抽时间逐步完善了。
1、在init_arry函数和普通函数引用vector,但其实不是同一个vector的,导致无法保存已hook的信息,应该用指针,不能直接引用vector。属于代码逻辑bug。
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)
最后于 2020-1-6 15:24
被卓桐编辑
,原因: