本文案例是对书旗app搜索接口的请求密文进行逆向分析。
案例环境:书旗app (安卓11.5.4.152)、Frida、夜神、Jadx
@
抓包分析接口
'https://ocean.shuqireader.com/sqan/render/render/search/native?_reqid=1ce4c18035'
脱壳反编译
查壳
脱壳
可以把非常小的删除掉,把非常大的单独拿出来分析一下。我先把最大的文件(47MB)单独用Jadx打开。
data定位
看到请求内容或者响应内容加密的,就直接搜加解密关键词 decrypt和encrypt 。
另外请求中data的格式很像被base64过,然后再aes加密(猜的)。
搜完并没有发现特别合适的。(我这里调试了好一会儿,最终发现几个都不是)
于是换了个关键词,搜索表单中的 "data" 。 最后的 params.put("data", n.encrypt(jSONObject.toString()));
非常像
点进去后,可以看到encrypt相关的方法
用frida打印一下看看。
Frida hook
启动frida
HOOK代码:
import frida, sys
def on_message(message, data):
print("[%s] => %s" % (message, data))
session = frida.get_usb_device().attach('com.shuqi.controller')
js_code = '''
Java.perform(
function(){
console.log("1. start hook");
var ba = Java.use("com.shuqi.common.l");
if (ba != undefined) {
console.log("2. find class");
ba.c.implementation = function(a1){
console.log("3. hook method");
console.log(a1);
var res = ba.c(a1);
console.log(res);
return res
}
}
}
)
'''
script = session.create_script(js_code)
script.on('message', on_message)
script.load()
sys.stdin.read()
打印内容:
可以发现加密的是一些RequestParams 参数。
{
"query": "遮天3",
"page": "liteAppSearchResult",
"pagination": "{'page':'1','pageSize':'10'}",
"mod": "LIO-AN00",
"sdk": "25",
"umidtoken": "vZhLXf1LOhBiGzV9+qvRixj8ptIK2Wg+",
"utdid": "WVc0ZjZRRndmVllEQUFRQU9CUnIxY0Fy",
"first_placeid": "src1011",
"vc": "75ec",
"subVer": "sqrelease2",
"manufacturer": "HUAWEI",
"utype": "pre_vip",
"aak": "4d1fe5",
"permissionType": "3",
"net_env": "4",
"net_type": "wifi",
"sn": "1640587766920775",
"contentRecom": "1",
"net": "4",
"platform": "an",
"scene_code": "",
"enc": "182121640596695489",
"wh": "900x1600",
"brand": "HUAWEI",
"placeid": "1038",
"appVer": "11.5.4.152",
"msv": "3",
"soft_id": "1",
"sqUniqDeviceId": "ODYzMDY0NDI1NjQ0Mzky",
"personalized": "1",
"ver": "211208",
"user_id": "3120839143",
"isTeenMode": "0",
"sqSv": "1.0",
"key": "sq_app_gateway",
"sign": "b8669a96b48e6ceb07d34d7eee2df0ac"
}
经过多次测试,参数中的enc和sign还是一个动态的。
先不管这俩参数是怎么计算的,我们先看看data是如何加密的。
主要方式是 n.encrypt 。 但是跳不进去,方法在其他的dex文件中。
我把原apk丢Jadx后,找到了 com.shuqi.common.n类,并且有 encrypt方法。
再次用Frida hook分析:
import frida, sys
def on_message(message, data):
print("[%s] => %s" % (message, data))
session = frida.get_usb_device().attach('com.shuqi.controller')
js_code = '''
Java.perform(
function(){
console.log("1. start hook");
var ba = Java.use("com.shuqi.common.n");
if (ba != undefined) {
console.log("2. find class");
ba.encrypt.implementation = function(a1){
console.log("3. hook method");
console.log(a1);
var res = ba.encrypt(a1);
console.log(res);
return res
}
}
}
)
'''
script = session.create_script(js_code)
script.on('message', on_message)
script.load()
sys.stdin.read()
打印内容:
现在加解参数和结果都有了。参数和之前分析的一样,不过参数中的enc和sign是动态的。
其实到这里就可以了,用frida来调用生成密文。
快下班了,字符串加密分析暂时到这。回头再看 cqT.staticSafeEncrypt(3, "23011413", str);
Sign 分析
在全局搜了一下,发现在CommonSignUtils中。
简单看一下生成流程,是把he进行UrlEncode,he是RequestParams请求的参数,去掉cBG中的key,然后m49290a 后再md5一下。
cBG = {"sign", "key", "_public", "_reqid", "beta", "", "X-NEBULAXMLHTTPREQUEST", "callbackUrl"};
m49290a:
m49308h:
可以看到m49308h是一个md5的实现。
enc 分析
全局搜索 enc
查看 m10070br
可以看出来enc是一个由时间戳控制和计算的随机值。
_reqid 分析
请求时的params中有一个 _reqid参数。全局搜索 _reqid
此处很合理
_reqid = aBW
aBW = aBV()
参数太多了,就不再还原了,大家可自行尝试。