⚠️前言⚠️
本文仅用于学术交流。
学习探讨逆向知识,欢迎私信共享学习心得。
如有侵权,联系博主删除。
请勿商用,否则后果自负。
接口网址
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, 这个方法直接扣代码就可以了
-
- CryptoTool.laesEncryptByteArr,此方法存在于 native 层,可以 hook 得到
- CryptoTool.laesEncryptByteArr,此方法存在于 native 层,可以 hook 得到
-
- b.a,和 b.b 一样也是直接扣代码即可
- 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) {
...
}
}