【安卓逆向】某打折app api_sign分析

本次分析的案例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

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值