欢迎访问 生活随笔!

生活随笔

当前位置: 首页 > 编程资源 > 编程问答 >内容正文

编程问答

美图秀秀 sig参数分析

发布时间:2024/4/11 编程问答 64 豆豆
生活随笔 收集整理的这篇文章主要介绍了 美图秀秀 sig参数分析 小编觉得挺不错的,现在分享给大家,帮大家做个参考.

转载一篇好的博客,原始链接 https://mp.weixin.qq.com/s/5kUDmlPvIOw-6mdzywvyFA

推荐一波,这是博主的网站:https://www.qinless.com/ 有兴趣看看,多学习学习。

今天我们要研究的app是美图秀秀,版本号:v9080,下载链接:https://www.wandoujia.com/apps/37577/history_v9080

同系列文章推荐下:
1.聚美app之 _sign参数分析
2.大润发优鲜app之paramsMD5参数分析
3.美图秀秀 sig参数分析
4.贝壳app Authorization参数分析
5.豆瓣app sig参数分析
6.半次元app之data参数分析

转载请注明出处:
https://blog.csdn.net/weixin_38819889/article/details/122307928?spm=1001.2014.3001.5501

美图秀秀请求接口中有一个sig参数加密

1.来我们先抓个包:

2.java 层分析

「直接全局搜索 sig 关键词即可」

最终是定位到了这个函数 com.meitu.secret.SigEntity.generatorSig

调用 SigEntity.nativeGeneratorSig 执行加密逻辑。

该 native 函数在 librelease_sig.so 文件里

3.so 分析

「librelease_sig so 文件都是静态注册的函数,也没啥对抗分析,打开即可分析」


打开 so 可以看到符号都没混淆,逻辑还是比较清晰的。先来看看 ValidateKey::getValidateResult 函数逻辑。

进来这里调用了 JavaHelper::getAndroidAPKKeyHash 函数,看起来像是校验 apk 签名。

点进来逻辑很清晰一眼望去,确实是获取签名。下面再来看 GeneratorSIG sig 生成函数。

刚开始掉用了一个 GetSecretKey 函数,获取 key,接着是拼接一些不明的字符串,最后调用了 MD5_Calculate 计算函数。着重分析下这个。

md5 init update final 函数一应俱全,应该是标准的。


点进 init 函数,这些常量确实是标准的

4.frida hook

hook java 函数

var SigEntity = Java.use("com.meitu.secret.SigEntity"); SigEntity.generatorSig.overload('java.lang.String', '[Ljava.lang.String;', 'java.lang.String', 'java.lang.Object').implementation = function (a, b, c, d) {printLog('SigEntity.generatorSig.a: ', a);printLog('SigEntity.generatorSig.b: ', b);printLog('SigEntity.generatorSig.c: ', c);printLog('SigEntity.generatorSig.d: ', d);var res = this.generatorSig(a, b, c, d);printLog('SigEntity.generatorSig.res: ', res.sig.value);return res; }

hook so 函数
「里面的一部分 hook 逻辑,在后面使用 unidbg 跑 so 时会用的」

var soAddress = Module.findBaseAddress("librelease_sig.so"); console.log('soAddress: ', soAddress);var sub_6794 = soAddress.add(0x6794 + 1); console.log('sub_6794: ', sub_6794);Interceptor.attach(sub_6794, {onEnter: function (args) {console.log('\nsub_6794 onEnter r0: ', this.context.r0);},onLeave: function (retval) {} })var sub_6790 = soAddress.add(0x6790 + 1); console.log('sub_6790: ', sub_6790);Interceptor.attach(sub_6790, {onEnter: function (args) {console.log('\nsub_6790 onEnter r0: ', hexdump(this.context.r0, {length: 64, header: false}));console.log('\nsub_6790 onEnter r1: ', hexdump(this.context.r1, {length: 64, header: false}));},onLeave: function (retval) {} })var sub_6730 = soAddress.add(0x6730 + 1); console.log('sub_6730: ', sub_6730);Interceptor.attach(sub_6730, {onEnter: function (args) {console.log('\nsub_6730 onEnter r0: ', hexdump(this.context.r0, {length: 64, header: false}));},onLeave: function (retval) {} })var sub_4A68 = soAddress.add(0x4A68 + 1); console.log('sub_4A68: ', sub_4A68);Interceptor.attach(sub_4A68, {onEnter: function (args) {console.log('\nmd5_update onEnter r0: ', hexdump(args[0], {length: 32, header: false}));console.log('\nmd5_update onEnter r1: ', hexdump(args[1], {length: args[2].toInt32(), header: false}));console.log('\nmd5_update onEnter r2: ', args[2].toInt32());console.log('\nmd5_update onEnter r3: ', args[3]);},onLeave: function (retval) {} })

frida hook 命令跑起来,把结果保存到文件中,generatorSig 函数入参, 参数比较简单,也就是请求的一些参数。

so md5 update 函数

hook 结果 md5 update 函数一共调用了三次。这对 md5 算法有了解的小伙伴就会看的出来,只有第一次是真正的数据,后面两次全是填充的数据

「Tips: 不了解的可以看看之前分享的密码学文章:https://www.qinless.com/1246」

最终结果:

就是这个 390b028d37b588d2b4380c4aa215be72, 我们来加密下看看是否相同。


最终加密结果相同。

「下面分享下怎么使用 unidbg 跑起来」

5.unidbg

老规矩,先搭架子

package com.xiayu.meituxiuxiu;import com.github.unidbg.AndroidEmulator; import com.github.unidbg.Emulator; import com.github.unidbg.Module; import com.github.unidbg.debugger.BreakPointCallback; import com.github.unidbg.debugger.Debugger; import com.github.unidbg.debugger.DebuggerType; import com.github.unidbg.linux.android.AndroidEmulatorBuilder; import com.github.unidbg.linux.android.AndroidResolver; import com.github.unidbg.linux.android.dvm.*; import com.github.unidbg.linux.android.dvm.array.ArrayObject; import com.github.unidbg.linux.android.dvm.array.ByteArray; import com.github.unidbg.memory.Memory; import unicorn.ArmConst;import java.io.File; import java.io.IOException; import java.security.MessageDigest;public class GetSig_v9080Test extends AbstractJni {private final AndroidEmulator emulator;private final Module module;private final VM vm;public String apkPath = "/Volumes/T7/android/android-file/meituxiuxiu-9.0.8.0.apk";public String soPath2 = "unidbg-android/src/test/resources/test_so/meituxiuxiu/librelease_sig-9.0.8.0.so";GetSig_v9080Test() {emulator = AndroidEmulatorBuilder.for32Bit().build();final Memory memory = emulator.getMemory();memory.setLibraryResolver(new AndroidResolver(23));vm = emulator.createDalvikVM(new File(apkPath));vm.setVerbose(true);vm.setJni(this);DalvikModule dm2 = vm.loadLibrary(new File(soPath2), true);dm2.callJNI_OnLoad(emulator);module = dm2.getModule();}public static void main(String[] args) {GetSig_v9080Test getSig = new GetSig_v9080Test();getSig.destroy();}private void destroy() {try {emulator.close();} catch (IOException e) {e.printStackTrace();}} }


又是熟悉的 bug,这里依赖了 libgnustl_shared.so 我们给他加上。

跑起来,发现啥都没有输出,这没啥事,只要没报错就是好事。下面直接 call 函数
「注意这里的 byte[][] 类型,千万不要直接 vm.resolveClass("[[B") 这样在后面取值会报错,需要使用 ArrayObject + ByteArray 构建(龙哥指点------->ps:龙哥也是一位大佬)」

public ArrayObject getByteByte() {String[] arg5 = {这里大家自行填充 frida hook 出来的参数即可};DvmObject<?>[] dvm = new DvmObject<?>[arg5.length];for (int v1 = 0; v1 < arg5.length; ++v1) {if (arg5[v1] == null) {arg5[v1] = "";}dvm[v1] = new ByteArray(vm, arg5[v1].getBytes());}return new ArrayObject(dvm); }public void call() {String methodId = "nativeGeneratorSig(Ljava/lang/String;[[BLjava/lang/String;Ljava/lang/Object;)Lcom/meitu/secret/SigEntity;";DvmClass SigEntity = vm.resolveClass("com/meitu/secret/SigEntity");SigEntity.callStaticJniMethodObject(emulator, methodId,new StringObject(vm, "search/feeds.json"),getByteByte(),new StringObject(vm, "6184556633574670337"),vm.resolveClass("android/content/Context").newObject(null)); }


报错,正常补环境

又报错,但是看不出来啥问题,日志全开看看。

发现这里是调用 GetSuperClass JNI 函数报错的。不知道啥问题点进源码看看 DalvikVM.java:150

这里调用了 dvmClass.getSuperclass 函数,返回空,才报错的。跟进去看看

这个函数返回 superClass 字段,该字段的值来源于初始化的时候。在哪里初始化的呢。这个跟一下源码就可以了,我这里就不再继续分析了。是在 vm.resolveClass 的时候

这里可以传递多个参数,正常都是传递一个,如果有两个第二个参数就是 superClass


咱们加上试试

跑起来,成功了,没有报 superClass 错误了,但是又出现其他的了,不慌点进源码一探究竟

发现这里是个未实现的 JNI 函数,这里为了简单,就直接返回需要的值就行了。使用 jnitrace 看看返回值是啥

这里是个 0 直接返回

上面正常补环境即可

这里需要注意下,android base64 跟 java base64 还是有点区别的(龙哥的精讲课程里也说到过),这里为了不出问题,就直接复制了 android base64 的代码,大家自行复制就行。


环境补完,但是出奇的是结果尽然为空就很奇怪。不慌继续分析

「复制最后一次 NewStringUTF 的地址,ida 里跳过去」

是在这里操作的,x 查看该函数的交叉引用。


跟到了这里 LABEL_8 代码块,很明显是在 ValidateKey::getValidateResult 函数之后的 if 判断里调用的,在分析该函数逻辑。
这里有多地方会返回 -1 那就应该是该函数出了问题

「Tips: 这里应该是单步调试或者 hook 排除,博主就不去一步步分析了,大家可以自行尝试(具体啥原因也是没分析出来,直接使用 patch 大法搞定它)」

还记得前面的 frida hook 吗,里面就有 hook while 循环里 v11 的值,来看一下

这里循环了三次,最后一次结果为 0,刚好前面的 if 判断不成立就可以继续了

这里直接使用 unidbg hook 强制修改函数返回值为 0,最后也是成功运行出了结果。

6.最后end:

嗯,这篇文章好长啊,转载了我半天时间,后来qinless大哥给了一个固定url 的demo,前前后后分析了半天请求url参数差别,最后还是给还原成Python,测试 详情,评论和搜索接口都能用。

可以看到计算出来的结果 美图秀秀sig: 9667703fa8bc772b436ad0b451de9a6d 和抓包拿到的一模一样。
核心代码 就不公布了毕竟对人家不太友善,有兴趣的朋友可以一起交流学习(扣扣: 519545433)

总结

以上是生活随笔为你收集整理的美图秀秀 sig参数分析的全部内容,希望文章能够帮你解决所遇到的问题。

如果觉得生活随笔网站内容还不错,欢迎将生活随笔推荐给好友。