一.思路整理 二.某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模拟执行
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!
最后于 2021-12-16 18:02
被爱吃菠菜编辑
,原因: