郁金香汇编代码注入怎么写看雪_如何写一个Android inline hook框架

7f75b1dcbae12a4ab58c98d117d44031.png

本文为看雪论坛精华文章

看雪论坛作者ID:卓桐

Android_Inline_Hook  

https://github.com/GToad/Android_Inline_Hook_ARM64

有32和64的实现,但是是分离的,要用的话还要自己把两份代码合并在一起。

缺点:1、不支持函数替换(即hook后不执行原函数),现在只能修改参数寄存器,无法修改返回值。

2、不支持定义同类型的hook函数来接受处理参数,只能通过修改寄存器的方式修改参数。

多余4个/或者占两个字节的参数,那么参数还要自己从栈上捞取。所以issues中说的把mov r0,sp去掉用来接收参数也是有问题的,就是参数在栈上的情况,传过来的时候sp不是原来的sp了。

因为以上的两个缺点,想来没太多人用也是情理之中了,因为自己解析参数、不能修改返回值、不能不执行原函数,局限太大了。看来要想用起来,还得自己修改代码。

Whale

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了。

HookZz

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:

#00  pc 00025ef0  [anon:libc_malloc]12-12 15:12:53.685 181-181/? I/DEBUG: #01  pc 000081f1  /data/app-lib/com.zhuotong.myhkzz-1/libhookzz.so (LiteCollectionIterator::getNextObject()+28)12-12 15:12:53.685 181-181/? I/DEBUG: #02  pc 00007531  /data/app-lib/com.zhuotong.myhkzz-1/libhookzz.so (gen_thumb_relocate_code(void*, int*, unsigned int, unsigned int)+312)12-12 15:12:53.685 181-181/? I/DEBUG: #03  pc 00007ac1  /data/app-lib/com.zhuotong.myhkzz-1/libhookzz.so (InterceptRouting::Prepare()+56)12-12 15:12:53.685 181-181/? I/DEBUG: #04  pc 00007cc1  /data/app-lib/com.zhuotong.myhkzz-1/libhookzz.so (FunctionInlineReplaceRouting::Dispatch()+12)12-12 15:12:53.685 181-181/? I/DEBUG: #05  pc 00007d4d  /data/app-lib/com.zhuotong.myhkzz-1/libhookzz.so (ZzReplace+120)

且通过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了。

自实现 inline hook  

因为以上的问题,目前/或者之前用过的一些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指令。其他指令的一些修复也是类似的道理。

四种hook形式应用不同的场景
dump读写寄存器、堆栈--一种hook形式

Android_Inline_Hook就是这样的实现,只能接收读写寄存器和堆栈,原函数还运行。

那么其实这种方式主要作用就是读写参数寄存器、栈,不能读写函数返回值,如果是不受参数控制流程的函数就无能为力了(当然是可以逆向分析,在已经得到返回值的指令处hook,但是应用场景太小了)。

那么实现的核心就是,跳板0->dumpshellcode。dumpshellcode把寄存器以数组的形式存放(栈就是天然的数组),把这个数组传递给dump函数,dump函数接收处理寄存器(未生效)。

dump函数返回shellcode,恢复数组数据到寄存器,完成恢复或者修改。执行backupshellcode(备份的原函数的开头部分,修复pc,跳回原函数之后的部分)完成原函数的执行流程。

替换/拦截函数,接收处理参数,调用原函数、读写返回值--一种hook形式

最常用、最符合使用习惯的的方式。

实现的核心,跳板0->funshellcode。funshellcode可以在上面的dumpshellcode基础上实现也可以全新实现。

在dumpshellcode基础上可以取巧一些,前面的保存dump寄存器保留,把后面的执行backupshellcode换成执行hook函数。

全新实现就是不保存寄存器,那么就可以把hook函数放在跳板shellcode中,直接跳到hook函数;也可以跳板0->funshellcode,funshellcode中再跳转到hook函数。

之后就进入hook函数,如果不调用原函数,直接返回一个返回值或者void函数什么都不做即可(如果是参数也做返回值的情况就修改参数)。如果调用原函数,就通过一个结构体/map等查询被hook函数地址对应的backupshellcode,把backupshellcode转成函数指针,传参调用,即可完成原函数的调用、或者返回值。

dump读写寄存器、堆栈,调用原函数、读写返回值/寄存器--一种hook形式

在dump的基础上,执行backupshellcode(备份的原函数的开头部分,修复pc,跳回原函数之后的部分)完成原函数的执行流程之后返回到dumpshellcode,调用另一个dump函数,读写返回值(r0/x0,r1/x1寄存器)。

不同于dump的地方在于如果要返回到dumpshellcode,那么在调用backupshellcode之前应该备份原来的lr寄存器。考虑到多线程的问题,肯定是不能用一个固定的变量/地址去存储lr寄存器的,可能被覆盖,同一个线程哪怕是递归调用函数也是有顺序的,所以每个线程的被hook函数使用一个顺序的容器保存lr,后进先出。

dump读写寄存器、堆栈,读写返回值/寄存器--一种hook形式

在dump的基础上,不执行backupshellcode(备份的原函数的开头部分,修复pc,跳回原函数之后的部分),直接修改r0/x0、r1/x1寄存器,返回。和dump函数很多地方是一致的,应用于只需要返回值,并不需要原函数执行的情况。

以上四种场景应该是足够满足hook的需要了。

arm64实现难点

因为无法直接操作pc,那么实现跳转(通用情况)需要占用一个寄存器。要么使用一个不会被使用的寄存器(哪有绝对不会被使用的寄存器),要么先保存这个寄存器,通过栈保存(之前就是忽略了这个问题在固定地址保存寄存器,那么多线程情况下就可能被覆盖),跳过去之后先恢复这个寄存器。例如:

stp X1, X0, [SP, #-0x10];//保存寄存器
...
ldr x0, [sp, #-0x8];//恢复x0寄存器

对应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申请,不确定是否可行。

暂时可用的一些方式如下(最终未采用):

自定义section,增加蹦床,可读写执行

实际上到内存中还是被和代码段放在一起都是可读可执行,没有写的权限。

.section ".my_sec", "awx"
.global _myopen_start//.text
_myopen_start:
    ldr x0, [sp, #-0x8];
    b my_open;
.end//用于手动生成蹦床代码,因为hook代码和这个蹦床一起编译的,所以基本上不会超出加减64m,那么就可以使用b跳转//到偏移,就不用占用一个寄存器了。需要每增加一个hook函数,就加一个蹦床,相应的生成shellcode中跳转到hook//函数的地方都要改成这个蹦床的地址。难就难在这个不好通过内嵌汇编实现,因为b跟的是个偏移值,在汇编中可以使用//函数名称,编译器替换,但是内嵌汇编中不行。而自己计算

shellcode如下:
//用于演示,因为不是最终方案,不写完整代码了。//修改trampoline指向蹦床即可
.data
replace_start:
    ldr x0, trampoline; //如果每一个函数都在源码中新建一个shellcode的话,而不是动态复制生成,那么这个shellcode和蹦床可以合为一个。
    br x0; //不能改变lr寄存器
trampoline: //蹦床的地址
.double 0xffffffffffffffff
replace_end:
awx指定读写执行,在elf中(ida查看)确实是读写执行。如果这个section中仅包含变量,那么在内存中放在可读写的段;如果存在代码或者代码和变量均存在,都是和代码段一样仅可读执行。 那么在单独的section中存放蹦床代码意义也不大了,还是需要调用mprotect修改内存权限。 不过考虑到如果放在仅可读写的段中,那么万一映射后的rw-p和r-xp超过了64m,不就白瞎了,所以还是以代码或者至少这个section中存在一个函数,保证和被hook的函数都在r-xp。
77c5545000-77c557c000 r-xp 00000000 103:37 533353                        /data/app/com.zhuotong.myinhk-TRvqt0ReHRjQeeAnpXDnFQ==/lib/arm64/libInlineHook.so77c558b000-77c558e000 r--p 00036000 103:37 533353                        /data/app/com.zhuotong.myinhk-TRvqt0ReHRjQeeAnpXDnFQ==/lib/arm64/libInlineHook.so77c558e000-77c558f000 rw-p 00039000 103:37 533353                        /data/app/com.zhuotong.myinhk-TRvqt0ReHRjQeeAnpXDnFQ==/lib/arm64/libInlineHook.so
而如果直接和.text一起,主要是怕对这块内存修改权限引发什么异常,不确定如果代码正在执行,修改权限是否会出问题,所以最好能仅修改蹦床的区间。 而且不确定是否有只能修改为读写和读执行的系统,所以可能要先修改成读写,写了之后再修改成读执行?这样会不会也有几率触发问题,但是如果每个蹦床都分配一个页的内存也不现实。
内嵌汇编,在自定义section中增加蹦床
b97948edd456418cdf4e8dcfbcc1bed5.png   这样定义一个无参无返回的函数,倒是可以使这个函数就是内嵌汇编,但是不确定如果ollvm混淆等是否会被拆分、加入垃圾代码等。   而且问题在于怎么自动生成一个蹦床函数。宏定义?那这个宏要在函数之外,不太容易自动化实现,包括蹦床的函数名称、b后面的函数名称。 和上面的汇编中添加一个蹦床一样的问题,除非自己实现预处理之类的自动插入汇编代码、宏等,似乎不太现实。   似乎很难实现自动化,手动写代码太麻烦且和arm的部分接口、行为不一致。但是如果自己简单测试、不在乎这些也是可以的。
在自定义section中预留蹦床空间:
unsigned int hkArry[256*2] __attribute__ ((section(".my_sec")));
//下面为伪代码,通过这样也可以实现不修改/保存寄存器、不修改hook函数,动态生成蹦床完成hook。//优点是不用保存寄存器,缺点是因为正负64m的限制,hook函数应该和hook库是一起编译成动态库/可执行文件的。不能单独使用hook库。//256个蹦床,实际使用可能要考虑这个hkArry内存对齐的问题(如果编译器未做内存对齐)。//unsigned int hkArry[256 * 2] __attribute__ ((section(".my_sec")));//void test_trampoline(){//仅用于演示,应该先设置内存为可读写/执行,// hkArry[0] = 0xf85f83e0;//或者memcpy, ldr x0, [sp, #-0x8];//例如这样计算偏移,组合b指令。// unsigned long offset = (unsigned long) my_open - ((unsigned long) hkArry + 1 * 4);// hkArry[1] = 0x14000000;//计算偏移,生成指令,b 0;//}
256个蹦床。动态生成蹦床代码,第一条指令固定,第二条指令计算生成。需要hkArry修改为读写执行。 fc8f8fe319b043c4e194428ca93bcf66.png
未实现
so的r-xp中应该是有未使用的多余的内存的,为了对齐、页面等,所以怎么确定多余的空间大小和位置,然后蹦床代码存放其中。
为什么尽量不使用x16、x17等不用保存的寄存器?
因为种种限制,所以最后采用的还是保存一个寄存器,而使用哪个寄存器呢,我是选的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寄存器。 解析寄存器、栈取出参数、调用hook函数 难点在于: 1、不知道参数个数、参数类型。 2、需要确定各种类型参数占几个寄存器、可变参数等   其实在源码中定义hook函数的时候是有函数原型的,但是运行时拿不到。忽然想到"c++"函数名的规则,里面包含参数、返回值类型,似乎可行,但是很多情况并不希望导出函数,而且也并不一定都是c++实现的。   那么如果定义一个接口,传入函数原型也不是不行,基本类型就用相应的字符或者枚举标识,其他所有的都是void*指针。 可还是怕碰见可变参数函数,不确定参数个数,参数类型,只有接收/实现函数才知道。所以似乎不太可行。   而libffi似乎不太适合这个情况,也是需要明确指令参数和返回类型,还要传输参数。   目前的实现没有经过自己解析参数,只是中转,通过定义一致的函数原型,让编译器帮助我们解析参数。

代码实现arm64统一的跳板0

因为arm64限制条件最多,那么应该先实现arm64,这样才能更好的保证api的通用/一致性,因为arm64不能操作pc不得不这么做,arm32同样也可以这么做,但是如果先实现arm32,可能就先入为主的设计一些arm32可用的api。 这算是一种架构思维吧,或者多思考一下就明白了。
STP X1, X0, [SP, #-0x10];//因为要使用一个寄存器,所以先保存,当然不一定是x0LDR X0, 8; //把shellcode/hook函数地址存到x0BR X0; //执行shellcode/hook函数ADDR(64)LDR X0, [SP, -0x8];//因为不能操作pc,所以跳回来的时候免不了也要使用一个寄存器
其实很简单,注释基本都说明了,使用了24字节,所以如果能确定要hook的函数是标准的c/c++函数,不会使用x16、x17去保存值的话也可以这样,使用16字节,降低被hook函数太短失败的概率。
LDR    X17, 8;BR    x17;ADDR(64)

代码实现_arm64_dump函数

.include "../../asm/base.s"//.extern _dump_start//.extern _dump_end//.extern _hk_info//.set _dump_start, r_dump_start//.set _dump_end, r__dump_end//.set _hk_info, r__hk_info//.global _dump_start//.global _dump_end//.global _hk_info//.hidden _dump_start//.hidden _dump_end//.hidden _hk_info
...完整代码请阅读原文
基本上注释已经说明了。_hk_inf为如下结构体的指针,onPreCallBack函数原型如下。
//hook信息typedef struct STR_HK_INFO{void (*onPreCallBack)(struct my_pt_regs *, struct STR_HK_INFO *pInfo); //回调函数,在执行原函数之前获取参数/寄存器的函数void * pOriFuncAddr; //存放备份/修复后原函数的地址void (*pre_callback)(struct my_pt_regs *, struct STR_HK_INFO *pInfo); //pre_callback,内部做保存lr的操作,之后回调onPreCallBack,不能被用户操作void (*onCallBack)(struct my_pt_regs *, struct STR_HK_INFO *pInfo); //回调函数,执行原函数之后获取返回值/寄存器的函数void (*aft_callback)(struct my_pt_regs *, struct STR_HK_INFO *pInfo); //aft_callback,内部做恢复lr的操作,之后回调onCallBack,不能被用户操作void *pHkFunAddr; //hook函数,即自定义按照被hook的函数原型构造,处理参数/返回值的函数//以上为在shellcode中通过偏移直接或间接用到的,所以如果有变动,相应的shellcode也要跟着变动
my_pt_regs对应在栈上存放的寄存器。这里面其他寄存器都容易保存,pc寄存器因为不能直接操作,所以要取巧一些。 我是利用bl跳转之前会把下一条指令的地址存放到lr寄存器,那么再跳回读取lr寄存器即可。
bl get_lr_pc; //lr为下条指令
    add lr, lr, #8; //lr为blr x3的地址
    str lr, [sp, #0x108]; //lr当作pc,覆盖栈上的x0
    blr x3
    ...
get_lr_pc:
    ret; //仅用于获取LR/PC
当然这里保存pc寄存器其实也不是必须的。
dump_demo
/**
 * 用户自定义的stub函数,嵌入在hook点中,可直接操作寄存器等
 * @param regs 寄存器结构,保存寄存器当前hook点的寄存器信息
 * @param pInfo 保存了被hook函数、hook函数等的结构体
 */
void onPreCallBack(my_pt_regs *regs, HK_INFO *pInfo) //参数regs就是指向栈上的一个数据结构,由第二部分的mov r0, sp所传递。
{const char* name = "null";if (pInfo) {if (pInfo->methodName) {
            name = pInfo->methodName;
        } else {
            char buf[20];
            sprintf(buf, "%p", pInfo->pBeHookAddr);
            name = buf;
        }
    }// LE("tid=%d onPreCallBack:%s", gettid(), name);.....
自定义onPreCallBack修改寄存器即可。如果是一个函数,那么能控制的是参数,不能阻止原函数的调用。

代码实现_arm64_dump_with_ret函数

//.include "../../asm/base.s"
.global r_dump_start
.global r_dump_end
.global r_hk_info
.hidden r_dump_start
.hidden r_dump_end
.hidden r_hk_info
.data......完整代码请阅读原文查看
和dump不同,在原函数执行后可以对返回值进行读写,且可以只处理参数或者只处理返回值。   因为要读写返回值,所以执行原函数之前需要修改lr寄存器,而读写返回值之后还要恢复正常的流程,那么lr寄存器是需要保存的。在这个shellcode或者结构体的一个字段存储lr都存在多线程覆盖的问题,所以使用和线程绑定的容器存储。那么何时保存呢? 考虑到代码复用和最小的更改,那么可以在调用onPreCallBack函数内保存,但是这个函数是用户创建的,不应该让用户参与保存,而且这个onPreCallBack不一定存在。 所以做一次中转,shellcode中先跳到一个一定存在的函数preCallBack,preCallBack内保存lr,并调用onPreCallBack(如果存在)。恢复lr也是同样的思路。
//头文件#include #include #include typedef std::vector<unsigned long> LRS;//static LRS lrs;struct STR_LR {
};typedef std::map<const void*, LRS*> LR_MAP;//typedef std::map TID_MAP;typedef std::map<pid_t, LR_MAP*> TID_MAP;static TID_MAP * getTid_map();static void saveLR(void* key_fun, unsigned long lr);static unsigned long getLR(void* key_fun);
目前是使用"c++"容器实现的,考虑到有些项目可能不能用c++,有时间再用c实现map、vector。
dump_with_ret_demo
void test_dump_with_ret(){
    LE("open=%p, callback=%p", open, onPreCallBack);if (dump((void *)(open), onPreCallBack, onCallBack, "open") != success) {
        LE("hook open error");
    }int fd = open("/system/lib/libc.so", O_RDONLY);
    LE("open /system/lib/libc.so, fd=%d", fd);
}void test_dump_ret(){
    LE("open=%p, callback=%p", open, onPreCallBack);if (dump((void *)(open), NULL, onCallBack, "open") != success) {
        LE("hook open error");
    }int fd = open("/system/lib/libc.so", O_RDONLY);
    LE("open /system/lib/libc.so, fd=%d", fd);
}
代码实现_arm64_dump_just_ret函数
.global j_dump_start.global j_dump_end.global j_hk_info.hidden j_dump_start.hidden j_dump_end.hidden j_hk_info.dataj_dump_start:
......完整代码请阅读原文查看 
和dump不同,不再执行原函数,可以直接修改x0、x1寄存器返回值或者什么都不做。这是四种方式中最简单的一种。
dump_just_ret_demo
void test_dump_just_ret(){
    LE("open=%p, callback=%p", open, onPreCallBack);if (dumpRet((void *)(open), onCallBack, "open") != success) {
        LE("hook open error");
    }int fd = open("/system/lib/libc.so", O_RDONLY);
    LE("open /system/lib/libc.so, fd=%d", fd);
}

代码实现_arm64_replace函数

.global replace_start
.global replace_end
.global p_hk_info
.hidden replace_start
.hidden replace_end
.hidden p_hk_info
.data
//这种方式尽量用于标准的c/c++函数,因为通过hook函数再调用原函数,只能保证参数寄存器和lr寄存器是一致的,其他寄存器可能被修改。
......完整代码请阅读原文查看
和dump_with_ret大部分是一致的,执行原函数换成执行hook函数。 因为这个hook函数不是shellcode,所以没有好办法在开头加入恢复寄存器的指令,那么就要使用一个无关的寄存器,因为要返回所以lr寄存器被保存了也改变了,所以这里就使用lr寄存器即可,blr lr,既跳到lr保存的地址去执行,也会把下条指令的地址存到lr。
replace_demo
typedef int (*old_open)(const char* pathname,int flags,/*mode_t mode*/...);typedef int (*old__open)(const char* pathname,int flags,int mode);typedef int (*old__openat)(int fd, const char *pathname, int flags, int mode);typedef FILE* (*old_fopen)(const char* __path, const char* __mode);
old_open *ori_open;
old__open *ori__open;//可变参数的函数,需要自己按照被hook函数的逻辑,解析出参数再传递给原函数。//因为并不清楚参数个数/类型,如果不改变参数的情况下还有方法不解析参数调用原函数。//但是如果改变了参数,比如printf中的fmt,那么理论上后面的参数类型个数也应该改变,这种情况下应该是使用者//已经清楚共用多少参数和类型,应该自己调用,而如果只改fmt,应该会出bug的。//所以如果只是想打印明确类型的参数,不改变参数直接调用原函数的情况,应该实现下参数的解析重组/传递,待实现
......完整代码请阅读原文查看

api

以上就是4种hook方式和对应的实现。提供给用户的api接口如下:
#ifdef __cplusplus#include #include #include typedef std::vector INFOS;//typedef std::map LR_MAP;#endifenum hk_status{
    success, hooked, error
};struct RetInfo {enum hk_status status;
    HK_INFO *info;
};
......完整代码请阅读原文查看
arm/thumb实现和难点   以上就是arm64的实现和定义的api接口。arm的只需要依葫芦画瓢即可,比arm64简单多了。
shellcode统一使用arm
shellcode理论上是arm还是thumb都没有关系,而且Android的编译链中的汇编器不支持大部分的32位Thumb-2指令。(默认情况下)
.code 16
//.thumb //和上面效果一致
//.force_thumb //强制thumb
shell_code:
    push {r0, r1, r2, r3} //r0=r0, r1=sp(push之前的sp), r2=r14/lr, r3=cpsr
    mrs r0, cpsr
    str r0, [sp, #0xC]
    str r14, [sp, #8]
第四条指令编译报错:

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指令,汇编器不支持:
Error: unexpected character `w' in type specifier
Error: bad instruction `str.w r14,[sp,#8]'
所以不用在Android.mk中指定LOCAL_ARM_MODE := arm,因为这样整个module都是arm指令、4字节,浪费了空间,所以默认就行。 奇怪的是编译出的文件包含arm和thumb32,混合的。没有细看是什么情况,编译c/c++使用的汇编器不一致吗?   呃,后来看到:经过Google工程师的提醒,对于ARM GCC的汇编器,在汇编文件最上面加入.syntax unified之后,Thumb-2 T3 encoding汇编也能正常使用了,比如:
.syntax unified.text.align 4.globl helloThumb.thumb.thumb_funchelloThumb:add.w    r0, r0, r1, lsl #2bx        lr
所以在汇编文件中加入:
.syntax unified.force_thumb
可以把操作r0-r7寄存器之外的指令写成,ldr.w、str.w、add.w等,也可以不修改,编译器会自动转成thumb32/thumb-2指令。   编译后就是thumb32/thumb-2指令了: c261d7493ab9c864b16b38f014079449.png 因为这个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就保证是非奇数的地址即可。 23bed6efc0a354e64d08ac6a0a307004.png   至于这个shellcode编译到.text还是.data都可以。不同的就是.data中是数据,.text中为函数。
跳板0统一使用ldr pc
只要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/thumb统一的跳板0

arm:
LDR PC, [PC, #-4];流水线,pc为addr下面的指令,所以-4addr:0x12345678
thumb32/thumb-2:
LDR PC, [PC, #0];流水线,pc为addr指令addr:0x12345678
都是占用8条指令,如果thumb函数地址不是4字节对齐的话就加nop或者bx #-2占用10字节。

代码实现_arm_dump函数

.global _dump_start
.global _dump_end
.global _hk_info
.global _oriFuc
.hidden _dump_start
.hidden _dump_end
.hidden _hk_info
.hidden _oriFuc//可用于标准的c/c++函数、非标准函数、函数的一部分(用于读写寄存器),前提都是字节长度足够//非标准函数即非c/c++编译的函数,那么手写汇编可能存在并不遵守约定的情况,比如我们使用了sp寄存器,并在未使用的栈上保存寄存器//但是可能不是满递减而是反过来满递增,或者不遵守栈平衡,往栈上写数据,但是并不改变sp寄存器。当然应该是很少见的。
......完整代码请阅读原文查看
这里和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即可。

代码实现_arm_dump_with_ret函数

.global r_dump_start
.global r_dump_end
.global r_hk_info
.hidden r_dump_start
.hidden r_dump_end
.hidden r_hk_info
.data
r_dump_start: //用于读写寄存器/栈,需要自己解析参数,不能读写返回值,不能阻止原函数(被hook函数)的执行//从行为上来我觉得更偏向dump,所以起名为dump。
......完整代码请阅读原文查看
demo和arm64一致,这里不用多定义一个_oriFuc,因为保存了lr,不是必须使用pc寄存器了。也是让lr寄存器做了很多事情。

代码实现_arm_dump_just_ret函数

.global j_dump_start
.global j_dump_end
.global j_hk_info
.hidden j_dump_start
.hidden j_dump_end
.hidden j_hk_info
.data
j_dump_start:
    push {r0-r4} //r0=r0,中转用 r1=sp(push之前的sp), r2=r14/lr, r3=pc, r4=cpsr
    mrs r0, cpsr
    str r0, [sp, #0x10] //r4的位置存放cpsr
    str r14, [sp, #8] //r2的位置存放lradd     r14, sp, #0x14
    str r14, [sp, #4] //r1的位置存放真实sppop     {r0} //恢复r0
    push {r0-r12} //保存r-r12,之后是r13-r16/cpsr
......完整代码请阅读原文查看
demo和arm64一致。

代码实现_arm_replace函数

.global replace_start
.global replace_end
.global p_hk_info
.hidden replace_start
.hidden replace_end
.hidden p_hk_info
.data
//这种方式尽量用于标准的c/c++函数,因为通过hook函数再调用原函数,只能保证参数寄存器和lr寄存器是一致的,其他寄存器可能被修改。
replace_start:                      //如果只是替换/跳到hook函数,其实是不用保存寄存器的,只是重新写比较麻烦,所以在之前的基础上
    push {r0-r4} //r0=r0,中转用 r1=sp(push之前的sp), r2=r14/lr, r3=pc, r4=cpsr
    mrs r0, cpsr
demo和arm64一致。 细节的优化   框架已经搭好了且已经可以运行了,接下来就是一些细节的优化。
内存对齐
shellcode、备份/修复的原函数不能使用malloc等非内存对齐的函数,原因在下面的Android_Inline_Hook中较严重的一些bug有提到。 但是每个shellcode都占用一页内存也太浪费了,理想情况是一页用完或者放不下一个shellcode再申请,但是考虑到解除hook释放shellcode等问题,这么做实现起来很复杂,要监听整个流程,暂时没精力写这么完美,所以就把shellcode和备份/修复的原函数放在同一页内存,当然还是用不满。 但是至少节约了一些,这样解除hook直接释放这页内存都可以,当然还可以进一步把HK_INFO这个结构体也放在这一页内,但是不确定是否确实有些系统只允许内存权限为rw或者rp,暂时没有把HK_INFO混在一起。
刷新指令缓存
void test_args_11(int a0, int a1, int a2, int a3, int a4, int a5, int a6, int a7, int a8, int a9, int a10, int a11){
    LE("a0=%d, a1=%d, a2=%d, a3=%d, a4=%d, a5=%d,"" a6=%d, a7=%d, a8=%d, a9=%d, a10=%d. a11=%d",
       a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11);
}void nullCallBack(my_pt_regs *regs, HK_INFO *pInfo){
}void test_args_for_cache(){//测试多参数传递情况
    dump((void *)(test_args_11), nullCallBack, /*onCallBack*/NULL);// dumpRet((void *) (test_args_11), onPreCallBack, "test_args_11");
    test_args_11(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11);
    HK_INFO *pInfo = isHooked((void*)test_args_11);// unHook(pInfo);
}......完整代码请阅读原文查看
为了测试不刷新缓存的情况,使用了循环,因为实测发现想触发缓存不刷新crash概率太低了。 我能想到的就是循环执行一段指令,触发jit,提高指令被缓存的概率。然后修改指令后不刷新缓存,触发crash。关于如何刷新缓存在下面的Android_Inline_Hook中较严重的一些bug有提到。
既是入参也是出参的函数
只是确认下,因为代码写的没问题,所以应该是不会有问题的。
int (*ori__system_property_get)(const char *name, char *value);//注意操作参数之前最好都检查参数是否为null,如果原函数也是crash处理还行,如果不是就改变了函数逻辑、进程中断等//所以细节都要注意,参数和返回值也有对应关系int my__system_property_get(const char *name, char *value){
    LE("hk: __system_property_get(%s, %p)", name ? name : "is null", value);
    ori__system_property_get = (int (*)(const char *, char *))(getOriFunByHkFun(
            (void *) (my__system_property_get)));if (!ori__system_property_get) {
        ori__system_property_get = __system_property_get;
    }int ret = ori__system_property_get(name, value);
    LE("ori: __system_property_get(%s, %s)=%d", name ? name : "is null", ret > 0 ? value : "is null", ret);if (name && !strncmp("ro.serialno", name, strlen("ro.serialno"))) {
        strcpy(value, "12345678");return (int)(strlen("12345678"));
    }return ret;
}......完整代码请阅读原文查看
测试不存在hookzz的问题。

todo   基本上上面已经是大部分的框架和部分细节,剩下的对用户api的具体实现都是很灵活的,只要保持和api函数原型和定义行为一致即可(已实现)。   跳板、shellcode、api都实现了,还剩下一个对备份的原函数进行修复。还没有重新开始写,暂时用的Android_Inline_Hook修复pc相关的指令。在写arm的vmp,到时抽取出指令解析和生成的代码就好了,加一些简单的语义分析。   线程暂停的问题,抽时间加上,因为一般自己常用的场景是fork一个进程还未执行应用代码的时候,基本上不会出现多线程导致的crach,不过还是应该提供接口,毕竟有些场景需要,但是怕是也不能百分百解决线程问题,还需要用户自己判断函数行为。   可变参数的传递,如果不修改只是调用原函数倒是有思路了,但是如果是对明确的参数进行修改了,还是要抽时间写解析构造参数的部分。 已知bug 1、arm64指令修复
.text:0000000000000EF8 dlopen ; DATA XREF: LOAD:0000000000000508↑o
.text:0000000000000EF8
.text:0000000000000EF8 var_s0 = 0
.text:0000000000000EF8
.text:0000000000000EF8 ; __unwind {.text:0000000000000EF8 STP X29, X30, [SP,#-0x10+var_s0]!
.text:0000000000000EFC MOV X29, SP
.text:0000000000000F00 MOV X2, X30
.text:0000000000000F04 BL .__loader_dlopen
.text:0000000000000F08 LDP X29, X30, [SP+var_s0],#0x10
.text:0000000000000F0C RET
.text:0000000000000F0C ; } // starts at EF8.text:0000000000000F0C ; End of function dlopen
例如其中的BL指令并未修复,这个其实还容易修复,可以替换成LDR LR, 8(应该放到最后);BLR LR;因为既然是BL指令那么LR寄存器肯定是会被修改,那就说明LR寄存器已经保存了,所以我们就使用LR寄存器也是没有关系的。   如果是B指令才麻烦,因为不能操作pc,那么就要至少使用一个寄存器且还不能是LR,只能使用x16、x17了。   后面看了下Android_Inline_Hook写了B指令的修复(是错的),BL指令未实现,后来自己写了BL指令的实现,只是暂时用,不准备在这个基础上再写了,缺少的待修复的指令还有不少,且已实现的也有一些错误,待重构。 c1161ab62dd593c955cb3dcb223f66c9.png 5ceeec2e30e1ea4b4b67f58d45006a11.png   修复之后Android9.0 arm64的dlopen可以hook了。   2、arm64从备份/修复的原方法跳回跳板0之后执行,采用的方式是在备份/修复的原方法后面加入:
STP X1, X0, [SP, #-0x10]LDR X0, 8BR X0ADDR(64)LDR X0, [SP, -0x8]
和跳板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过的,直接返回。
if (result != NULL) {if (backup[0] == A$ldr_rd_$rn_im$(A$pc, A$pc, 4 - 8)) {
    *result = reinterpret_cast<void *>(backup[1]);return;
}
可见只有第一次hook生效。 48d1944eb8df0e79de1c290baeca4970.png   而thumb模式,把上次的shellcode赋给result,再把跳板中的地址替换为新的地址。
f (
        (align == 0 || area[0] == T$nop) &&
        thumb[0] == T$bx(A$pc) &&
        thumb[1] == T$nop &&
        arm[0] == A$ldr_rd_$rn_im$(A$pc, A$pc, 4 - 8)
    ) {if (result != NULL)
            *result = reinterpret_cast(arm[1]);
        SubstrateHookMemory code(process, arm + 1, sizeof(uint32_t) * 1);
        arm[1] = reinterpret_cast(replace);return sizeof(arm[0]);
    }
19fe56c6b9628d725a2b45a8e1ba4df6.png   感觉是不是写代码的忘了处理arm的,因为arm也是可以同样处理的,呃,无语的bug。 例如这么修复即可,前面再加一行SubstrateHookMemory code(process, symbol, used);修改权限/刷新缓存。 ef974126c2cc91279294778ed01bff3d.png 应该VirtualApp hook系统函数之后使用syscall调用,就是因为梆梆等一些壳hook了函数,而VirtualApp使用Cydia Substrate未修复该bug,所以导致hook失败。 但是一推理应该是错误的,VirtualApp是先hook的,壳应该是后hook的,所以只能是壳hook失败,所以应该是某些系统函数修复指令不正确导致调用原函数失败,和本篇其实没多大关系,只是推理下。   其实以上bug根本原因还是指令的修复,这是最麻烦的,也是暂时没办法达到百分百修复的,尤其是arm64,只能抽时间逐步完善了。 Android_Inline_Hook中较严重的一些bug   1、在init_arry函数和普通函数引用vector,但其实不是同一个vector的,导致无法保存已hook的信息,应该用指针,不能直接引用vector。属于代码逻辑bug。   2、内存权限问题 虽然mprotect的时候取一页内存,但是因为使用的malloc申请的内存可能是夹缝中内存,前后被使用了?也可能是申请后剩余的被其他占用更改了权限?虽然mprotect不报错,修改权限成功了,但是有几率signal 11 (SIGSEGV), code 2 (SEGV_ACCERR)。还要一种可能是malloc返回的地址是奇数的,未测试,理论上如果是奇数的也会crash的,因为内存没有对齐。 所以使用:
long pagesize = sysconf(_SC_PAGE_SIZE);void *pNewShellCode = NULL;// = malloc(sShellCodeLength);int code = posix_memalign(&pNewShellCode,pagesize, pagesize);
或者:
mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_ANONYMOUS | MAP_PRIVATE, 0, 0);
申请一整页的内存,但是这样肯定是浪费的,所以之后应该在这个内存上填充,占满之后再申请新的一页内存。   3、修改内存权限,ChangePageProperty中如果超过一页的内存,第二页和之后的都没有设置权限,属于代码逻辑bug,但是没有超过2页的情况,所以不会触发。   4、arm刷新缓存错误/未生效,arm64未刷新缓存。

//通过测试:cacheflush无法刷新缓存,不过不确定是不是第三个参数的问题,ICACHE, DCACHE, or BCACHE,抽时间再看吧,//不过gtoad的把地址转成无符号指针再取值肯定是不正确的。// cacheflush(((uint32_t)(info->pBeHookAddr)), info->backUpLength, 0);//测试也无法刷新缓存// cacheflush(PAGE_START((uintptr_t)info->pBeHookAddr), PAGE_SIZE, 0);//测试也无法刷新缓存// cacheflush(info->pBeHookAddr, info->backUpLength, 0);//测试也无法刷新缓存

//而确实有效的刷新缓存的是:可以直接传地址和结束地址,也可以刷新一页,效果来看都成功了。

// if(TEST_BIT0((uint32_t)info->pBeHookAddr)){// builtin_clear_cache((char)info->pBeHookAddr - 1, (char)info->pBeHookAddr - 1 + info->backUpLength);// } else {// builtin_clear_cache((char ) info->pBeHookAddr,// (char ) info->pBeHookAddr + info->backUpLength);// }// builtin_clear_cache(PAGE_START((uintptr_t)info->pBeHookAddr), PAGE_END((uintptr_t)info->pBeHookAddr + info->backUpLength));

//后记: //测试cacheflush可以,但是和int cacheflush(long addr, long nbytes, long __cache);不一致的是,从命名来看第二个参数应该是大小,不应该是 //结束地址,而且linux man中Some or all of the address range addr to (addr+nbytes-1) is not accessible。哎奇怪,抽时间再逆向看看吧。

/if(TEST_BIT0((uint32_t)info->pBeHookAddr)){
cacheflush((char)info->pBeHookAddr - 1, (char)info->pBeHookAddr - 1 + info->backUpLength, 0);
} else {
cacheflush((char ) info->pBeHookAddr,
(char ) info->pBeHookAddr + info->backUpLength, 0);
}/

//目前来看使用builtin_clear_cache吧,应该是都实现了且确实可用,如果某些系统不生效在抠出系统调用吧,先使用函数吧。 写在后面   为了避免不必要的纠纷,前面列举的框架只是为了引出为什么在写一个的原因,没有其他的意思。只是想写一个让大多数人更容易使用、了解实现细节、能更容易参与开发完善的Android inline hook项目。所以暂时不考虑代码的艺术,而尽量的写成易懂的代码,且代码写了较多、完整的注释。   也是希望不止是为了使用,而能让使用者能自己了解原理,自己就能解决错误,这方面感谢下Android_Inline_Hook。之前也写过一些自己个人用的临时hook,但是时间久了代码都找不到了,且也都是最简单的实现,临时用下的,所以也打算重新实现下,没时间一直拖着,看到Android_Inline_Hook,发现很多思路和我之前是一样的,代码也易懂,看一下就全明白了,所以一开始在Android_Inline_Hook上面修了一些bug凑合用,不过后来修的多了发现不如重新实现,因为很多地方不兼容、架构有冲突了。   整理下,最近开源了,希望各位大佬多参与,完善修复bug,多谢! https://github.com/zhuotong/Android_InlineHook 00410bfbee0117a49816478f30227a86.gif 3b6c4d85d556532ac37a4c5ff5eb8ea3.gif - End - 37a4e7f797ef62bc01a2e343882f441f.png

看雪ID:卓桐

https://bbs.pediy.com/user-670707.htm 

*本文由看雪论坛  卓桐  原创,转载请注明来自看雪社区

推荐文章++++

ff0189d6ce5d96d24c3144f58a60e991.png

* 新手熊猫烧香学习笔记

* GandCrab v5.2 分析

* 定制Xposed框架

* Android 4.4 从url.openConnection到DNS解析

* 通过一道pwn题详细分析retdlresolve技术

好书推荐515833bcb59551cef8eae8070a1e45e4.png

﹀ ﹀ ﹀ 6ba43924db30633aa28ea88d4dc1af1e.png 公众号ID:ikanxue 官方微博:看雪安全 商务合作:wsc@kanxue.com 9cef765327fe7d00f47a00984e7263cc.gif
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值