首页
论坛
课程
招聘
[原创]基于Xposed&Edxp实现HookLinker实现的So脱壳机
2022-4-23 14:47 6681

[原创]基于Xposed&Edxp实现HookLinker实现的So脱壳机

2022-4-23 14:47
6681

前言:

现在市面上很多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的。

  • 使用系统Classloader
    只能在只能在正常xposed的环境可以初始化成功,如果切换到edxp会失败。
    因为edxp的InMemoryDexClassLoader不是系统的classloader,这显然是不允许的。

  • 使用模块本身的Classloader(最优解)
    直接用xposed模块初始化的类.class.getclassloader即可。
    代码如下
    图片描述

注册完毕以后开始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工具在下方。


恭喜ID[飞翔的猫咪]获看雪安卓应用安全能力认证高级安全工程师!!

上传的附件:
收藏
点赞6
打赏
分享
打赏 + 10.00雪花
打赏次数 1 雪花 + 10.00
 
赞赏  小黄鸭爱学习   +10.00 2022/04/23 默认给出好评
最新回复 (14)
雪    币: 527
活跃值: 活跃值 (2348)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
小黄鸭爱学习 活跃值 2022-4-23 14:52
2
0

如果有源码就好了 光看文章想写自己的脱壳机有点勉强

最后于 2022-4-23 14:52 被小黄鸭爱学习编辑 ,原因:
雪    币: 2408
活跃值: 活跃值 (7268)
能力值: ( LV8,RANK:130 )
在线值:
发帖
回帖
粉丝
珍惜Any 活跃值 1 2022-4-23 14:53
3
0
小黄鸭爱学习 如果有源码就好了&nbsp;光看文章想写自己的脱壳机有点勉强
微信私聊我,发你就行。
雪    币: 441
活跃值: 活跃值 (1084)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
王麻子本人 活跃值 2022-4-23 15:15
4
0
我也想私聊你
雪    币: 2408
活跃值: 活跃值 (7268)
能力值: ( LV8,RANK:130 )
在线值:
发帖
回帖
粉丝
珍惜Any 活跃值 1 2022-4-23 16:40
5
0
王麻子本人 我也想私聊你
v296488320
雪    币: 1569
活跃值: 活跃值 (3674)
能力值: ( LV8,RANK:146 )
在线值:
发帖
回帖
粉丝
Simp1er 活跃值 2022-4-23 20:43
6
0
不愧珍惜大佬,nnnb
雪    币: 0
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
x1c20000s 活跃值 2022-4-24 10:13
7
0
+1
雪    币: 10010
活跃值: 活跃值 (4629)
能力值: ( LV11,RANK:198 )
在线值:
发帖
回帖
粉丝
neilwu 活跃值 1 2022-4-24 10:57
8
0
你这个方案对于运行时解密执行的函数,在执行后再次加密回来还不支持吧  
雪    币: 1033
活跃值: 活跃值 (972)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
huangjw 活跃值 2022-4-24 11:13
9
0
你这个方案对于运行时解密执行的函数,在执行后再次加密回来还不支持吧 
雪    币: 2408
活跃值: 活跃值 (7268)
能力值: ( LV8,RANK:130 )
在线值:
发帖
回帖
粉丝
珍惜Any 活跃值 1 2022-4-24 13:40
10
0
neilwu 你这个方案对于运行时解密执行的函数,在执行后再次加密回来还不支持吧
不支持,这种需要自己分析了。不过这种蛮少的,有demo么?
雪    币: 2408
活跃值: 活跃值 (7268)
能力值: ( LV8,RANK:130 )
在线值:
发帖
回帖
粉丝
珍惜Any 活跃值 1 2022-4-24 15:06
11
0
huangjw 你这个方案对于运行时解密执行的函数,在执行后再次加密回来还不支持吧
有样本嘛。这样貌似效率很低
雪    币: 301
活跃值: 活跃值 (614)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
天边之云 活跃值 2022-4-25 17:49
12
0
珍惜Any 有样本嘛。这样貌似效率很低
小密盾啊
雪    币: 2408
活跃值: 活跃值 (7268)
能力值: ( LV8,RANK:130 )
在线值:
发帖
回帖
粉丝
珍惜Any 活跃值 1 2022-4-26 18:59
13
0
天边之云 小密盾啊
app叫啥名
雪    币: 22
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
万里星河 活跃值 2022-4-27 11:43
14
0
请问大佬 这样和使用frida根据so.base和so.size dump出来的有什么区别吗
雪    币: 2530
活跃值: 活跃值 (1168)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2beNo2 活跃值 2022-4-27 13:07
15
0
天边之云 小密盾啊
小密盾是分块解密的,在内存里面不是连续的
游客
登录 | 注册 方可回帖
返回