某知乎APP - X-Zse-96

⚠️前言⚠️

本文仅用于学术交流。
学习探讨逆向知识,欢迎私信共享学习心得。
如有侵权,联系博主删除。
请勿商用,否则后果自负。

接口网址

app 版本: 8.10.0
aHR0cHM6Ly93d3cuemhpaHUuY29tL2FwaS92NC9zZWFyY2hfdjM=

在这里插入图片描述

加密位置分析

> 老规矩,jadx打开,先全局检索一下。

在这里插入图片描述

  • 居然没有搜到任何结果,应该做了混淆
> 由于 X-Zse-96 参数存在于headers中,全局检索一下 addHeader 看有没有啥线索

在这里插入图片描述

  • 发现在添加请求头信息的时候,字段名貌似通过 H.d 做了转换,这也可能是我们搜不到关键词的原因
> frida hook H.d,通过判断 result 减少日志打印,方便我们查看
Java.perform(function () {
    var h = Java.use('com.secneo.apkwrapper.H');
    h.d.implementation = function(str){
        var result = this.d(str);
        if(result == 'X-Zse-96'){
            send('arg: ' + str);
            send('result :' + result);
        }
        return result;
    };
})

在这里插入图片描述

  • 果然是通过H.d方法转换的,那我们可以直接检索一下 G51CEEF09BA7DF27F 来看一下
> 全局检索 G51CEEF09BA7DF27F,这结果就很少了,来看一下这个 addHeader

在这里插入图片描述
在这里插入图片描述

  • 就是这里了,那 H.d(“G38CD8525”) + new String(this.f61339c.encode(encrypt)) 有可能就是我们想要的最终结果
> H.d(“G38CD8525”) 的结果是什么?我们通过 frida hook 固定参数的形式来看一下
Java.perform(function () {
    var h = Java.use('com.secneo.apkwrapper.H');
    h.d.implementation = function(str){
        str = "G38CD8525";
        var result = this.d(str);
        send('arg: ' + str);
        send('result :' + result);
        return result;
    };
})

在这里插入图片描述

  • 这不就是 X-Zse-96 的特征值嘛,那我们只需要搞定 new String(this.f61339c.encode(encrypt)) 就可以拿到加密值了

加密逻辑分析

> 首先来看一下这个 a3 是怎么生成,hook 一下这个 a 方法

在这里插入图片描述
在这里插入图片描述

  • 通过 hook 的结果可以看出它参数由4部分组成
  • 101_1_1.0+接口路径及参数+app版本号+Bearer 2.1rDb…
  • 最后一部分经多次测试也是固定不变的,所以对于我们来说唯一需要传递的参数就是 接口的路径及参数
>>这个 a 是一个单独的方法不需要hook,我们直接扣代码即可,代码如下:

在这里插入图片描述

private static String a(String str) throws Exception{
        return String.format("%032X", new BigInteger(1, MessageDigest.getInstance("MD5").digest(str.getBytes())));
    }
  • 到这里我们就得到了 a3 的值
> 下面来看一下 encrypt 是怎么生成的

在这里插入图片描述

  • 发现生成 encrypt 值的方法存在于接口类b中,那么一定会在某个地方有一个类继承了这个b,并实现了 encrypt 方法
  • 全局检索一下关键词 接口类 b 所在包名, 这就很明了了。在这里插入图片描述
  • 最终就会找到这个方法
    在这里插入图片描述
>> frida hook 一下这个a方法

在这里插入图片描述

  • 三个参数:参数1 - a3.toLowerCase().getBytes()
  • 参数 2,3 固定值,不要问为什么,一步步方法跟过来你就知道了。???? ???? ????
>> 参数搞明白之后我们再回头看这个 a 方法

在这里插入图片描述

  • 我们需要搞定三个方法就可以得到 encrypt
  • 首先 b.b, 这个方法直接扣代码就可以了
    在这里插入图片描述
    1. CryptoTool.laesEncryptByteArr,此方法存在于 native 层,可以 hook 得到
      在这里插入图片描述
    1. b.a,和 b.b 一样也是直接扣代码即可
      在这里插入图片描述
  • 至此,encrypt 值就搞定了。
> 下面进入最后一步,发现这个 encode 的方法也是一个接口类

在这里插入图片描述在这里插入图片描述

  • 同样的方法看一下这个 a 是在哪里复现的。。。这里 encrypt 传过来之后通过 j.a 生成 a2,并返回
    在这里插入图片描述
  • j.a 其实就是做了一个 Base64 加密
    在这里插入图片描述
  • 至此,X-Zse-96 加密逻辑就全部分析结束了。

Xposed hook

关键代码:  这里只保留关键性逻辑
@Action("x-zse-96")
public class ZhihuHandler implements RequestHandler {
    @AutoBind
    private String p1; // 以base64的形式传入
    
    @Override
    public void handleRequest(SekiroRequest sekiroRequest, SekiroResponse sekiroResponse) {
        try {
            log("进来了。。。 开始生成 x_zse_96。。。");
            String p1_decode = new String(Base64.decode(p1, 2));
            log(p1_decode);
            // System.out.println("******************helloword*****************************");
            StringBuilder sb = new StringBuilder();
            String temp_str = "101_1_1.0+" + p1_decode + "+8.10.0+Bearer 2.1...";
            sb.append(temp_str);
            String result = sb.toString().substring(0, sb.length() - 1);
            String a3 = a(result);
            byte[] barr_a3 = a3.toLowerCase().getBytes();
            // 生成变量 encrypt   b.b(CryptoTool.laesEncryptByteArr(b.a(bArr, str, bArr2), str, bArr2), str, bArr2);
            String arg2_str = "541a3a5896...";
            byte[] arg3_barr = new byte[]{ ... };
            byte[] result_ba = b_a(barr_a3, arg2_str, arg3_barr);
            // hook so层方法 aesEncryptByteArr
            final Class<?> clazz = XposedHelpers.findClass("com.bangcle.CryptoTool", HookZhihu.loadPackageParam.classLoader);
            // 注意标明返回值类型 (byte[])  静态方法可以直接使用类名调用
            byte[] result_CryptoTool = (byte[]) XposedHelpers.callStaticMethod(clazz,"laesEncryptByteArr",new Object[]{result_ba,arg2_str,arg3_barr});
            byte[] encrypt = b_b(result_CryptoTool, arg2_str, arg3_barr);
            String x_zse_96 = "1.0_" + new String(Base64.encodeToString(encrypt, 2).getBytes());
            sekiroResponse.success(x_zse_96);
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    // throws Exception 表示的是本方法不处理异常,交给被调用处处理
    private static String a(String str) throws Exception{
        return String.format("%032X", new BigInteger(1, MessageDigest.getInstance("MD5").digest(str.getBytes())));
    }

    public static byte[] b_a(byte[] bArr, String str, byte[] bArr2) {
       	...
    }

    public static byte[] b_b(byte[] bArr, String str, byte[] bArr2) {
        ...
     }
}

最终成果展示

在这里插入图片描述

上一篇:MemFire解决方案-物联网数据平台解决方案


下一篇:iOS 模拟请求 (本地数据调试)