首页
社区
课程
招聘
[原创] JDex:基于Xposed / Lsposed的主动调用抽取壳脱壳工具
发表于: 4天前 1789

[原创] JDex:基于Xposed / Lsposed的主动调用抽取壳脱壳工具

4天前
1789

本工具仅用于安全研究和学习目的,请勿用于非法用途

得益于Frida优秀的Java以及Native层Hook功能,现在基于Frida的脱壳脚本层出不穷,但是对Frida的检测也日益加强,现在都不必说企业壳,免费壳都配备了不少Frida检测。而Fart类基于修改Android源码进行主动调用脱壳的方案虽然非常完美,但是编译,刷机等操作却会对于刷机小白来说门槛较高,而且对源码的修改考虑不周就可能造成无法预知的错误,而且比较难Debug(而且因为需要Linux进行编译,对电脑配置不高使用虚拟机的人来说也会不大友好)

这时Lsposed相对隐蔽的Hook手段和较低的开发成本就会让脱壳这门技术和过检测被划分开来,入门和实操难度也会低很多(但是也有部分厂商是上了比较强的针对lsposed的检测手段的,所以只能应对大多数企业抽取壳情况,而不是全部)

Github仓库地址写在帖子末尾,本项目依赖于Xposed/Lsposed主动调用构造方法进行抽取壳的脱壳,但是并不是非常完善,很多功能还有待完善和优化。如果你对本项目感兴趣,非常欢迎参与讨论、魔改、二次开发、提交 Issue 或分享你的思路

本项目的应用场景:

应对多数未对Lsposed进行有效检测的企业抽取壳和免费壳的Dex加固

工作流程:

首先在Android上,Dex的动态加载大部分都是依赖于ClassLoader的,最常见的是通过DexClassLoaderPathClassLoaderInMemoryClassLoader进行加载。大多数的壳无非就是将Dex进行不同粒度的加密,但是实际交给ART虚拟机解释执行的时候都要进行动态解密。这就是我们脱壳的核心原理,在Dex解密的时候将其从内存中Dump下来。

以下所有源码部分演示均以android-9.0.0_r61作为演示

对于每一个App,都有一个非常核心的类:LoadedApk

它被用于描述该应用在当前进程中的所有运行时信息,其中就有我们非常感兴趣的APP的真实ClassLoader,并且还为我们提供了getClassLoader方法,返回当前LoadedApk中的mClassLoader字段

至于为什么大多数APPLoadedApk中的ClassLoader就是真实的ClassLoader:

系统在启动 Activity 时,不会使用你自定义的 ClassLoader,而是使用 LoadedApk 中的 mClassLoader。你的 DexClassLoader 加载的类对系统框架层来说是"不可见"的,当App启动自定义的Activity,Service时就会找不到这些类。所以常见的壳会选择将解密后dex的ClassLoader插入到LoadedApkmClassLoader中,或者在双亲委派链中加入自定义的ClassLoader。而几乎所有主流加固方案都采用替换 mClassLoader 的方式

因为壳需要解密 DEX 并创建全新的 ClassLoader,这与"替换"方案天然契合。而"插入委派链"方案无法解决"原始 ClassLoader 不认识解密后 DEX"这一根本问题,且控制力,兼容性和安全性都不如直接替换

获取LoadedApk的方案有很多种,我们这里选择这条反射链获取ClassLoader,因为偶然间在Android源码中看到了这种获取LoadedApk的方式,所以可能会更稳定一些

因为Xposed对于反射获取字段的封装非常友好,所以我们不需要自己写大量反射,只需要借助Xposed的API:

当然其实loadPackageParam.classLoader也是从LoadedApk中拿ClassLoader,虽然通过两种方式都可以获取,但是这里为了考虑到LoadedApk的ClassLoader可能被替换,而xposed获取的时机不一定是我们想要的时机,所以反射调用获取会更保险

最初想过做一个Frida那样可以枚举ClassLoader的功能:

从当前虚拟机的 Runtime 中获取唯一的 ClassLinker*,在一个 ART 线程中、并在线程被 JVM 暂停的状态下主动调用 ClassLinker::VisitClassLoaders,传入一个始终返回 true 的 visitor 回调。 该 visitor 是 Frida 构造的 native 回调函数指针,其签名为 bool visitor(mirror::ClassLoader* loader),(不包含 this 指针)。 ART 在遍历每一个已注册的 mirror::ClassLoader* 时,主动调用该 visitor,并将当前遍历到的 ClassLoader 作为参数传入。 Frida 在回调中将这些 loader 提升为 JNI 全局引用并保存,遍历结束后在 Java 世界中将它们包装为 java.lang.ClassLoader 对象数组返回

但是Xposed实现该功能时间成本较高,而且很多壳并不需要这种方式去脱壳,LoadedApk已经够用,所以这里就暂时不做实现了

首先众所周知的三大ClassLoader就是

而它们都继承自BaseDexClassLoader,没有使用更上层的ClassLoader是因为在BaseDexClassLoader有很方便的DexPathList用于获取DexFile

其中的dexElements属于Element这个内部类,其中我们就可以遍历所有dexElements的拿到该ClassLoader对应的所有DexFile

可是我们都知道ART虚拟机中的DexFile和我们真正需要的Dex相差甚远,而从Android8系列开始Android源码中去除了DexFile类中的getBytes方法,想拿到我们需要的Dex最好的方法就是拿到Native层的Art::DexFile。而恰好DexFile中有两个比较有趣的字段,是mCookiemInternalCookie,这两个字段虽然是Object类型,但是其实是long[],内部存储的数据就是指向Art::DexFile绝对地址的指针,所以我们就可以通过将其强转为long[]之后传到Native层去进行读写来Dump(强转之前先做验证)

我们就可以封装这样两个函数来从BaseDexClassLoader中Dump我们需要的Dex文件

64位下Art::DexFile的内存结构大致如下,我们可以使用偏移直接读,但是这里我选择使用dex\n头进行判断,避免Dump下来一大堆无效数据,当然有需求的话可以修改代码重新编译即可,直接通过对应偏移读取

因为我们Native层的读取方式相对Android对开发者的期望来说,可以说是另辟蹊径,所以自然会导致很多问题,其中最重要的问题就是读取到不可读的地址,因为有些地址可能已经被释放了,但是还是被mCookie持有,因为Android本来就没指望有人这样去读取,所以在对一些APP的测试中直接读取就会导致崩溃

所以我们需要在读取前借助pipe进行内核层读写,如果地址不可读,内核会返回 -EFAULT,不会让我们想要脱壳的进程崩溃

此时在Xposed成功注入到对应APP进程后新建一个Thread,sleep一会之后进行Dump就是最初的JDex1了,这也是为什么本项目叫JDex2的原因,因为之前的一版脱壳功能实在不尽人意,对于不少大家可能感兴趣的APP来说,只能脱下来已经触发逻辑的那一些类。我一般分析还是比较喜欢从字符串入手的,这样的话对和我一样的字符串党非常不友好。所以就需要上主动调用之路了

此小节为未实现的探索方案,仅供参考

其实最初的设计是借助一些inlineHook框架对ArtMethod::Invoke解释执行链路上的EnterInterpreterFromEntryPoint函数进行Hook,通过第三个参数ShadowFrame去获取ArtMethoddex_pc_ptr_,然后return 0中断执行流。

该函数在Native层的实现如下

ShadowFrame的内存结构如下

如果需要dump,就需要在之前通过mCookie去DumpDex的时候将所有Dump下来的Dex的begin和size都存起来,在这里通过dex_pc_ptr_指向的地址范围来快速判断方法位于哪个Dex。

这样或许可以避免像frida-fart那样借助自实现的GetObsoleteDexCache去确认方法属于哪个DexFile,同时也能通过dex_pc_ptr_ - begin快速定位offset回填。但是实测了一些inline hook框架,即使hook后不进行任何操作直接调用原函数,也不进行主动调用,也会在Hook之后一段时间之后崩溃,可能是调用约定或是其它原因,所以最后没有实现

此设想因为本项目最终并没有实现,所以也就不做赘述了,大家感兴趣的话可以详细了解该部分Android源码

DexFile.java中有这样一个函数,可以帮我们通过cookie获取该DexFile的所有类,从而让我们可以快速得到一个String[],进而进行构造方法的批量调用。这个cookie参数或许也可以引发大家的思考,进而发现cookie和真实Art::DexFile的关系

因为我们在实现Native层的Hook的时候遇到了比较难解决的问题,所以我们主动调用所有方法并且进行方法粒度dump的想法就化为了泡影 QwQ ,那我们只能在Java层通过对原App尽量少的操作进行脱壳。前面提到很多抽取壳都是基于级别进行抽取的,只要触发类中的任意一个方法就可以了

对原App破坏最小的方法调用就是构造方法了,因为静态方法并不是每个类都有,使用静态方法进行脱壳的话就会很被动,而且调用静态方法由于不可预知的效果,对类的影响扩散可能会直接导致App崩溃,并且仅通过loadClass是无法让大部分抽取壳回填的(父类方法是肯定不行的,不然调用一个Object的方法所有类都解密了还加什么壳)

最初尝试单纯调用构造方法new实例,但是错误的参数可能会导致大量的异常甚至崩溃,并且Activity等系统类实例化是需要Looper和各种各样符合要求的参数的,而我们批量主动调用要获取这些乱七八糟的参数是非常费力的。所以我们可以添加一个Hook,来让方法不实际执行,避免导致的各种不可控的崩溃。

然后在实测中被某些壳检测到了,导致app崩溃了......

但是之前对该壳的免费版有一些了解,该壳会通过检测方法是否被非法转为Native方法从而判断是否被Hook,所以我们需要在newInstance之后解除Hook,以规避这种检测

在我们反射获取的构造方法中,Android为我们提供了一个非隐藏的方法getParameterTypes供我们调用

所以我们可以将它的返回值传递进来进行参数的构造,尽量减少错误的发生(避免在Hook执行执行前遇到的参数检查导致直接异常)

而在主动调用的时候我们要注意接口,注解类,抽象类,枚举类是没有构造函数的,并且本来也没有CodeItem,所以我们直接跳过这些方法避免崩溃

到这里基本上反射脱壳的方案就结束了,只需要对其主动调用后触发回填,然后DumpDex即可了

为什么还要设置这样一个分支呢,因为之前在一些App中碰到了自身一些在Java或Native层进行动态加载的情况,因为并不像整体加壳那样想要加载Activity,Service等组件,所以不会在LoadedApk字段中。我们就需要通过Hook去获取ClassLoader

该方法的弊端也很明显,因为xposed/lsposed系列实现 Java Hook 的原理就是将方法转为Native方法后指向自己的处理函数去处理,所以Hook了不解除就很容易被检测出来,所以该分支一般情况下不推荐使用

这里我们选择Hook的是BaseDexClassLoader的构造方法和这些方法

因为很多壳为了防止Hook ClassLoader导致壳被直接脱下来,以及兼容性问题,所以会选择使用这些方法向DexPathList 中的 dexElements 数组中直接插入DexFile,这样就会避免调用三大ClassLoader的构造方法。

这两个方法在Android源码中的定义如下

此时的thisObjectDexPathList实例,通过 definingContext字段拿到对应的 ClassLoader,然后仿照之前的反射获取dex即可,这里就不做赘述了

这种情况下为了脱壳完整性也加了反射dumpdex

由于配置文件存在本地,需要读写存储权限

MainActivity中有如下配置,会根据输入将配置写到/sdcard/config.properties路径下,也可以手动编辑,除了目标包名,其它配置多个路径可以通过,间隔

白名单和黑名单只是不做主动调用,但是仍然会进行dump

图片描述

如果使用的是Lsposed,记得在Lsposed中勾选对应APP

脱壳效果展示: 图片描述

只能对抗类级别的方法抽取,而基于方法粒度的抽取则会无法进行,并且无法应对方法执行结束后重新抽取的情况,还有真正开始执行字节码才动态解密的情况


[培训]Windows内核深度攻防:从Hook技术到Rootkit实战!

最后于 1天前 被JSnow编辑 ,原因:
收藏
免费 100
支持
分享
最新回复 (64)
雪    币: 3471
活跃值: (4504)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
学习一下
4天前
0
雪    币: 0
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
3
666
4天前
0
雪    币: 697
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
4
tql
4天前
0
雪    币: 6
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
5
学习一下
4天前
0
雪    币: 268
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
6
感谢分享,学习一下。
4天前
0
雪    币: 106
活跃值: (230)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7
非常感谢
4天前
0
雪    币: 5
活跃值: (1105)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
8
非常感谢
3天前
0
雪    币: 51
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
9
tql
3天前
0
雪    币: 7
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
10
感谢分享
3天前
0
雪    币: 1855
活跃值: (1865)
能力值: ( LV4,RANK:40 )
在线值:
发帖
回帖
粉丝
11
感谢
3天前
0
雪    币: 1867
活跃值: (4638)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
12
666
3天前
1
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
13
666
3天前
0
雪    币: 4028
活跃值: (6472)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
14
感谢分享
3天前
0
雪    币: 378
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
15
666
3天前
0
雪    币: 220
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
16
大佬太强了
3天前
0
雪    币: 1247
活跃值: (658)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
17
66
3天前
0
雪    币: 200
活跃值: (2698)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
18
666
3天前
0
雪    币: 1550
活跃值: (4463)
能力值: ( LV4,RANK:40 )
在线值:
发帖
回帖
粉丝
19
666
3天前
0
雪    币: 8627
活跃值: (5313)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
20
看看
3天前
0
雪    币: 199
活跃值: (676)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
21
666
3天前
0
雪    币: 3779
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
22
666
3天前
0
雪    币: 155
活跃值: (4426)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
23
学习一下 !!!
3天前
0
雪    币: 0
活跃值: (114)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
24
 感谢分享  !! 
3天前
0
雪    币: 200
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
25
感谢分享
3天前
0
游客
登录 | 注册 方可回帖
返回