仅供安卓逆向交流学习, 相关数据已脱敏.
最近某高校推出了要求学生跑步打卡的操作, 虽然配速要求其实并不严格, 但一学期要打卡 24 次, 每次至少 2400 m… 对我来说还是有点困难了, 所以来研究一下相关的软件.
0x00 前期观察&信息收集
通过分析相关 Activity 的包名发现这个跑步打卡的部分并不是学校自己做的, 是整合了一个第三方公司的产品到学校的 APP 中, 根据相关的软件说明发现它应该会进行步频(加速度传感器), GPS定位, 配速, 以及设备本身几个方面的检查.
酷安上搜索对应产品讨论区发现很多人在使用 Fake Location 虚拟定位进行跑步打卡, 不过这显然一点都不优雅, 而且还有人反馈较新版本会被检测到取消成绩, 同时该 APP 也会进行虚拟机检测. (有实机的我无所畏惧~)
0x01 抓包
遇事不决先抓包, 在高版本安卓使用 Http Canary 进行抓包需要 Xposed 模块绕过 SSL 证书校验. 由于我日用主力机一直是 GSI 类原生 + Magisk + LSPosed, 所以抓个包自然不成问题, 很容易就抓到了通信相关的 http 包. (省略了无关 Header, 部分敏感数据用 * 替代.)
1 | POST /v3/api.php/Run/getTimestampV278 HTTP/1.1 |
1 | HTTP/1.1 200 OK |
观察请求和响应, 发现请求中包含了 timestamp, nonce 和 sign, 响应显然是加密的, 所以要通过模拟相关的请求先要搞定签名和加密的问题.
0x02.0 解密方法一: Hook 加密相关 API 拦截密钥
最偷懒也是相对比较万金油的逆向加密的方法就是直接 Hook 相关的 API, 并获取密钥. (只要使用的是通用的加密/哈希算法, 且密钥和用户无关是定值/密钥变化但规律比较容易推测.)
虽然使用条件看起来比较苛刻, 但实际上很多用了签名&加密的 APP 都符合这个要求, 很简单就能模仿请求&解密响应.
Google 能搜到不少 Hook 加密 API 的 Frida 脚本, 不过我自己使用的是 Inspeckage, 它 Hook 的接口相对更多更全.
当然, 很多工具支持的安卓版本都没那么高, 要做逆向的话一台谷歌亲儿子还是很需要的, 配置好相关环境, 运行目标应用, 查看 ADB Log, 通过 grep 筛选很快就能得到下面的关键信息.
(btw. 我们学校的 APP 真的是不知道整合了多少家公司的(垃圾)软件… 调用的加密 API 就乱七八糟千奇百怪. 简直就是在我手机里养蛊.)
1 | Inspeckage_Crypto:SecretKeySpec(****************,AES) , Cipher[AES/CBC/PKCS5Padding] IV: **************** |
不难发现加密使用的是 AES/CBC/PKCS5Padding, 并得到了相关的 Key 和 IV, 发现能解密所有的请求响应. 签名使用的是把所有 Http Param 排列拼接后加上一个字符串进行 MD5.
0x02.1 解密方法二: 脱壳+IDA Pro
本来其实方法一已经达成目的了, 只是因为我使用了 FART, 所以一不小心就完成了脱壳的工作… 既然如此, 也就顺便来分析一下源代码, 看看能否从中找出密钥.
使用 jadx 打开脱壳后的 dex, 发现相关代码虽然加了壳, 但完全没有混淆, 所以所有的代码逻辑和变量名都一清二楚. 直接搜索相关关键词, 不难发现加密最终调用的方法.
1 | public class JNIUtils { |
相关的密钥显然保存在 Native 层中, 直接找出相关的 .so
文件, 丢进 IDA Pro.
也没有遇到任何阻碍, 直接就能找到需要的密钥. (甚至都不一定需要脱壳… 直接解包 apk 在 so 里搜索就完了. 这个 JNI 保护的力度还没壳强, 壳至少把字符串都保护了.🤡)
0x03 抓包分析全流程
上面已经找到了签名和解密的方法, 现在不难写出对应的 Python 代码对请求进行模拟并解密响应.
1 | def sign(ret): |
同时我们也可以解密 Http Canary 抓包抓到的请求了, 于是拿着手机开着小黄鸟出去跑一小会儿步, 并整理一下相关的请求, 不难发现一次跑步中需要依次发送下面这些请求.
/User/User
获取当前登录用户的信息. (登录是使用的学校 APP 的 OAuth 认证.)/Run2/beforeRunV260
返回一些跑步地点的相关信息, 包括速度, 时间, 地点等要求./Run/getTimestampV278
获得时间戳和一个 “record_str”, 结束跑步时需要用到.在跑步过程中每隔一段时间(约 1-2min)调用
/Run/setRunLocationRecord
, 上报当前的经纬度.结束跑步前调用
/Report/getOssSign
, 获得一个阿里云 OSS 的签名, 用于上传一张图片和一个文本文档.图片是跑步过程中手机屏幕的截图, 文本文档是 AES 加密过的 json, json 内容是实时采集(2s 一次)到的所有经纬度信息和手机侧记录的配速.
最后综合上述所有信息(包括跑步开始时间, 结束时间, 时长, 公里数, 刚刚上传的两个文件链接, record_str, 每分钟步频等), 调用
/Run/stopRunV278
, 提交一次跑步记录到服务器, 即完成了一次跑步过程.
0x04 编写程序模拟
理顺了流程之后写一些 Python 代码来随机生成一份看起来比较合法的记录, 并依次发送上面的请求到服务器, 即完成了一次赛博跑步.(
本文采用 CC BY-NC-SA 4.0 许可协议发布.
作者: lyc8503, 文章链接: https://blog.lyc8503.net/post/nju-cyber-run/
如果本文给你带来了帮助或让你觉得有趣, 可以考虑赞助我¬_¬