观前提示:
本文章仅供学习交流,切勿用于非法通途,如有侵犯贵司请及时联系删除
样本:aHR0cHM6Ly93d3cuYWxpeXVuZHJpdmUuY29tL3MvMzV4a1BzcFRnQkg=
0x1 抓包
安装样本App 打开抓包软件
登录失败 一气呵成 可以判断可能存在证书验证
上hook脚本
配合手机AppPostern
食用更佳
打开 Charles
抓包并启动脚本
建议使用spawn
模式来hook
然后就可以正常抓包啦
找到本次需要分析的包https://***/x/passport-login/oauth2/login
看里面PostData 其中需要分析的值只有
Name | Value |
password | FPvjUpqde8PaW5cONQtxuEEbF6MFBR5XNX8QPVxakZfzcL7Crz02/F1JOwk4kAMfD+YYNEPT8Ib3cE5b9+jxA2PDPwwvzYeX/G3yhmbZ1yEVjwiJesRO4zb5qKkPtQdEpJi3a5WoAqZhpX3/dv7FssH5b1fQxaq1aqcaR6bZ7Nw= |
sign | 1b20efbb2f975326ea41dbddcafa7e9b |
0x2 找加密
使用jdax
打开样本apk
样本很大 你忍一下 如果出现内存不足的情况 则需要修改更大的内存来解决
参考文章:https://www.cnblogs.com/wuxianyu/p/14382310.html
正常打开后 搜索passport-login/oauth2/login
只有一个结果 双击进入方法 然后 往上找调用
其中出现了明显的encryptPassword
这里就是password
的加密入口
进入b.b
方法
其中a
方法对应上面 明牌展示RSA/ECB/PKCS1PADDING
加密
了解了password
的加密过程 接着找sign
的加密方法
先回到原先的方法 有点繁琐 我就省略一点
- 进入biliPassportApi.d()
- 进入(AuthKey) i(n().getKeyV2()) 中的
i
- 进入 biliCall.execute()
- 进入 this.h.intercept(request)
- 找到 接口的实现
DefaultRequestInterceptor implements c
- 找到
DefaultRequestInterceptor
中的intercept
- 进入
intercept
中的addCommonParamToBody(request.url(), request.body(), newBuilder)
- 进入
addCommonParamToBody
中的signQuery(hashMap)
这里就是sign
的加密处 往里找 最终定位到
static native SignedQuery s(SortedMap<String, String> sortedMap);
往上看可以看到bili
字眼 可以猜测是加载了libbili.so
在apk中也能够找到该文件
0x3 加密分析
找到加密点后 先上Frida hook验证
Java.perform(function () { var Class = Java.use('com.bilibili.nativelibrary.LibBili'); var Method = 'signQuery'; Class[Method].overload('java.util.Map', 'int', 'int').implementation = function (a, b, c) { console.log('===args==='); console.log(a.entrySet().toArray()); console.log(b); console.log(c); var result = this[Method](a, b, c); console.log('===result==='); console.log(result); console.log('---------------------------------------'); return result; } })
能够看到方法被调用 入参和返回结果都出来了
接下来 尝试一下主动调用一下这个方法
Java.perform(function () { var Class = Java.use('com.bilibili.nativelibrary.LibBili'); var Method = 'signQuery'; var TreeMap = Java.use('java.util.TreeMap'); var map = TreeMap.$new(); map.put('disable_rcmd', '0'); map.put('build', '6620300'); map.put('buvid', 'XY4A57646AAC62990A45476D19D573C81F192'); map.put('mobi_app', 'android'); map.put('channel', 'bili'); map.put('appkey', '783bbb7264451d82'); map.put('s_locale', 'zh-Hans_CN'); map.put('c_locale', 'zh-Hans_CN'); map.put('platform', 'android'); map.put('statistics', '{"appId":1,"platform":3,"version":"6.62.0","abtest":""}'); var result = Class[Method](map, 1, 0); console.log(result); })
成功调用并且返回了包含ts
和sign
的字符串
接下来上IDA Pro
大姐姐伺候这个so
加载完后看导出表
oh 上帝 我的天呐 这是什么 不会是OLLVM吧
尝试搜索是否是静态注册的方法
没有结果 搜索JNI_Onload
进来后看到了不想看到的画面
不过现在hook技术那么发达 还怕找不到吗
上万能的百度 找个RegisterNatives
的hook
Github:
https://github.com/lasting-yang/frida_hook_libart/blob/master/hook_RegisterNatives.js
启动hook
太多了吧 所以还得改改代码做一次筛选
此处需要加上if来筛选我们需要的class类
筛选完后就可以看到SignedQuery
的偏移了
回到IDA Pro
跳转即可
跳转过来后 还是看到了令人恶心的OLLVM
因为从抓包来看sign的长度是32位 初步怀疑是MD5 所以本篇我使用IDA插件findhash
来辅助分析
Github:
下载好后放入IDA目录下的plugins
目录 在IDA里面启动插件即可
由于so被OLLVM混淆过 代码大量膨胀 所以需要时间处理 需要耐心等待
运行完代码自动生成一个Frida hook文件 Frida运行此文件就能hook住疑似哈希的方法
得到方法以及堆栈后直接跳转
跳转过来后发现真就MD5的运算部分
继续跳转0xe614
堆栈
通过对比其他的MD5的代码 可以判断这里是MD5update
上Frida hook!
Java.perform(function () { var so_addr = Module.findBaseAddress("libbili.so"); var method_addr = so_addr.add(0xDE8C); Interceptor.attach(method_addr, { onEnter: function (args) { console.log('==================', method_addr, '========================'); console.log(hexdump(args[1], { length: args[2].toInt32() })) console.log('Length->', args[2]) }, onLeave: function (retval) { console.log('==================', 'out', '========================'); } }) });
然后主动调用一下 结果就出来了
可以看到update方法调用了七次 但是后俩次并无增加内容
从第一次返回的内容可知
- map中增加了ts和时间戳
- 将map的内容拼接为字符串
后面又连续拼接了四次
- 2653583c
- 8873dea2
- 68ab9386
- 918b1d65
并且多次主动调用 这四个拼接的内容都为定值 那就不管啦
后续我们手动拼接一下测试
原文->appkey=783bbb7264451d82&build=6620300&buvid=XY4A57646AAC62990A45476D19D573C81F192&c_locale=zh-Hans_CN&channel=bili&disable_rcmd=0&mobi_app=android&platform=android&s_locale=zh-Hans_CN&statistics=%7B%22appId%22%3A1%2C%22platform%22%3A3%2C%22version%22%3A%226.62.0%22%2C%22abtest%22%3A%22%22%7D&ts=1647181143 盐->2653583c8873dea268ab9386918b1d65 拼接结果->appkey=783bbb7264451d82&build=6620300&buvid=XY4A57646AAC62990A45476D19D573C81F192&c_locale=zh-Hans_CN&channel=bili&disable_rcmd=0&mobi_app=android&platform=android&s_locale=zh-Hans_CN&statistics=%7B%22appId%22%3A1%2C%22platform%22%3A3%2C%22version%22%3A%226.62.0%22%2C%22abtest%22%3A%22%22%7D&ts=16471811432653583c8873dea268ab9386918b1d65
MD5加密结果
对比主动调用的结果
对比一致 基本确定为无魔改的MD5了
通过多次调用发现四次拼接并非定值 一段时间后又会变换回其他值 目前仅发现有俩组
- 560c52ccd288fed045859ed18bffd973
- 2653583c8873dea268ab9386918b1d65
至于是为啥会变动 什么条件下变动 有待考究了
感谢各位大佬观看
如有错误 还请海涵
共同进步
[完]