首页
社区
课程
招聘
[原创] 某DEX_VMP安全分析与还原
发表于: 2021-12-16 14:55 44470

[原创] 某DEX_VMP安全分析与还原

2021-12-16 14:55
44470

一.思路整理
二.某VMP入口特征
三.定位VMP字节码
四.分割VMP字节码
五.还原为SMALI
六.攻击面总结
七.深入VMP还原的一些问题
八.调试与工具总结

(1)定位VMP字节码
(2)分割VMP字节码
(3)还原成SMALI

因为如果目标方法的字节码地址,都找不到,还原也就没法展开了.

如果要反汇编成smali,
起码要知道这条smali对应的字节码一共几个字节.

在确定一条指令占几个字节后,
还要知道这几个字节中,
谁是操作码,谁是操作数.

有了前两步铺垫,最终我们可以解读一条完整的smali的含义.

跳板方法

进入native后的参数处理逻辑

为了处理不同类型的返回值, 定义了多个jni方法

对应jni函数入口指令情况

根据上述逻辑,则一定存在函数F,向F输入index可得到对应codeitem_addr
F(index) == codeitem_addr

我们看一下这个函数,从index到codeitem_addr的过程
(0x2dce->0xcac85880)

通过Trace记录REG信息,
用到了两个关键数值,0x2dce(index)与0xcac85880(codeitems),
标记两个数值出现的中间区间即可.

我们已经有了关键数据0x2dce,但还需要知道另一个提前条件,
即codeitem是0xcac85880,所以这个信息是从哪得知的?
这里是本章的关键.

(1) 已知明文
(2) 沙箱日志获取切入点
(3) JNI参数回溯
(4) 内存访问统计

目标APP内很多的onCreate()方法,其内部普遍调用了,
NBSTraceEngine.startTracing();以及super.onCreate()

我们选一个被vmp保护了的onCreate()作为分析目标, ZxWebViewActivity.onCreate()

① ZxWebViewActivity.onCreate内必定存在NBSTraceEngine.startTracing();以及super.onCreate()
② startTracing为静态方法,会被编译器编译为invoke-static
③ super.onCreate()为超类调用,会被编译器编译为invoke-super
④我们猜测vmp对invoke-static模拟实现借助了JNI函数,
所以我们触发ZxWebViewActivity.onCreate()执行,截取其调用序列,效果如下:

大致逻辑为

我们在trace中找到这条GetStaticMethodID()的出现位置,
然后作为起点向上展开回溯,希望找到其参数”startTracing”的最早出处,
如果有自动化的脚本和条件可进行污点分析,由于逻辑不是很复杂,这里人工回溯完成.

具体过程省略……
在trace中对参数”startTracing”来源进行一番回溯,
最终发现了一个起到决定性作用的偏移值0x000081de.
可以简单理解成,它以base+0x000081de的形式确立的参数”startTracing”.

结论:
如果0x000081de是那个起到决定性意义的数值,
那么毫无疑问0x000081de来自codeitem.

在trace中找到0x81de的出现位置,
发现它来自于内存位置0xcac858a8.

0x81de来自0xcac858a8,
由于这个地址可能是codeitem,
因此我们检索一下,trace中对这片内存区域的访问情况
0xcac858a8取前5个高位,忽略后3个地位,即检索对0xcac85???的访问

找到19条指令, 而对0xcac85???的访问,最早的第一条指令,出现在编号5691的位置,
对应的内存地址为0xcac85890,说明这里是ZxWebViewActivity.onCreate()第一条字节码.

由于codeitem第一条字节码之前0x10个字节还存在一些固定内容,
所以0xcac85890-0x10取得codeitem地址0xcac85880,
即codeitem的地址是0xcac85880

现在已经有了某厂vmp codeitems全部内容,
但是还没法反汇编成smali,

因为还不知道,
第一条指令一共占几个字节,
第二条指令一共占几个字节,
依次......

dalvik指令是不等长,
反汇编成smali的话,
起码要知道这条smali对应的字节码一共几个字节
在知道了每条指令占几个字节后,
还要知道这几个字节中,
谁是操作码,谁是操作数.

一般opcode后面会有一个EOR解密指令,
以及一串类似定位handle的CMP指令操作,
而operand没有,这就为区分opcode和operand提供了特征依据.

由eor指令向上回key出现的位置,
即可确定key的来源,
以及解密逻辑.

大致逻辑:
off1 = sub( codeitem当前指令地址, codeitem基址 )
off2 = lsl( off1, 1)
key = load( base + off2 )
de_opcode = xor(en_opcode, key)

1 标准dalvik指令反汇编过程
2 VMP指令反汇编过程
3 还原VMP所有指令需要什么?
4 没有opcode对照表时,如何展开还原?

由于使用了已知明文条件作为切入点,
已知分析目标ZxWebViewActivity.onCreate()中,
必定会调用startTracing()方法,
即必定存在invoke-static {v0}, method@00da6f // ...startTracing

又通过上面的分析得知关键值81de出现在这条invoke-static中,
且充当操作数的角色,那么按照我们按照标准invoke-static反汇编规则进行解析,
就可以得到结论.

.

VMP指令由标准指令基础上修改而来,有哪些异同?

(1)接口猜测法
(2)参数推导法
(3)标准dalvik指令格式的信息利用
(4)人肉逆向法(略)

method相关的invoke系列指令,可以通过JNI执行情况猜测.
Field相关的get set系列指令,也可以通过JNI执行情况猜测.

方法调用前,会先准备参数,
通常是声明类型的指令,
可以很大程度缩小猜测的候选指令范围.

由于vmp指令是由dalvik标准指令略微修改/变异而来,
只做了较小的改动,仍然保留了BIT位分布特征这样信息.
在做还原时,可以利用这些信息,一定程度缩小候选范围.
https://source.android.com/devices/tech/dalvik/instruction-formats
https://source.android.com/devices/tech/dalvik/dalvik-bytecode#instructions

1 分析路径
2 攻击面总结 && 启示

(1) 被VMP的方法内部存在已知明文指令.
(2) VMP的实现高度依赖JNI函数,通过HOOK拿到其调用信息,是非常有效的切入点与突破口.
(3) codeitems的连续性,集中存储的特性,通过内存访问统计最终被发现.
(4) vmp指令由标准dalvik指令基础上略改而来,整体仍然保留了很多可用信息,
对于一些内部逻辑比较简单的方法,可以以较小成本还原.

获取程序完整的执行&&数据信息 (trace).

1 GDB调试
2 FridaStalker编译执行
3 脱机unicorn模拟执行


[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)

最后于 2021-12-16 18:02 被爱吃菠菜编辑 ,原因:
收藏
免费 51
支持
分享
打赏 + 15.00雪花
打赏次数 3 雪花 + 15.00
 
赞赏  hackdaliu   +5.00 2022/07/05 支持大佬
赞赏  pareto   +5.00 2021/12/20 精品文章~
赞赏  orz1ruo   +5.00 2021/12/16 精品文章~
最新回复 (36)
雪    币: 3836
活跃值: (4142)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
坐等加精
2021-12-16 16:11
0
雪    币: 4164
活跃值: (3587)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
3
太吊了吧... 掉头发系列
2021-12-16 17:02
0
雪    币: 196
活跃值: (5976)
能力值: (RANK:10 )
在线值:
发帖
回帖
粉丝
4
棒棒的,已经将vmp课程的内容融会贯通,给你个大大的赞
2021-12-16 17:14
0
雪    币: 19956
活跃值: (4952)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
真大佬
2021-12-16 19:24
0
雪    币: 3818
活跃值: (4233)
能力值: ( LV9,RANK:180 )
在线值:
发帖
回帖
粉丝
6
大佬6,excel还能这么用,是我没想到的.
2021-12-16 19:45
0
雪    币: 4583
活跃值: (6836)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7
very goods
2021-12-16 20:23
0
雪    币: 145
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
8
牛逼了
2021-12-17 15:09
0
雪    币: 163
活跃值: (509)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
9
厉害了。libjni.so,Fix:Opcode、MethodID、StringID  xxx...... 还是头发要紧点
2021-12-17 16:25
0
雪    币: 5330
活跃值: (5479)
能力值: ( LV9,RANK:170 )
在线值:
发帖
回帖
粉丝
10
整理的太好了,给大佬点赞!
2021-12-17 17:36
0
雪    币: 3269
活跃值: (3079)
能力值: ( LV3,RANK:25 )
在线值:
发帖
回帖
粉丝
11

学习了,膜拜大佬Orz

最后于 2021-12-18 12:49 被xhyeax编辑 ,原因:
2021-12-17 21:11
0
雪    币: 498
活跃值: (4291)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
12
太肝了,小心头发阿老哥
2021-12-17 22:49
0
雪    币: 1623
活跃值: (1773)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
13
真心厉害  第一次看完了
2021-12-18 16:57
0
雪    币: 8447
活跃值: (5041)
能力值: ( LV4,RANK:45 )
在线值:
发帖
回帖
粉丝
14
mark
2021-12-18 21:02
0
雪    币: 6573
活跃值: (3933)
能力值: (RANK:200 )
在线值:
发帖
回帖
粉丝
15
2021-12-19 09:55
0
雪    币: 4087
活跃值: (2491)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
16
学习了,感谢大佬的教程
2021-12-20 00:02
0
雪    币: 623
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
17
 大开眼界
2021-12-20 00:32
0
雪    币: 4
活跃值: (347)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
18
分析得非常不错啊,不过往后面还原的路还挺长的。
另外如果 操作码 切换成双 byte,或者 每个包映射随机的情况下,感觉难度又是翻倍了。
同时 数据如果离散化存储 ,难度也会增加。LZ这成片的数据分析功底确实很强!!
2021-12-20 11:06
0
雪    币: 461
活跃值: (319)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
19
2021-12-20 11:24
0
雪    币: 463
活跃值: (2741)
能力值: ( LV4,RANK:40 )
在线值:
发帖
回帖
粉丝
20

1

最后于 2023-2-6 13:40 被pareto编辑 ,原因:
2021-12-20 16:52
0
雪    币: 783
活跃值: (1171)
能力值: ( LV5,RANK:78 )
在线值:
发帖
回帖
粉丝
21
看不懂但是感觉好流弊
2021-12-20 21:05
0
雪    币: 2731
活跃值: (1651)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
22
2021-12-24 15:16
1
雪    币: 116
活跃值: (1012)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
23
妙不可言
2021-12-27 14:37
0
雪    币: 198
活跃值: (548)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
24
实现原始APP进程环境 && 原始context中,
通过unicorn构造虚拟化CPU,
执行目标function,获得trace,
无已知检测和对抗手段,简单过anti.  这个是如何实现的 ?
2021-12-27 15:30
0
雪    币: 70
活跃值: (2097)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
25
Orz
2021-12-30 18:09
0
游客
登录 | 注册 方可回帖
返回
//