OneHook

目前比较流行的几个安卓HOOK方案,都有功能上的欠缺,有的不支持art模式,有的不支持jni层,有的不支持侵入HOOK。

所以OneHook诞生了!

这是一个同时支持ART和Dalvik两种模式,理论上支持安卓4.0.3以上所有版本,同时支持JAVA和NATIVE层,使用全局注入技术的侵入式HOOK框架。

本框架不需要额外的安装,可以静态编译到自己的APP中。

首先感谢看雪论坛中很多大牛提供的技术资料,没有你们的分享,就没有目前的这个OneHook的诞生。

感谢ele7enxxh提供的Android Inline Hook模块

项目地址:https://github.com/ele7enxxh/Android-Inline-Hook

项目地址:https://github.com/aslody/legend

还感谢其他贡献知识的同学们。

功能实现:

0x01

首先考虑如何进入系统,如何在合适的位置和时间启动我的模块。之前做WIN32的时候就有人说过,如果在WINDOWS启动前前运行我的代码,那么我无所不能。理论上,这句话是成立的。

所以在安卓系统,我也需要找一个相对足够早的位置来启动我的模块,这个位置我没有选择init进程,而是libc.so。

为什么?

因为安卓的底层是LINUX构建的,LINUX的底层就是C语言,而libc.so就是C语言的具体实现库,在这个位置上插入代码显然是合适的。而且在libc.so中去执行我们的代码,就不需要去考虑如何注入模块,因为安卓的所有进程都会主动加载libc.so。这是一个一举两得的方案,首先解决了启动代码,其次解决了注入问题。

原理/过程:

so文件的初始化工作 是在init_array段中定义的


.init_array段的起始位置为:0X000683C0


android hook技术 源码 android jni hook_android hook技术 源码

初始化时 会按照顺序去执行初始化函数

_ZL14....

_ZL30....

_ZL29....

_ZL31....

那么如果把_ZL14__libc_preinitv的指针重定向到一个物理地址,使之dlopen我们自己的so,就可以实现我们想要的功能。


android hook技术 源码 android jni hook_AndFix_02

修改init_array的段的第一个指针 _ZL14__libc_preinitv ---> 0x55D24


指向位置的机器码

机器码对应的C语言代码

1



2



3



4



5


int  _fun()



{



dlopen(( const  char  *) "libckis.so" , 0);



return  __libc_preinit();



}


这样就可以顺利的在每个进程中加载libckis.so文件

关于如何修改libc.so文件使之加载自定义库文件的方法 可以参考我的另外一篇文章

http://bbs.pediy.com/thread-213043.htm

需要注意的是

1 在不同安卓版本中,libc.so是不一样的。

2 自定义的代码位置不一定必须在.text段中,在很多情况下.text段没有足够的空间让你去插入代码,我的解决方案是把这段代码放在.rodata段,如果放在rodata段,在运算地址偏移时,需要+0x10000

3 libc.so修改后需要妥善的push到手机的/system/lib目录下,要注意权限问题,否则手机会死机



0x02

现在我们已经进入了安卓的Native层,在这里,我们可以做很多事情,比如HOOK fopen来控制底层文件的访问 也可以HOOK __system_property_get来修改ro属性 等等....

https://github.com/ele7enxxh/Android-Inline-Hook

附一个简单的例子

1



2



3



4



5



6



7



8



9



10



11



12



13



14



15



16



17



18



19



20



21


#include <stdio.h>      
       #include <stdint.h>      
       #include <dlfcn.h>      
       #include "inlineHook.h"      
       void        my_init(       void       ) __attribute__((constructor));      
       typedef        int        (*t_system_property_get)(       const        char        *name,        char        *value);      
       t_system_property_get __system_property_get=NULL;      
       int        (*old__system_property_get)(       const        char        *name,        char        *value)=NULL;      
       int        new__system_property_get(       const        char        *name,        char        *value)      
       {      
       if       (       strstr       (name,       "ro.serialno"       ))        return        strlen       (       strcpy       (value,       "AABBCC"       ));      
              else        return        old__system_property_get(name,value);      
       }      
       int        my_init(       void       )      
       {      
       void       * libc=dlopen(       "/system/lib/libc.so"       ,0);      
       __system_property_get=(t_system_property_get)dlsym(libc,       "__system_property_get"       );      
       if       (registerInlineHook((uint32_t)__system_property_get,(uint32_t) new__system_property_get,(uint32_t **) &old__system_property_get)!= ELE7EN_OK)        printf       (       "error find __system_property_get "       );      
       else        if        (inlineHook((uint32_t) __system_property_get) != ELE7EN_OK)        printf       (       "error hook __system_property_get "       );      
       return        0;      
       }


那么,我们现在已经可以完整的实现Native/JNI层的HOOK了。

在adb shell下 输入getprop ro.serialno 会得到返回值 AABBCC 如图:


android hook技术 源码 android jni hook_ART_03

并且我们已经完成了了进入JAVA层的先决条件。

0x03

进入JAVA世界

论坛中如何从native层进入java层有很多种方案吗,在这里 我使用了ADBI的解决方案,在libc.so库中的epoll_wait函数挂钩,进而跳转到APP进程中去,然后使用DexClassLoader加载自己的dex文件,达到JAVA HOOK的目的

1



2



3



4



5



6



7



8



9



10



11



12



13



14



15



16



17



18


bool        IsInToJava=       false       ;      
       int        (*oldepoll_wait)(       int        epfd,        int        events,        int        maxevents,        int        timeout)=NULL;      
       int        newepoll_wait(       int        epfd,        int        events,        int        maxevents,        int        timeout)      
       {      
       if       (!IsInToJava)      
       {      
       IsInToJava=       true       ;      
       invoke_dex_method(       "/data/local/tmp/xxx.dex"       ,path,       "com.xxx.xxx.inject"       ,       "main"       ,AppName);      
       return        oldepoll_wait(epfd,events,maxevents,timeout);      
       }      
       }      
       void        my_init(       void       )      
       {      
       void       * libc = dlopen(       "/system/lib/libc.so"       , 0);      
       void        *hookepoll_wait = dlsym(libc,        "epoll_wait"       );      
       if        (registerInlineHook((uint32_t)hookepoll_wait, (uint32_t)newepoll_wait, (uint32_t **)&oldepoll_wait) != ELE7EN_OK)        printf       (       "error find epoll_wait\n"       );      
       else        if        (inlineHook((uint32_t)hookepoll_wait) != ELE7EN_OK)        printf       (       "error hook epoll_wait "       );      
       }


在APP进程调用epoll_wait函数时 就会加载指定的dex文件,并且执行HOOK的初始化方法。

注意epoll_wait可能会被多次调用,我们只需要执行一次即可。

0x04

JAVA层HOOK

JAVA层的HOOK我使用了Legend的方案,因为已经进入了APP进程内,就和热补丁的流程是一样的。

1



2



3



4



5



6



7



8



9



10



11



12



13



14



15



16


public        class        inject{      
              public        static        final        String TAG =        "CKIS_INJECT"       ;          
              public        static        Context context;          
              public        static        void        main(String packageName)      
              {      
              Log.e(TAG,        "Dex Inject in by: "       +packageName);              
              HookManager.getDefault().applyHooks(inject.       class       );              
              Log.e(TAG,        "DEX called success:"       +packageName);          
              }      
              
              @Hook       (       "android.telephony.TelephonyManager::getDeviceId"       )      
              public        static        String TelephonyManager_getDeviceId(TelephonyManager thiz)       
              {      
              return        "123456789012345"       ;          
              }      
       }

在这里 我们HOOK了TelephonyManager.getDeviceId方法,实现了系统的IMEI的修改。

在执行IMEI获取操作时 调用getDeviceID会获得123456789012345


android hook技术 源码 android jni hook_ART_04

具体HOOK方案 也可以参考我另外一篇基于XPOSED框架的文章

http://bbs.pediy.com/thread-213042.htm

0x05

环境部署

本框架需要ROOT权限

1 首先读取/system/lib/libs.so 并在本地解析elf文件格式 添加加载自定义库的代码 然后再回写到/system/lib/libc.so目录下

2 释放DEXPOSED的运行库 libdexposed.so libdexposed_l.so libdexposed_l51.so  到\system\lib\或\vendor\lib目录下

3 释放libckis.so、libjava.so到/system/lib目录下

4 释放DEX文件到指定的目录下 本例为 /data/local/tmp/xxx.dex

5 重启生效

目前该框架的完成度已经接近90%,支持ART、DALVIK,支持JAVA、JNI/NATIVE 

理论上此方案支持安卓4.0.3以上所有版本 但是Legend的HOOK模块支持

  •  Dalvik & Android 4.2
  •  Dalvik & Android 4.3
  •  Art & Android 5.0
  •  Art & Android 5.0.1
  •  Art & Android 5.1
  •  Art & Android 6.0
  •  Art & Android 6.0.1

所以考虑后期JAVA层可能会更换为可以支持7.0的AndFix


目前已测试的版本有

4.0.3

4.4.2

4.4.4

5.0

5.1