首页
社区
课程
招聘
[原创]基于Xposed&Edxp实现HookLinker实现的So脱壳机
发表于: 2022-4-23 14:47 15301

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

2022-4-23 14:47
15301

现在市面上很多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
,所以不需要考虑这些问题。

分为三种情况:
讲这个问题之前,我们需要先了解edxp和xposed的classloader的加载区别。
正常xposed ,使用的是PathClassLoader,父类是系统的classloader也就是可以直接使用系统的classloader就可以得到加载模块的这个PathClassLoader。

而在edxp里面使用的classloader是InMemoryDexClassLoader
这个类是8.0以上独有的。

回到下面的问题:
因为我们在so在里面需要对一个native方法进行动态进行注册,所以第一步需要先找到模块加载的Class。所以选择不同的Classloader也会有不同的结果。

如果在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段的修复,我也懒得加以后用到再说吧)修复逻辑如下,分别对下面节点进行修复。
图片描述

修复完毕保存&输出即可。

这种So是不会走linker的所以,我们在dump的时候Hooklinker无效
但是dump也很简单,先cat maps 查看到So的开始地址和结束地址。
直接导出内存即可。在配合win版本的sofix修复即可。
图片描述

编译好的FunELF和win工具在下方。

 
 
 
 
 
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;
     *        }
     *    .....
     * )
private static void loadModule(String apk) {
     *         log("Loading modules from " + apk);
     *
     *         if (!new File(apk).exists()) {
     *             log("  File does not exist");
     *             return;
     *                }

[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

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

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

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