一个安卓应用的逆向分析
待分析应用的名字不透露了,避免引火烧身。需要准备的工具包括
- mumu 模拟器(或者别的什么有 root 权限、能装 xposed 的模拟器)
- FDex2 脱壳
- jadx 反编译 dex 源码
- apktools 拆解 apk
- mitmproxy 中间人拦截网络请求
<!-- more -->
## 0x01 目标和方向选择
首要的目标是分析这个软件的 api 加密。
使用 mitmproxy 抓到 https 流量,发现请求体全部是 base64 ,解码发现乱码。基本断定是加密了。
!(https://nnnewb.github.io/blog/image/Just-crack-an-android-app/01.png)
搜了一圈没有什么现成的对这个 App 的分析的文章,于是决定自己动手。
## 0x02 解包和脱壳
先确认下电脑上装了 JDK 或者 JRE ,没有的话就装好。
推荐一个 vscode 的插件,`apklab`。会帮你装好 jadx 和 apktools / signer 这些工具。
接下来直接用 `apklab` 打开需要分析的 apk 文件。
!(https://nnnewb.github.io/blog/image/Just-crack-an-android-app/02.png)
!(https://nnnewb.github.io/blog/image/Just-crack-an-android-app/03.png)
!(https://nnnewb.github.io/blog/image/Just-crack-an-android-app/04.png)
apklab 会自动用 apktools 和 jadx 完成拆包和反编译。
然后简单观察...
!(https://nnnewb.github.io/blog/image/Just-crack-an-android-app/05.png)
应该是被 360 加固了。
apk 加固的基本原理就是把易被反编译的 java 字节码转译或者加密后保存,运行的时候再释放出来。用过 upx 一类的软件应该会联想到,就是加壳、反调试什么的这一套。
xposed 提供了一个[在安卓包加载时设置钩子的机会](https://api.xposed.info/reference/de/robv/android/xposed/IXposedHookLoadPackage.html),将 ClassLoader Hook 掉,以此获得真正的应用字节码。
安装 xposed 框架和 FDex2 之后启动目标应用,即可获得对应的字节码 dex 文件。
!(https://nnnewb.github.io/blog/image/Just-crack-an-android-app/06.png)
!(https://nnnewb.github.io/blog/image/Just-crack-an-android-app/07.png)
接着把这些 dex 文件复制出来,即可使用 jadx 反编译到 java 了。
```shell
jadx -d out *.dex
```
将反编译的结果用 vscode 打开,可以看到目标已经被我们脱干净了。
!(https://nnnewb.github.io/blog/image/Just-crack-an-android-app/08.png)
## 0x03 寻找加解密代码
目标是解密 Api 请求的内容,所以下一步就是找到哪里保存了加密代码。
幸运的是这个 App 没有做过混淆,完成脱壳后就已经是全身赤裸的站在我们面前了。
直接在代码里搜索之前我们观察到的 url:`index_des.php`,仅有一个结果。
!(https://nnnewb.github.io/blog/image/Just-crack-an-android-app/09.png)
相关函数非常短,这个 HTTP 框架我没有使用过,不过从函数名看应该是一个中间件模式,对所有 Web 请求进行加密处理。
!(https://nnnewb.github.io/blog/image/Just-crack-an-android-app/10.png)
`getOverPost2` 源码如下
!(https://nnnewb.github.io/blog/image/Just-crack-an-android-app/11.png)
从代码里可以得出:
- g 的含义是 Get 请求的参数,应该就是 QueryString。函数名 `getOverPost2` 字面意义就是把 GET 请求以 POST 方式发送出去。
- p 的含义大概就是 Post 的参数了。
- 加密代码在 `encryptByte`
如此看来已经接近终点了,再点开 `encryptByte` 的定义
!(https://nnnewb.github.io/blog/image/Just-crack-an-android-app/12.png)
密钥保存在 `DesLib.sharedInstance().getAuthKey()` 中。
接着点开 `getAuthKey` 的定义:
!(https://nnnewb.github.io/blog/image/Just-crack-an-android-app/13.png)
`native` 关键字一出,得,白高兴了。差点劝退成功。
还是先看下怎么加密的。
!(https://nnnewb.github.io/blog/image/Just-crack-an-android-app/14.png)
再往回翻一下响应解密的代码,免得拆除密钥来又白高兴一场。
!(https://nnnewb.github.io/blog/image/Just-crack-an-android-app/15.png)
!(https://nnnewb.github.io/blog/image/Just-crack-an-android-app/16.png)
很好,也是 DES 。
其实到这一步已经基本完成解密了,唯一欠缺的就是密钥。
抱着试一试的心情,还是找到了 `libencry.so` ,用 IDA 打开分析了一下。
!(https://nnnewb.github.io/blog/image/Just-crack-an-android-app/17.png)
一通操作猛如虎,结果发现看不懂汇编。=w=
按下 F5,看看伪代码。
!(https://nnnewb.github.io/blog/image/Just-crack-an-android-app/18.png)
还是看不懂。这都调的什么函数... `a1 + 668` 这个蜜汁偏移也不知道是在算什么。
网上搜索了一圈,说道可以手动改一下函数签名,IDA 就能提示出函数了。试试看。
先把函数签名纠正
!(https://nnnewb.github.io/blog/image/Just-crack-an-android-app/19.png)
!(https://nnnewb.github.io/blog/image/Just-crack-an-android-app/20.png)
再关掉类型转换
!(https://nnnewb.github.io/blog/image/Just-crack-an-android-app/21.png)
最终关键代码清晰了很多,看起来就是个直接返回字符串常量的函数。
!(https://nnnewb.github.io/blog/image/Just-crack-an-android-app/22.png)
比较具有迷惑性的是上面的 v5-v9,可以看到 v5-v9 地址是增长、连续的,只有 v5 和 v6 有值。v7/v8/v9 都是 0 。而 v5 的地址被用作 `NewStringUTF` 函数的参数。查阅 JNI 接口也可以看到这个参数应该是 `const char*` 类型。
所以 ...
把数值转换成 16 进制再做观察。
!(https://nnnewb.github.io/blog/image/Just-crack-an-android-app/23.png)
发现很有规律,每个字节的值都在 ASCII 范围内。于是右键转换成字符串,再按字节序翻转一下,即可得到密钥。
到此,解密方法的探索已经完成。
## 0x04 mitmproxy 解密
mitmproxy 支持使用 python 脚本扩展,用法很简单就是 `mitmweb.exe -s decrypt.py`
可以参考 mitmproxy 的[例子](https://github.com/mitmproxy/mitmproxy/blob/master/examples/addons/contentview.py)
最终效果应该是这样
!(https://nnnewb.github.io/blog/image/Just-crack-an-android-app/25.png)
核心的解密代码就一句,利用 mitmproxy 的扩展即可对每个请求进行统一的处理。
```python
from pyDes import des, PAD_PKCS5
def decrypt(data: Union) -> bytes:
return des(key).decrypt(data, padmode=PAD_PKCS5)
```
## 0x05 总结
这个分析的最大意义还是完成了一次完整的安卓逆向,算是点亮了新技能。 带俗 发表于 2021-9-11 10:57
执行Open an APK后提示这个:Downloading file: apktool_2.5.0.jar
Downloading file: uber-apk-signer-1. ...
请求超时,可以临时挂个梯{和谐}子,再打开vscode,让apklab下载。或者也可以手动下载这三个工具,放进家目录下的.apklab,目录结构如下所示。
目录: C:\Users\weakptr\.apklab
Mode LastWriteTime Length Name
---- ------------- ------ ----
d----- 2021/9/11 11:52 jadx-1.2.0
-a---- 2021/9/11 11:52 19300357 apktool_2.5.0.jar
-a---- 2021/9/11 11:52 15434528 jadx-1.2.0.zip
-a---- 2021/9/11 11:52 1861628 uber-apk-signer-1.2.1.jar 执行Open an APK后提示这个:Downloading file: apktool_2.5.0.jar
Downloading file: uber-apk-signer-1.2.1.jar
Downloading file: jadx-1.2.0.zip
Error: connect ETIMEDOUT 192.30.253.112:443
Can't download/update dependencies!
Error: connect ETIMEDOUT 192.30.253.112:443
Error: connect ETIMEDOUT 192.30.253.112:443。怎么处理啊? 很好的分析逆向示例{:301_1003:} 学习了 谢谢楼主分享 修改类型为JNIEnv*后,(*a1)->NewStringUTF这里右键NewStringUTF,force call就可以正确显示参数列表 工具能分享一下吗
很好的技术贴,学到了方法 Ichild 发表于 2021-9-11 08:35
修改类型为JNIEnv*后,(*a1)->NewStringUTF这里右键NewStringUTF,force call就可以正确显示参数列表
谢谢分享小技巧,我还不知道能这样。
试了下在 IDA 7.5 和 IDA 7.0(论坛下的)打开 x86 的 so ,在 NewStringUTF 上右键都没有这个 force call,是需要特定版本的 IDA 或者装什么插件吗? liu流年 发表于 2021-9-11 08:47
工具能分享一下吗
您好,麻烦看帖子内容,工具都已经列出来了。
IDA可以在论坛找,mitmproxy可以到 github 找。