本次分析的案例app 是5ZSv5ZOB5LyadjcuNDUuNg==
(base64解码)
抓个包,请求头中有个api_sign
,该字段就是本次分析的对象。
1.jadx 静态分析
直接搜索关键词:api_sign=
跟进去,来到这里。
这里用到了VCSPSecurityBasicService.apiSign()
方法,双击两下,找到函数的定义地方。
这里引用到的方法是VCSPSecurityConfig.getMapParamsSign()
,双击两下,找到函数的定义。
这里在该方法return 返回时调用getSignHash()
方法。
继续跟进getSignHash()
上面的方法返回时调用了gs()
方法
继续跟进
这里稍微要注意下,gs()函数有一步initInstance()
初始化的操作,之后通过clazz.getMethod()
方法获取到"gs"函数并加以调用,刚开始我都没有注意到这点,迷了半天。
跟进gs()方法看一看,它又调用到了gsNav()
方法,而且还是一个native
方法,用到的so是libkeyinfo.so
。
可真够绕的,找了大半天 才找到真正的函数调用入口。
2.ida静态分析
把libkeyinfo.so
拖到ida打开,看一看。
在导出表搜索"java_"
,可以看到有对应的方法,没错这也是一个静态注册的函数。
这里我们用的是Java_com_vip_vcsp_KeyInfo_gsNav()
函数,双击两下,跟进去。
返回的值是v9,该值由j_Functions_gs()
方法计算出来,跟进去。
Functions_gs(),我再跟进去。
Functions_gs()
方法的计算就比较长了,拉倒底部,我们只看最核心的代码。
可以看到if ( j_getByteHash(v5, v6, (int)&v63, v45, (int)&v64, 256) )
有一个if判断
感觉快跟到头了。
点进去
再进去
看关键字,
j_SHA1Reset((int)v19);
j_SHA1Input(v19, v5, v6);
结合返回加密字段api_sign长度也是40位的,可以大胆的猜测这里用到的是sha1
加密算法。
so层的分析也是够绕的,快吐了。
3.hook frida动态分析
运行命令:
frida -U com.achievo.vipshop -l hook_vip_so.js
function main() {
Java.perform(function () {
function mapToString(hash_map) {
var result = "";
var keyset = hash_map.keySet();
var it = keyset.iterator();
while (it.hasNext()) {
var keystr = it.next().toString();
var valuestr = hash_map.get(keystr).toString();
result += keystr +"="+valuestr+"&";
}
return result.substring(0, result.length - 1);
}
var KeyInfo = Java.use("com.vip.vcsp.KeyInfo");
KeyInfo["gs"].implementation = function (context, map, str, z) {
console.log("↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓");
console.log('gs is called');
console.log('context: ' + context);
console.log('map: ' + map);
console.log('map_string: ' + mapToString(map));
console.log('str: ' + str);
console.log('z: ' + z);
var ret = this.gs(context, map, str, z);
console.log('gs ret value is ' + ret);
console.log("↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑");
return ret;
};
var native_func = Module.findExportByName(
"libkeyinfo.so", "getByteHash"
);
Interceptor.attach(native_func, {
onEnter: function (args) {
console.log("↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓");
console.log();
// console.log('args[0]: ', hexdump(args[0]));
console.log('args[0]: ', parseInt(args[0]));
// console.log('args[1]: ', hexdump(args[1]));
console.log('args[1]: ', parseInt(args[1]));
console.log('args[2]: ', Memory.readCString(args[2]));
// console.log('args[3]: ', Memory.readCString(args[3]));
// console.log('args[4]: ', Memory.readCString(args[4]));
},
onLeave: function (return_val) {
console.log('return_val: ', Memory.readCString(return_val));
console.log("↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑");
}
});
});
}
setImmediate(main);
输出结果
入参2是个map
,正常打印出来是[object Object]
,这里我们把它转换成string,可以看到它其实就是把请求参数的key和value拼接起来。
再hook 下so层
仔细对比,会发现计算一个sign结果,会调用两次sha1加密,并且每次都会在头部拼接一个固定盐值a84c5883206309ad076deea939e850dc
4.python算法还原
py代码就不贴了
可以看到frida hook出来的和python计算的一样,证明正确。
完事,撤退,水文一篇。
参考文章:
https://www.qinless.com/139