一、目标
分析某段子App的通讯协议,实现段子爬虫
- 某段子App版本 5.5.10
二、分析
我们上篇文章分析完了,某段子App的通讯协议流程是 请求参数→AES加密→密文做sign签名 ,所以我们需要通过frida的rpc调用Aes加解密函数和密文签名函数。
用jeb打开之前分析的 com.izuiyou.network.NetCrypto 类,可以找到目标函数
public static native byte[] decodeAES(byte[] arg0, boolean arg1) {
}
public static native byte[] encodeAES(byte[] arg0) {
}
public static native String getProtocolKey() {
}
public static native void setProtocolKey(String arg0) {
}
public static native String sign(String arg0, byte[] arg1) {
}
我们以调用encodeAES为例,encodeAES函数的参数和返回值都是byte数组,但是encodeAES的参数实际是个字符串,所以我们在python调用时传入字符串,然后在js代码中来转成byte[]类型。
返回值的处理比较麻烦,因为python和js对byte[]类型的处理可能不一样,所以我们在js代码中把byte数组转成对应hex字符串,然后再返回给python。
某段子App请求的返回值也是加密数据,解密函数由decodeAES完成,调用decodeAES的时候遇到一个问题,decodeAES函数的返回值是byte[]类型,但是实际上是String类型的明文。所以一开始我们用
var result = this.decodeAES(arg1,arg2);
var strIn = Java.use('java.lang.String');
var outStr = strIn.$new(result);
这种方式把类型转换成String,然后直接返回给python代码,结果总是异常报错,百思不得其解。 最后还是用encodeAES一样的方式来转成hex字符串来输出:
最后我们用一个python来组装获取某段子App热门数据的请求
# 获取数据
def get_data(uri, msg):
postData = dataAesEnc(msg)
# print(postData)
sign = dataSign(postData)
# print(sign)
protocolKey = dataGetKey()
# print(protocolKey)
url = uri + str(sign)
headers = { 'ZYP': 'mid=239631186',
'X-Xc-Agent' : 'av=5.5.11,dt=0',
'User-Agent': 'okhttp/3.12.2 Zuiyou/5.5.11 (Android/27)',
'X-Xc-Proto-Req' : protocolKey,
'Request-Type' : 'text/json',
'Content-Type' : 'application/xcp',
}
byte_data = bytes.fromhex(postData)
# proxies = {'http': '127.0.0.1:8888',
# 'https': '127.0.0.1:8888'
# }
try:
# ,proxies=proxies
r = requests.post(url, headers=headers, data=byte_data, verify=False,stream=True,timeout=15)
# print(r.headers['X-Xc-Proto-Res'])
# key是 cat开头的就需要把返回包里面的duck key设置进去,
if protocolKey.find('cat') == 0:
print(protocolKey)
print(r.headers['X-Xc-Proto-Res'])
dataSetKey(r.headers['X-Xc-Proto-Res'])
# 这里要做一些错误处理
bufRc = r.raw.read();
rcStr = dataAesDec(bufRc.hex())
rc = bytes.fromhex(rcStr).decode("utf-8")
return rc
except RequestException as e:
print(e)
def main():
# 手机端首页推荐地址
uri = 'http://api.izuiyou.com/index/recommend?sign='
msg = '{"filter":"all","auto":0,"tab":"推荐","direction":"down","c_types":[1,2,11,15,16,51,17,52,53,40,50,41,22,25,27],"sdk_ver":{"tt":"3.1.0.3","tx":"4.211.1081","bd":"5.86","mimo":"5.0.3","tt_aid":"5004095","tx_aid":"1106701465","bd_aid":"c8655095","mimo_aid":"2882303761518470184"},"ad_wakeup":1,"h_ua":"Mozilla\/5.0 (Linux; Android 8.1.0; Redmi 6A Build\/O11019; wv) AppleWebKit\/537.36 (KHTML, like Gecko) Version\/4.0 Chrome\/62.0.3202.84 Mobile Safari\/537.36","manufacturer":"Xiaomi","h_av":"5.5.11","h_dt":0,"h_os":27,"h_app":"zuiyou","h_model":"Redmi 6A","h_did":"866655030396869","h_nt":1,"h_m":239631186,"h_ch":"xiaomi","h_ts":1603179121590,"token":"T7K4Nnqg98_aFV9JwkfuiZtvPrRJ02EXxbnm7TXr3qiIWWaT1vjNNNCpcUu112TDw_VXu","android_id":"57b9b8465c2e440b","h_ids":{"imei2":"878739042239784","meid":"98001184062989","imei1":"878739042239776","imei":"98001184062989"},"h_os_type":"miui"}'
items = get_data(uri, msg)
print(items)
结果如下,可以成功获取到数据:
三、总结
frida rpc调用主要就是处理好参数类型,python和js的互相调用。实际运行过程中 frida不是很稳定,偶尔会崩溃退出,所以线上环境还是建议用Xposed来做rpc。
关注微信公众号,最新技术干货实时推送