查看原文
其他

逆向学习sgavmp篇

angelToms 看雪学院 2021-03-08

本文为看雪论坛精华文章

看雪论坛作者ID:angelToms





觉着降生


也许在业界中 sgavmp 算是一个比较高的点了;它让很多人望而生畏,特别是对于我这种菜鸡来说,它简直是一个无法跨越的屏障。和猫一样人也对一些事物充满好奇,但往往催生你欲望的不是好奇而是外界事物。

前不久有一个阿里安全部的面试邀请,记得最后一面的时候面试官几次问到了我他们 avmp 的实现原理,在当时我几乎除了听过其名字外对此一无所知,这便燃起了我的好奇心。

聊到 sgavmp 就不得不聊聊 liteVM,不过我并不想在这里过多的描述它,我会单独在写一篇 liteVM 的文章。

在逆 sgavmp 早期 litevm 并没有引起我的过多注意,从 sdk 中我知道有它的存在,但是它是怎样的存在就不得而知了;然而你想逆 sgavmp 你就不得不过 litevm 这一关,它出现的机率不亚于sgavmp;早期的时候它经常会乱入,随着我们把坑慢慢填满,才终于发现原来 litevm 是这样的一个存在,它作为一个附属大礼包免费送给你了。

逆向 sgavmp 过程是极其煎熬和富有挑战的,因为它确实有难度;做逆向的人应该最喜欢流水式的程序,你不用思考,很多时候静态分析就解决了大半工作,剩下的部分只需看着伪代码在模糊点调试一下即可。

困难和恐惧来自未知,当程序打破流水式的时候,你会发现即使有伪代码你仍看不懂,程序不再有连续性,它的走向是未知的;在未知中给你添加几个小黑盒,黑盒隐藏了大部分逻辑,当你兴高采烈的弄明白黑盒子时,你发现原来它只是个盒子,盒子里面的东西才是你想要的;当你再次高兴地把里面看清楚时,你发现原来你想要的东西还是在外面,你永远不会知道下一步等待你的是什么。

代码风格好的程序也是逆向者的最爱,因为数据结构清晰,我们几乎不用动脑就能轻松地还原;如果编程者刻意隐藏结构之间的关系,让本来存在直接关系的结构变成间接关系,那想必会增加逆向难度。

一个巨大的数据结构也会增加逆向的难度,想想如果你去逆向 linux 内核(假设没有源码),想还原 task_struct 结构你需要花特别多的力气,一个是它自身大,另外是和它存在关系的结构也非常巨大,想正确地还原每个结构的每个属性还是比较难的。





追寻正法

       
对于 sdk 类或者插件类程序,我并不喜欢直接调试或者逆向它的宿主程序,原因有三:一、sdk 或者插件本身就已经很复杂,和宿主融合到一起后会更加复杂,无疑增加逆向成本,二、需要找到 sdk 或者插件的调用入口点,三、需要绕过宿主的对抗。当一个宿主程序包罗万象大得夸张的时候,你千万别高估自己的定位能力,也千万别高估自己的耐心。
      
对抗 sdk 或者插件我的思路是从最小化集成到完全集成,通俗讲就是你来作为它的宿主,你负责它生命周期管理,需要什么集成什么,我也确是这么做的。
       
你的宿主可以很轻量化,并且能随时定制,仅提供你所需要的功能即可。

下图是我定制的宿主的view,它提供了我所需要的功能。


多道


sgmain、sgavmp、sgsecuritybody 等前身是百川 sdk 下的无线保镖,最早由聚安全开发,同时对外对内都提供安全能力,对外提供低版本的5.x,对内提供更具安全能力的6.x版本;5.x版本不具备 avmp、litevm 功能,也不具备其他插件的能力。

如某内部 app 集成了无线保镖,它将拥有如下图所示部分(具体看实际集成):


就 sgavmp 来说,libsgavmp.so 是一个压缩包,是一个完整的 apk,而带版本号的 so 才是真正的 so 文件。

这些插件中,sgmain 是主插件,其他插件强依赖这个插件,sgmain 对其他插件可能存在弱依赖关系,也就是说 sgmain 可以独立运行,但其他插件不可以,sgmain 插件的某些功能被单独拿出实现成了其他插件,因此对其他插件可能存在弱依赖。

sgmain 插件:


sgavmp 插件:


我之所以这么了解,是因为在逆向过程中遇到了解决不了的问题,我还特意注册申请了该 sdk,详细的了解了文档中我想关注的地方。

但很遗憾申请到的 sdk 版本是5.x的,上面也提到了它没有 avmp,最终它也
没能解决我的问题。但通过了解文档相关信息我更加了解这个 sdk 了,它对我最终逆向是有益的。

因此提醒大家有些时候收集信息真的很重要,千万不要陷入埋头的怪圈。

我用的版本是 sgmain6.4.176, sgavmp6.4.38,但为了保密性,我在文中隐藏了一些关键点,希望见谅。

该文档仅仅用来学习交流,用来提高安全门槛,不能用来做恶意事情,否则后果自负!


初禅


当 sgmain 插件启动之后,我们就可以创建 avmp 了,来让我们通过 sdk 代码捋一下它的创建过程:

1. 首先调用 createAVMPIntance 函数


2. 内部类调用 doCommand(60901)


3. so 层先准备 command,它的 JNI_OnLoad 函数大致如下图:


4. 创建 avmp 实例


创建过程还是比较复杂的,对应结构也比较复杂,我以简单的图示来展示一
下重要的创建过程(图示中的名称都是我自己命名的,不代表真实情况):





菩提证果


经过了漫长的逆向它慢慢的展现在我面前,我这里把一些结构的部分截图展示一下。

AvmpInst:


AvmpInstVcode:


XorAvmpInst:


还有很多复杂的结构,我就不一一展示了,感兴趣的同学可以自己逆向挑战一下。

它有自己的内存管理,这个可能是大多 vmp 不具备的功能,外层数据需要下沉两层;一层是 vmp 内存数据对应的偏移,一层是真正的 vmp 内存。

数据全程加密,当你使用数据时先进行解密,读取数据后再加密回去。
       
很多数据操作、交换、算法单单靠 vmp 指令实现是不现实的(代码膨胀地厉害、很多依赖关系无法解决)。

avmp 依赖两种外部调用实现:一种是封装libc库函数(叫封装可能不贴切,叫引用更合适),我把此类函数称作innerFunc,它可以完成内外部数据的交换等操作,如 memcpy。另一种调用也或多或少包含库函数,但除此之外它还可能调用 sgmain 中的相关逻辑甚至是 lvm。 


我把此类函数称之为 handler,此类 handler 共13种,在初始化时会用200xx这样的编号进行注册。
 

如某个 handler 逻辑中包含调用 command 的逻辑,这两种外部函数对应其vmp 指令类型相同,关于创建我就说这么多吧。





轮转法轮


波罗奈城


创建 avmp 实例后我们就可以进行 vmp 调用了,调用其大多是为了实现数据签名,我们来看一下 sdk 代码,调用 avmp 是通过 doCommand(60902) 来实现的,long 型参数就是


创建 avmp 实例时返回的,它大概调用序列如下图:


摩揭陀国


创建 avmp 实例时我们会得到字节码,字节码的前8个字节用来匹配解释器,目前 avmp 实现了三种解释器。

调用哪个算法是通过匹配模块符号来确定的,字节码9-16字节用来确定模块符号,通过模块符号匹配定位模块索引,最终我们就可以找到字节码了。

 

释迦国


指令解析,就如同 arm 指令一样它定义了一套自己的指令集,指令长度同样为4字节,它大概如下图的样子(并不能十分准确):


做逆向可以直接通过逆向工具看到汇编代码(它向我们隐藏了解码过程),对于指令的解析,运行时 cpu 直接译码,静态时反编译器帮助我们解码。

在实现自定义指令或者 vmp 时,解码工作是我们实现解释器的重要功能,解释器负责按照指令定义格式一个指令一个指令地一步一步解析,先加载指令:


接着解析指令:

 
我们以 avmp 某个指令为例(如指令 000C7BC2,指令类型为2):

1.  取指令类型


2. 取源寄存器 Rn,取偏移


3.  取指令第29、30位


4. F1 = off | CODE[30] | CODE[29]

5. load [context + 4*Rn]

6. OF = 指令>>16 & 0xc000,取31, 32位

7. offset = OF | F1

8. addr = base + offset


9. 取指令6-11位为目标寄存器 Rd,保存数据到目标寄存器


指令解析还是比较复杂的,解释器是由n多这样的块组成,最终完成各种指令的解析。

FFFFFFFE 类似是 pop 指令

FFFFFFEE 类似是 mov 指令

xxxxxxxA 指令类似 ldr r0,  [[base + off], addv], add r0, off + shift,  str r0 [base + rd]。

FFFFFFFA 类似是 mov 指令把内存值移动到寄存器, ldr r0,  [[base + off], addv], str r0, rd。

00000014 指令会调用 innerfunc 或者 handler。

我们需要特别关注一些类型为14的指令,因为它负责调用外部例程(上面提到的),如调用 innerFunc:


再例如调用 handler,先根据注册类型查找 handler:


查找到 handler 后调用相应的 handler:


舍卫城


avmp 主要用来实现签名算法的,其逻辑十分复杂,从上面的调用序列我们可以得到执行逻辑除 vmp 指令外,它还在 lvm 和 securitybody 之间穿梭。

即使不存在 vmp 指令这套算法逻辑还是很复杂的,它的复杂不在于最后计算算法的本身,而是在于对抗逆向上,想彻底搞明白它的算法,还原它的算法还是很难的。

对于签名算法我想说的是,如就 sign 算法:

首先它由三部分构成(三个部分拼接而成),算法外层分为两大类,算法内部每套都调用了三种密码学类算法(两套共用了四种密码学算法)。

其次你更多地需要关注 handler,有一个 handler 特别重要,每次它都是进入lvm 的入口,也是最终调用算法的入口。

再次最终算法和 securitybody 有关,它会间接地进入 lvm。





娑罗双树


金刚经

      
说了这么多,其实我觉得了解它的 vmp 指令或者还原它的指令、算法意义不大,我们应该更多地学习别人的优点,学习他们怎么做加固对抗的。另外补充说明他们真的很强大,做的东西真的很牛逼。


法华经

       
展示结果。

1. 调试获取


2. 程序黑盒


结果:


涅槃经


不过是个东西就有利有弊,我也肤浅地提出一些自己的看法,有不对的地方还请原谅。

优点:全程加密;复杂、特别复杂,完全是指令级别的vmp,有自己的指令集,设计过于复杂;拥有自己的内存管理,数据在内部保密性好;拥有多层数据管理,数据分散,保密性好。

缺点:有独立内存,也恰巧给了别人窥视的机会;很多复杂算法如加密算法(不是指 vmp 的内部数据加密)都是最终调用的外部实现这给了别人可乘之机;内部加解密也给了别人观看数据的机会;浪费空间浪费内存,内部内存太大,大多都用不到,一般小内存手机根本跑不起来,频繁的 vmp 内存操作,影响效率。

虽然这么说,但我自己可能连 vmp 都实现不了,哈哈,因为它确实已经非常优秀了,该有的都有,你能想到的它都有。
     
再次声明:该文档仅仅用来学习交流,用来提高安全门槛,不能用来做恶意事情,否则后果自负!
 
本人该项目 github 地址:
 https://github.com/ylcangel/crack_sgavmp

会不会提交某些数据结构看个人心情,你千万别指望我会这么做,谢谢!
 
转载需标明出处,否则发现必究!!!




- End -




看雪ID:angelToms

https://bbs.pediy.com/user-665739.htm

  *本文由看雪论坛 angelToms 原创,转载请注明来自看雪社区。



推荐文章++++

* 2020网鼎杯 青龙组 Android逆向题 rev01 WP

* Youpk: 又一款基于ART的主动调用的脱壳机

*新手系列教程——用bfinject脱壳、注入自己的动态framework、cycript的使用

* 新手教程——按键精灵脚本来模拟合成灯笼

*  RCTF 2020逆向Cipher


好书推荐






公众号ID:ikanxue
官方微博:看雪安全
商务合作:wsc@kanxue.com



“阅读原文”一起来充电吧!

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存