之前转载过该app的文章,今天翻版重新整理下,版本号:576O5Zu+56eA56eAYXBwIHY5MDgw
(base64 解码)。
上来先抓个包:
jadx搜索关键词 "sigTime"
,然后定位到这里
看这行代码 cVar.addForm(INoCaptchaComponent.sig, generatorSig.sig);
,看下INoCaptchaComponent.sig
其实就是 "sig"
,感觉是这里大差不差。
sig 是由 generatorSig.sig
计算出来的,跟进去。
这里是定义了一个 静态 SigEntity
,看下大概的功能就是做下参数拼接,底部是调用nativeGeneratorSigOld(str, bArr, str2)
方法,继续跟进去。
这里最终是调用了public static native SigEntity nativeGeneratorSigOld(String str, byte[][] bArr, String str2)
方法,是一个native方法,用到的so是release_sig
。
用ida pro 打开这个so 看看,继续跟进。
先看看导出表
搜索"java_"
,可以看到java层注册的函数名,没错是个静态注册的,而且没有什么混淆。
先看 Java_com_meitu_secret_SigEntity_nativeGeneratorSig
函数
先看下 v10 = ValidateKey::getValidateResult()
大概就是检验是否合规的key
进来这里调用了 JavaHelper::getAndroidAPKKeyHash
函数,看起来像是校验 apk 签名
然后再看下 GeneratorSIG
函数,看字段命名 就像是,瞎猜的。
刚开始掉用了一个 GetSecretKey
函数,获取 key,接着是拼接一些不明的字符串,最后调用了 MD5_Calculate
计算函数。
着重分析下这个
这个MD5_Calculate
函数 重要的是由三部分组成 MD5_Init
, MD5_Update
, MD5_Final
。
init 这里 全都是MD5标准的常量
最核心的加密都是在MD5_Update
里面。
静态分析到此结束,开始上 frida 动态调试下。
先hook下 com.meitu.secret.SigEntity
和_Z13MD5_CalculatePKcjPc
函数看看。
hook js 代码:
setImmediate(function () {
if (Java.available) {
Java.perform(function () {
var SigEntity = Java.use("com.meitu.secret.SigEntity");
// SigEntity.generatorSig.overload('java.lang.String', '[Ljava.lang.String;', 'java.lang.String').implementation = function (data1, data2, data3) {
// console.log("↓↓↓↓↓↓↓↓↓↓↓↓传递3个参数↓↓↓↓↓↓↓↓↓↓↓↓");
// console.log("SigEntityA-参数1:" + data1);
// console.log("SigEntityA-参数2:" + data2);
// console.log("SigEntityA-参数3:" + data2);
// var res1 = this.generatorSig(data1, data2, data3);
// console.log("SigEntityA-结果:" + res1.sig.value);
// console.log("↑↑↑↑↑↑↑↑↑↑↑↑传递3个参数↑↑↑↑↑↑↑↑↑↑↑↑");
// return res1;
// };
SigEntity.generatorSig.overload('java.lang.String', '[Ljava.lang.String;', 'java.lang.String', 'java.lang.Object').implementation = function (data1, data2, data3, data4) {
console.log("↓↓↓↓↓↓↓↓↓↓↓↓传递四个参数↓↓↓↓↓↓↓↓↓↓↓↓");
console.log("SigEntityB-参数1:" + data1);
console.log("SigEntityB-参数2:" + data2);
console.log("SigEntityB-参数3:" + data3);
console.log("SigEntityB-参数4:" + data4);
var res2 = this.generatorSig(data1, data2, data3, data4);
console.log("SigEntityB-结果:" + res2.sig.value);
console.log("↑↑↑↑↑↑↑↑↑↑↑↑传递四个参数↑↑↑↑↑↑↑↑↑↑↑↑");
return res2
};
SigEntity.generatorSigWithFinal.implementation = function (data1, data2, data3, data4) {
console.log('generatorSigWithFinal', data1, data2, data3, data4);
var result = this.generatorSigWithFinal(data1, data2, data3, data4);
console.log("generatorSigWithFinal", result.sig.value, result.sigTime.value, result.sigVersion.value);
return result;
};
var MD5_Calculate = Module.findExportByName("librelease_sig.so", "_Z13MD5_CalculatePKcjPc");
if (MD5_Calculate !== null) {
Interceptor.attach(MD5_Calculate, {
onEnter: function (args) {
console.log("so层入参-参数:\n", args[0], args[1].toInt32(), args[2]);
// var byteArray = args[0].readByteArray(args[1].toInt32());
// console.log("so层入参-byteArray:\n", byteArray);
var bufferData = Memory.readByteArray(args[0], args[1].toInt32());
console.log("so层入参-byteArray:\n", bufferData);
// var hexdata = hexdump(byteArray, {
// ansi: true,
// length: args[1].toInt32()
// });
// console.log("打印16进制数据-hexdata:\n", hexdata);
this.retbuff = args[2];
// console.log("打印retbuff-hexdata:", this.retbuff);
},
onLeave: function (args) {
var ret = this.retbuff.readCString();
console.log("so层加密", ret);
// console.log("so层加密args", args);
}
});
}
});
}
});
打印下看看
把MD5_Calculate
方法的入参参数2 用hexdump
转换成正常的字符串,在md5加密下,是cb67c671be0f0de69cfa310007eb146c
,果然和hook出来的so层加密是一样的。
但是会发现 so层加密 cb67c671be0f0de69cfa310007eb146c
和 SigEntityB-结果:bc766c17ebf0d06ec9af130070be41c6
值不一样,仔细对比会发现他们是相邻的两个字符互换得来。
把_Z13MD5_CalculatePKcjPc
函数的入参参数 和抓包拿到的请求参数对比,我们会发现,它其实是把get请求后面的参数,先排序,然后再拼接,在拼接的过程中各拼接了2个盐值(xxxxxx 手动马赛克),最后MD5计算出来的。
最后 最后当然是 python 算法还原了。
看看效果:
水文一篇,再接再续。