一、目标

一、目标

分析某段子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。

run.py
@app.route('/aesenc', methods=['POST']) # 数据加密
def zy_aesenc():
    global gScript

    data = request.get_data()
    print(data.decode("utf-8"))

    res = gScript.exports.callencaes(data.decode("utf-8"))
	return res
hook.js
function callEncodeAes(strData){
	var result = 'null';

	Java.perform(function() {
		var ZYNetCrypto = Java.use("com.izuiyou.network.NetCrypto");

        var strIn = Java.use('java.lang.String');
        var byteIn = strIn.$new(strData).getBytes();

		var res = ZYNetCrypto.encodeAES(byteIn);

        result =  Bytes2HexString(res);

    });

	return result;
}

某段子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字符串来输出:

run.py
@app.route('/aesdec', methods=['POST']) # 数据解密
def zy_aesdec():
    global gScript

    data = request.get_data()
    res = gScript.exports.calldecaes(data.decode("utf-8"))
    return res
hook.js
// 解密
function callDecodeAes(dataBuf){
	var rc = 'null';

	var arr = HexString2Bytes(dataBuf);

    Java.perform(function() {
        var ZYNetCrypto = Java.use("com.izuiyou.network.NetCrypto");
		var res = ZYNetCrypto.decodeAES(arr,true);
		rc = Bytes2HexString(res);
    });

	return rc;
}

最后我们用一个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)

结果如下,可以成功获取到数据:

rc
1:bashrc

三、总结

frida rpc调用主要就是处理好参数类型,python和js的互相调用。实际运行过程中 frida不是很稳定,偶尔会崩溃退出,所以线上环境还是建议用Xposed来做rpc。

100

关注微信公众号,最新技术干货实时推送

100