fnv1c 发表于 2021-7-7 19:30

某锁机软件的so脱壳与逆向分析

本帖最后由 fnv1c 于 2021-7-9 10:21 编辑

## 0x01 背景
高考完了,有时间水群吹牛逼了。碰巧某同学说自己平板被"锁机软件"锁了,只能通过输入动态码解锁。他提供了样本apk,说希望能逆出来动态码算法。虽然对安卓逆向非常陌生,但总比打一天游戏更有意义,于是开干。
> ** 免责声明:本文旨在与分享移动安全相关知识,相关内容已作脱敏处理。请勿用于非法用途。 **

## 0x02 前置知识和工具
本文主要是so层的脱壳逆向,几乎没有涉及java层的知识
> 预热篇:
> 前置知识:有手就行
> 工具:可以反编译ARM的IDA   

> 正篇:
> 前置知识:C++逆向经验,ELF相关的一点经验,frida基础使用   
> 工具:ADB,frida,可以反编译ARM的IDA,xAnSo

## 0x03 预热篇
基础逆向发现java层通过调用JNI层函数`verifyDevtoken(用户输入内容,null)`来判断用户输入解锁码是否正确。


找到对应的so,ida打开,肉眼可见验证函数。


发现对a1进行了几次虚函数调用,然后和creatDevToken函数生成的动态码比对并返回结果。
根据函数signature可以看出a1为JNIEnv,根据JNIEnv虚表可知+676出为`GetUTF8Char`,即获取jstring的内容。
跟入creatDevToken逻辑比较清晰,但在最后发现一个古怪的函数调用DDD4();

跟入发现是std::string构造函数的thunk函数,显然函数参数个数不对,继续跟入f5,然后返回再次f5,直到creatDevToken逻辑正常。

由逻辑判断此函数应该为substr,v15为计算完成的key,根据时间对key做substr再返回。

容易写出keygen

## 0x04 正篇
发完key,他告诉我key解不了。他的应该是新版本软件,又来了个新版本样本。我人傻在那里,这就是买一赠一吗,爱了爱了。好在so名字没变,直接拖ida看,发现export炸了。。。



全部都是只有ret的小函数,又有不正常的段,应该是加了奇奇怪怪的壳子。搜不到jni调用的函数,说明可能是动态注册(RegisterNative)或者是动态生成soinfo的强壳。看了下init_array和jni_onload,发现啥都看不懂。




只能上frida真机调试了。github上找一个可以hook RegisterNative的脚本(https://hub.fastgit.org/lasting-yang/frida_hook_libart),载入启动。启动过程中没有见到动态码相关的函数RegisterNative,应该是动态生成soinfo的强壳。考虑到JNI层验证动态码必须要获取jstring内容,所以给`GetUTF8Char`下断,根据调用者判断验证函数的地址。

终于发现so库的名字和JNI函数名,但是由于加了动态soinfo强壳,只能在frida中获取库基址和函数地址,ida中看不到导出项目。看看maps有什么特点。


验证函数居然不在so的映射范围内,而是在一个新的匿名段。四个匿名段有很典型的rx,rw,ro的权限分布,猜测是子母ELF壳,母so加载时解密真实ELF,并且装入内存。魔改soinfo使得子ELF的导出项对外可见。由于soinfo中库基址指向母ELF,直接将子母ELF一起dump到文件分析。

由于是内存dump,ida分析时需要选择手动装入并指定基址。


装入后就可以找到真实jni函数的导出项,但是ida无法显示其中内容。


经过查找资料,得知内存中dump出来的so必须修复sections才能被ida正确分析。通过xAnSo(github上可以找到)进行修复,再次拖入ida分析。
ida这次可以读出函数的内容,但是无法创建函数,提示控制流错误。

定位到错误点附近,发现已经有了pop PC,而下面有一个函数调用,随后是一串string的析构函数。熟悉C++逆向的朋友会发现下面一串析构函数调用是在此函数尾部一个ret后,大概率是栈展开相关代码。BLX的调用大概率不是正常的控制流,可能是错误处理。此时可以对照新旧so的汇编看,发现BLX其实调用的是stack canary检测失败时的报错退出函数(_stk_chk_failed)。双击进入这个函数,编辑函数,添加noreturn属性。然后就可以成功创建jni导出函数了。

找到了key生成函数和关键跳,大概逻辑是生成key并和用户输入对比,对比完返回结果。

追入key生成函数,发现ida无法反编译。在ida窗口调到对应地址,发现ida无法将其识别为函数(因为上文中_stk_chk_failed的问题)。手动创建函数,重新f5,成功。

自此可以观察到完整生成逻辑。

然而还是遇到了一些奇怪的函数,比如下图,跳转到了一个ELF地址范围外的函数。


其实这个函数是plt表中的thunk,调用的是导入的外部其他库的函数。由于xAnSo不带导入表修复的功能,所以IDA无法识别函数名,只能当作是间接跳转处理。而GOT表中已经存放了相应函数的地址,ida会反编译成JMPOUT(外部函数地址)。可以通过frida的`DebugSymbol.fromAddress`功能获取对应地址的函数。进行手动导入函数修复。

第二个奇怪的地方是对string的读取部分,会看到下图中的特征代码。其实是stl string的SSO。限于篇幅,本文不介绍SSO技术。可以手动定义一个string结构体,导入非SSO状态下的string内存布局。会让ida的伪代码更清晰一些。


注:frida中读取string.data()的实现

看完全部逻辑就可以写keygen了,经过测试key正确。

(为脱敏本文不附带新版本key生成过程伪代码,只记录脱壳和修复过程)
## 0x05 结语
脱壳逆向过程比想象中艰难恶心,但也给人一点启示。
**要有终身学习的觉悟**:在遇到so强壳时,因为没有安卓逆向经验,差点放弃。幸好狠下心花半个小时熟悉了下frida,追到输入的动态码后带来了继续下去的动力。如果因为从没做过安卓逆向就放弃学习,只停留在熟悉的领域,估计这辈子就和安卓逆向无缘了。
**做软件不能本末倒置**:当一个软件为了防破解而做的工作远大于软件内容本身时,就要仔细考虑是否需要引入这么复杂的防破解手段。对于某些用户必须使用的软件,砸着经费,版本迭代着,软件功能的风评却不改善,反而是于用户无用的加密手段越来越先进,何尝不是一种本末倒置,更给破解再二次倒卖的行为提供了滋生的温床。

毁我容颜 发表于 2021-7-8 07:46

高中生就这么厉害,牛

fnv1c 发表于 2021-7-8 09:13

本帖最后由 fnv1c 于 2021-7-8 09:15 编辑

laos 发表于 2021-7-8 03:59
配套frida代码能不能传一下当然那些重要信息可以处理下
https://hub.fastgit.org/lasting-yang/frida_hook_libart
根据真机环境可能得稍微改改脚本才能用

狄人3 发表于 2021-8-24 23:14

这tm不是学海吗?

王者归来back 发表于 2021-7-7 20:39

不懂的我居然看完了!:Dweeqw

Zhili.An 发表于 2021-7-7 21:20

不得不说,还是很厉害的

djxding 发表于 2021-7-7 21:41

楼主牛B啊,向你学习。

aihaibo 发表于 2021-7-7 22:23

虽然不懂 还是看完了

qcl54651 发表于 2021-7-7 23:19

不懂的我,也看完了

goda 发表于 2021-7-7 23:25

谢谢分享,学习了

lies2014 发表于 2021-7-8 02:11

这个可以提供样本吗?

laos 发表于 2021-7-8 03:59

配套frida代码能不能传一下当然那些重要信息可以处理下
页: [1] 2 3 4 5 6 7 8
查看完整版本: 某锁机软件的so脱壳与逆向分析