mPaaS是阿里研发的一个移动开发平台,其中移动网关服务(Mobile Gateway Service,简称 MGS)作为 mPaas 最重要的组件之一,连接了移动客户端与服务端,简化了移动端与服务端的数据协议和通讯协议,从而能够显著提升开发效率和网络通讯效率。通过RPC组件进行网络请求,并且可以设置拦截器拦截请求的发送数据和返回结果。
实例分析
首先对使用了mpaas进行网络请求的应用进行抓包发现请求的body
和返回的body
都被加密无法正常解析。
对应的接口为/otsmobile/app/mgs/mgw.htm
搜索此字符串可以看到其对应mobilegw.url
找到mobilegw.url
的引用处为函数getGWFURL
对getGWFURL
进行hook并打印调用栈可以看到http请求相关的方法名addRequestHeaders
和addCokie2Header
,所以HttpWorker.call
应该是组包的位置
查看HttpWorker.call
可以看到其在调用了addRequestHeaders
之后会继续调用executeRequest
进行网络请求
executeRequest
内部会调用executeHttpClientRequest
executeHttpClientRequest
中会调用getPostData
先获取需要post的数据
getPostData
会先调用getEncryptedEntity
将需要post的数据进行加密
getEncryptedEntity
会调用clientRpcPack.encrypt
将数据加密
hook一下clientRpcPack.encrypt
可以看到明文数据和密文数据,密文数据与抓包的数据相同
clientRpcPack.encrypt
内部会先将明文数据进行GZip
压缩,然后调用encode
函数,传入一个16个字节的随机字符串,GZip压缩后的数据和一个encrypt_index
encode
是一个jni函数,对应在libmpaas_crypto.so
的Java_com_alipay_mobile_common_mpaas_1crypto_Client_encode
函数
Java_com_alipay_mobile_common_mpaas_1crypto_Client_encode
会调用fake_island::client::encode
并返回三个buffer,最后将这三个buffer的数据返回
hook fake_island::client::encode
看一下他的参数,分别是传入的随机字符串,随机字符串长度,GZip压缩后的数据和长度,以及一个encrypt_index
,并且encrypt_index
为1
fake_island::client::encode
内部会根据encrypt_index
来选择具体的加密算法,这里encrypt_index
为1,所以选择的是xchg_ecc
ECC椭圆双曲线算法会根据传入的公钥和随机字符串生成rQ
和rG
在xchg_ecc
生成rQ
和rG
的时候需要首先调用EVP_PKEY_get0_EC_KEY
从EVP_PKEY
结构中得到EC_KEY
并进一步得到公钥,而EVP_PKEY
结构对应的就是xchg_ecc
第一个参数(这里ida不知道怎么回事优化没了),而这个参数是在jni
函数入口时从handle
字段中拿到的
通过分析找到是在调用Java_com_alipay_mobile_common_mpaas_1crypto_Client_init
函数是传入了一个公钥,并且初始化了EVP_PKEY
结构并保存在了handle
字段中
xchg_ecc
返回会会利用rQ
对传入的GZip
压缩数据再次进行aes128_cbc
加密
通过调用fake_island::cbc_128::initialize
生成aes128_cbc
加密的key
和iv
,当rQ
的长度大于0xf
时key就是将rQ
以16个字节为一组进行异或的结果。而iv则为rQ
剩余的字节,不足16个字节的部分用 0x10-剩余字节的个数 进行填充。
hook一下查看key和iv具体生成的过程,xchg_ecc
生成的rQ
为02 4b 97 21 9e dc 0c 91 d5 ff 2c 6a db 32 6c 0b 39 1a a3 db f6 f0 63 85 7e aa a6 e0 06 98 39 04 74
,02 4b 97 21 9e dc 0c 91 d5 ff 2c 6a db 32 6c 0b
异或39 1a a3 db f6 f0 63 85 7e aa a6 e0 06 98 39 04
得到aes的key为3b 51 34 fa 68 2c 6f 14 ab 55 8a 8a dd aa 55 0f
,而rQ还剩余一个字节为74
,所以aes的iv为74 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f
这里aes 的key是通过其会调用fake_island::aes_128_cbc_en::key_128
函数确定的,而为什么this + 8
位置保存的是iv呢,这个是因为其后面会调用update
对16个字节为一组的数据进行具体的加密操作,而加密之前会先将this + 8
位置的16个字节先和状态矩阵进行异或,所以其作用就是一个iv
。
hook一下 fake_island::client::encode
查看调用完成后返回的三个buffer分别存放的是rG
, 加密的参数
,rQ
Java_com_alipay_mobile_common_mpaas_1crypto_Client_encode
返回后会将rG
保存在this.e
中,将rQ
和加密的参数
传入函数a再次进行格式化
函数a会分别将encrypt_index
,rG的长度
,rG
,SYMMETRIC_ENCRYPT_RMK
,加密的请求参数的长度
,加密的请求参数
格式化到缓冲区
具体的格式就是encrypt_index
和SYMMETRIC_ENCRYPT_RMK
占一个字节,而rG的长度
和加密的请求参数的长度
占3个字节,具体格式如下。
这样服务器拿到rG
后通过私钥得到rQ
(ECC算法),然后对加密的请求参数进行解密。然后将需要返回的数据通过相同的方法进行加密,那客户端在接收到返回数据时直接用事先保存的rQ
进行解密即可。