新氧APP8.24.2接口签名sign和xy_sign分析


0x01、 目标需求:

  .a) 新氧APP的接口参数以及相关的签名获取的方式

  .b) 需要获取所有城市下的分类店铺等数据

0x02、分析背景:

  .a) 版本8.24.2(目前最新版)

  .b) 软件无壳

  .c) 软件通讯过程中采取了签名验证的方式,

0x03、分析流程:

  .a) 通过数据包抓取或敏感函数hook方式获得接口功能

  .b) 通过函数内部参数的组装继续分析参数的来源以及加密的流程(校验 _sign 和 xy_sign)

签名校验

0x04、分析开始:

  .a) jadx-gui 分析签名

    0x001. 打开jadx-gui 直接搜索关键词_sign:

分析签名代码



    0x002. 打开jadx-gui 直接搜索关键词_sign 我对第一个结果比较感兴趣点进去看看(两个sing都在这里,传入的map就是url中的参数):

分析sign和xy_sign

        if (z) {
            treeMap.put("xy_sign", paramsNewSign(paramsMd5(INetWorkCommonParasm.getParamsString(treeMap))));
            treeMap.put("_sign", paramsSign(INetWorkCommonParasm.getParamsString(treeMap)));
        }


    0x003. xy_sign是有MD5的处理,_sign是没有的跟到INetWorkCommonParasm.getParamsString这个函数看一下是如何处理map的(这里是直接把map转成了url的key=value&key=value的格式,其中如果key=adinfo的话value进行url解码):

    @NonNull
    public static String getParamsString(Map<String, String> map) {
        if (map == null) {
            return "";
        }
        StringBuilder sb = new StringBuilder();
        Iterator<Map.Entry<String, String>> it = map.entrySet().iterator();
        while (it.hasNext()) {
            Map.Entry<String, String> next = it.next();
            String key = next.getKey();
            String value = next.getValue();
            if (!TextUtils.isEmpty(value)) {
                if ("adinfo".equalsIgnoreCase(key)) {
                    value = URLDecoder.decode(value);
                }
                sb.append("&");
                sb.append(key);
                sb.append(ContainerUtils.KEY_VALUE_DELIMITER);
                sb.append(value);
            } else {
                it.remove();
                map.remove(key);
            }
        }
        return sb.substring(1);
    }


    0x004. 这里还需要看一下paramsNewSign的函数是如何处理的(主要的转码函数如下,然后再进行Base64编码):

public byte[] basesec_EncDataToBinary(String str, int i) throws Exception {
        if (this.ctx == null) {
            throw new NullPointerException("not init yet");
        } else if (str == null || "".equals(str)) {
            throw new NullPointerException("input data is null or zero length");
        } else {
            try {
                byte[] bytes = str.getBytes("UTF-8");
                byte[] bArr = new byte[(bytes.length + 7)];
                int length = bytes.length + 12;
                byte[] bArr2 = new byte[length];
                int[] iArr = {bytes.length + 12};
                setVersion(bArr, 1);
                setCallNum(bArr, 1);
                setOpt(bArr, (byte) (i & 255));
                setData(bArr, bytes);
                int o0o = o0o(this.ctx, bArr, bArr2, iArr); //如果参数2,3,4没有被修改,这行代码将毫无意义
                if (o0o != 0 || length < iArr[0]) {
                    throw new OException(Integer.toBinaryString(-o0o));
                }
                byte[] bArr3 = new byte[iArr[0]];
                System.arraycopy(bArr2, 0, bArr3, 0, iArr[0]);
                return bArr3;
            } catch (Exception unused) {
                throw new OException("in data can not getbytes(UTF-8)");
            }
        }
    }
  .b) 编写测试代码测试(如果不着急可以还原这个加密算法,如果着急可以通过hook的方式去调用它的函数来获取返回值).

    0x001. 这里hook到了加密的参数,继续往下追生成参数的地方:

调试参数生成



这里是生成参数的调用地方



    0x002. 这里hook到了加密的参数,继续往下追生成参数的地方(xy_sign调用的是paramsNewSign , _sign调用的是paramsSign)xy_sign的加密先使用md5 然后传入给paramsNewSign函数:

调试还原算法



    0x003. 调试测试数据的算法(v1: b733a2707835e008bfc8511511999feb , res: KSEeNJPfEA6GdaapPAvWjw%3D%3D)我们只要把v1的值带入还原函数返回res的值就证明我们成功了:

      0x0001. hook Navite函数 传入了2个byte数组1个int数组,在so内做了运算,改变倒数第二个的byte数组内容 只有前16位是有东西的, 然后在java内有一个System.copyArray函数 取的就是最后一个int数组的最终结果也就是16,最终结果是中间的后面全是0的数组的前16位然后base64编码在url编码

hook调用参数



      0x0002. 由于时间 问题,大家可以自己还原一下算法,本文中直接采用unidbg 或者 提取so文件直接调用该函数,未完待续,后面会补充算法的实现过程.经测试,数据一致,如下图(v1: b733a2707835e008bfc8511511999feb , res: KSEeNJPfEA6GdaapPAvWjw%3D%3D):

验证结果



      0x0004. 其主要汇编代码如下:


.text:0000EACC                 PUSH            {R4-R7,LR}
.text:0000EACE                 ADD             R7, SP, #0xC
.text:0000EAD0                 PUSH.W          {R8,R9,R11}
.text:0000EAD4                 SUB             SP, SP, #0x90
.text:0000EAD6                 MOV             R4, R0
.text:0000EAD8                 LDR             R0, =(__stack_chk_guard_ptr - 0xEADE)
.text:0000EADA                 ADD             R0, PC  ; __stack_chk_guard_ptr
.text:0000EADC                 LDR             R6, [R0] ; __stack_chk_guard
.text:0000EADE                 LDR             R0, [R6]
.text:0000EAE0                 STR             R0, [SP,#0xA8+var_1C]
.text:0000EAE2                 CBZ             R4, loc_EB18
.text:0000EAE4                 MOV             R5, R1
.text:0000EAE6                 CBZ             R1, loc_EB18
.text:0000EAE8                 CBZ             R2, loc_EB18
.text:0000EAEA                 CBZ             R3, loc_EB18
.text:0000EAEC                 LDRB            R0, [R5]
.text:0000EAEE                 CMP             R0, #1
.text:0000EAF0                 BNE             loc_EB28
.text:0000EAF2                 LDR.W           R0, [R5,#3]
.text:0000EAF6                 REV.W           R9, R0
.text:0000EAFA                 LDRB            R0, [R5,#1]
.text:0000EAFC                 CMP             R0, #1
.text:0000EAFE                 BEQ             loc_EB38
.text:0000EB00                 CBNZ            R0, loc_EB58
.text:0000EB02                 CMP.W           R9, #0x81
.text:0000EB06                 BLT             loc_EB74
.text:0000EB08                 LDR             R0, [R6]
.text:0000EB0A                 LDR             R1, [SP,#0xA8+var_1C]
.text:0000EB0C                 SUBS            R0, R0, R1
.text:0000EB0E                 BNE             loc_EBB2
.text:0000EB10                 MOVS            R0, #0
.text:0000EB12                 MOVS            R1, #4
.text:0000EB14                 MOVS            R2, #3
.text:0000EB16                 B               loc_EB66








友情提示:本文只为技术分享交流,请勿非法用途.产生一切法律问题与本人无关.
本文中所有的调试代码均在gitee仓库中(包含很多app的关键点的hook代码) Git公开仓库地址 欢迎star or fork



在浏览的同时希望给予作者打赏,来支持作者的服务器维护费用.一分也是爱~