对oppo健康研究app中的睡眠数据API的逆向研究
为什么弄
因为它本身oppo健康那个app里面的数据同步机制很奇怪,BLE也没有数据,抓包也没有。所以我在乱翻的时候看到了它可以授权给一健康研究的应用,里面有睡眠数据。
为此,我准备了该应用的.apk
安装包和一个通过Fiddler抓取的.saz
网络请求样本文件。
模拟器不要用WSA,这种应用会闪退,而且WSA已经是微软的弃子的
这里抓包我是先用的Mumu模拟器,然后Fidder抓。证书只需要PC安装了就行。然后你抓到的东西右键可以导出saz格式。
如上。我导出了.saz
文件,然后用bandzip进行了解压。
它其实本身就是一个压缩包,里面有一个raw
目录。
raw
目录包含了HTTP/HTTPS请求和响应。
通过在这些文本文件中搜索关键词sleep
,我找到了一个API端点:
POST https://health-researchkit-cn.heytapmobi.com/researchkit_api/api/sleep/v2/getStageByDate
但是,请求和响应的Content-Type
均为application/encrypted-json
,其内容主体(Body)是一串乱码,显然是经过加密的。
所以,反编译吧
我用Jadx对.apk
文件进行反编译,得到了混淆后的Java源代码。
我在所有反编译出的代码中全局搜索 X-Protocol
得到了一个关键类:com.platform.usercenter.network.interceptor.SecurityRequestInterceptor
。
这个类负责处理请求的加密和响应的解密。
并且这是一个动态的、基于RSA和AES的混合加密方案。
以下是简述
-
AES密钥生成:在每次API请求前,客户端在内存中随机生成一个16字节的AES密钥和16字节的AES初始化向量(IV)。这对密钥和IV仅用于本次请求。
-
RSA公钥加密:客户端代码中硬编码了一个RSA公钥。这个公钥用于加密上一步生成的AES密钥。
RsaCoder.java
类中有这个公钥的Base64字符串:
public static String getKey() { return "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDpgSW5VkZ6/xvh+wMXezrOokNdiupuvuMj4RVJy44byWDupl4H37z907A26RVdFzMeyLUQB4rsDIaXdxCODlljWW+/K96uF5MsDtOFUBw7VlOclIjcYTv/YDQEul8JoXoOuy1Yf3b5sbTpTuVTcl97tAuLJ8PoGe2K7N3B1eUQqQIDAQAB"; }
-
构造
X-Protocol
头:这个请求头的值是一个经过URL编码的JSON字符串,其结构如下:{ "key": "Base64编码的、被RSA加密后的AES密钥", "iv": "Base64编码的、明文的AES IV", "sessionTicket": "" }
-
请求体加密:原始的JSON请求体(例如:
{"date":"YYYY-MM-DD", "projectId":"..."}
)使用步骤1中生成的随机AES密钥和IV,通过AES/CBC/PKCS5Padding
算法进行加密。 -
响应体解密:服务器使用相同的AES密钥和IV加密响应数据。客户端在收到响应后,用内存中同一份AES密钥和IV进行解密。
嗯。很有信心,开测吧。
掌握了加密协议后,那就是测试了。
但是不太妙。
在测试中遇到了decrypt client secret key error
的错误。这个错误意味着虽然我的加密逻辑和请求格式是正确的(服务器返回了200 OK
),但服务器无法用它的私钥解密X-Protocol
头。
所以我又去找 Auth Token和 Project ID。
得到的结果是Auth Token是动态的。
然后Project ID是通过一个回调接口拿的。
我就不继续了,放弃了。因为再往下太复杂了。
可以说oppo还是挺...我是该夸他很安全吗?