前言:
现在市面上很多SO都是基于动态解密或者自定义Linker把So加载到内存里,本身的Lib目录下面根本没有需要初始化的So,但是不管如何,只要在加载到内存里就一定会在Maps里面有所体现。不管是动态解密的So,还是自定义Linker的方式,我们只需要读取Maps把So这段内存的开始地址和结束地址的内存dump出来,然后配合修复工具就可以进行快速的修复和分析。保存的方式也很方便,IDA或者GG修改器都可以快速的进行内存的dump。那么有没有一种可以自动化dump和修复的办法呢?
动态加载:
通过Hook linker方式自动化dump ,并且使用工具完成修复。
我是用xposed在app未启动之前将我们的SO进行注入。(这个时候So已经加载并且初始化完毕,有内存解密的So也会在这个时候完成解密)我们在通过Hook linker的方式得到加载的时机点,再通过读取Maps获取So的开始地址和结束地址。
使用F8的sofix对保存下来的文件进行修复,修复完毕输出到fix目录。执行完毕。
初始化&注入
注入的时间点一定要早,防止很多So是静态代码块初始化的,这个时候只要这个Application初始化了该So就会加载到内存里。也就是说我们需要在Application初始化之前进行注入即可。我直接在XPosed里面的handleLoadPackage进行初始化。
先尝试内存漫游的方式获取context,如果没找到则通过静态的方式创建Context。如果两者都失败了以后Hook createAppContext 方法,拿到返回值,也就是我们需要的Context
因为注入的时候需要获取模块的base.apk作为路径,对base.apk进行解压,得到注入的So,根据路径的内容进行判断目标app是64还是32位。如果对方App存在64位So则默认是64位。我们注入的So 也必须是64位。
这块注入的时候有个细节点问题,classloader的问题。
虚拟机里面确认一个Class或者Classloader
需要判断这个Class的签名和Classloader完全匹配的时候才认为是一个Class。不同Classloader在堆里面被划分成不同的作用域。
在调用nativeLoad进行So加载的时候的时候需要传入一个Object,这个Object是一个Classsloader,因为So和class一样也是有classloader限制。
你So的Classloader和class的classloader一样,才可以在So里面Findclass到这个Class,你才可以对一个native方法进行注册。
否则你Findclass是找不到这个Class的。平时我们开发的时候,一般我们默认system.loadlib用的都是当前进程的classloader
,所以不需要考虑这些问题。
如何选择classloader?
分为三种情况:
讲这个问题之前,我们需要先了解edxp和xposed的classloader的加载区别。
正常xposed ,使用的是PathClassLoader,父类是系统的classloader也就是可以直接使用系统的classloader就可以得到加载模块的这个PathClassLoader。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | private static void loadModule(String apk) {
* log( "Loading modules from " + apk);
*
* if (!new File (apk).exists()) {
* log( " File does not exist" );
* return ;
* }
* / / 加载Xposed模块的 Classloader
* ClassLoader mcl = new PathClassLoader(apk, BOOTCLASSLOADER);
*
* InputStream is = mcl.getResourceAsStream( "assets/xposed_init" );
* if ( is = = null) {
* log( "assets/xposed_init not found in the APK" );
* return ;
* }
* .....
* )
|
而在edxp里面使用的classloader是InMemoryDexClassLoader
这个类是8.0以上独有的。
1 2 3 4 5 6 7 8 9 10 11 | / / load dex
jobject bufferDex = env - >NewDirectByteBuffer(reinterpret_cast<void * >(dex.data()),
dex.size());
jclass in_memory_classloader = JNI_FindClass(env, "dalvik/system/InMemoryDexClassLoader" );
jmethodID initMid = JNI_GetMethodID(env, in_memory_classloader, "<init>" ,
"(Ljava/nio/ByteBuffer;Ljava/lang/ClassLoader;)V" );
jobject my_cl = JNI_NewObject(env, in_memory_classloader,
initMid,
bufferDex,
sys_classloader);
|
回到下面的问题:
因为我们在so在里面需要对一个native方法进行动态进行注册,所以第一步需要先找到模块加载的Class。所以选择不同的Classloader也会有不同的结果。
- 使用当前进程的classloader (Context.getclassloader)
如果在native去find我们模块的class的时候,会直接提示class nout found。因为当前进程的classloader里面是没有xposed模块的class的。
注册完毕以后开始Linker的Hook
Hooklinker也很简单,遍历linker elf 在内存中的符号
尝试获取dlopen的地址,这个时候需要注意,linker so 这些函数是非导出表,所以需要解析elf的方式进行hook。根据符号获取到函数的地址。
我用的是sandhook里面的elf_utils
Hook成功以后处理linker的回调,需要过滤掉系统的So
所以需要加路径包名判断。只有加载的So包含选择的app的时候才进行dump和修复
通过读取maps把内存端保存到FunELF/temp下。
将修复的So保存到FunELF下,保存的时候别忘了通过mprotect修改so的可读写权限,有些So在保存的时候也需要RWX权限的
修复工具我用的是f8的sofix,因为代码是开源的,之前他是在win上面写的工具
被我改改移植到安卓上面了,项目地址如下。https://github.com/F8LEFT/SoFixer
(他这个got表和init表没有进行base的相减,貌似只做了text段的修复,我也懒得加以后用到再说吧)修复逻辑如下,分别对下面节点进行修复。
修复完毕保存&输出即可。
自定义Linker加载:
这种So是不会走linker的所以,我们在dump的时候Hooklinker无效
但是dump也很简单,先cat maps 查看到So的开始地址和结束地址。
直接导出内存即可。在配合win版本的sofix修复即可。
编译好的FunELF和win工具在下方。
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课