吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 2449|回复: 19
收起左侧

[Android 原创] 简单分析 {某豆荚} 算法并还原

[复制链接]
西枫游戏 发表于 2024-4-11 17:34
本帖最后由 西枫游戏 于 2024-4-11 17:44 编辑

前言

简单分析{某豆荚}旧版本下载协议,本帖只负责分析交流切勿他用。若侵犯了权益,麻烦管理帮忙删除,谢谢!

工具

Tools Vsersion
样本 8.3.1
Frida 16.1.11
objection 1.11.0
jadx-gui 1.2.0
调试工具 Redmi Note 7
抓包工具 Charles 4.5.6

分析流程

1. 作为一个大部分旧版本APP的聚集地,上次的突然撤回让人很难受, 于是分析了下这里面的参数 然后批量一次性全部下载下来保存。各位下载自己喜欢的app的所有旧版本保存好,避免下次撤回就尴尬了。
2. 老规矩先上抓包,Charles 准备 代{过}{滤}理设置好,搜索一个app名字 点进去 找到历史版本的地方开始抓包
1.png
3. data数据包 如下 简单一看差不多都是设备id这些

  1. {
        "id": -1578021679051554492,
        "client": {
                "caller": "secret.wdj.client",
                "ex": {
                        "osVersion": 29,
                        "ch": "wandoujia_sem_default",
                        "productId": 2011,
                        "brand": "xiaomi",
                        "udid": "4058b1fee4ad4c0692b4dd42a7072fd15ec20b17",
                        "utdid": "ZgOaZ8nmo58DAHHI6Ld5O2tx",
                        "utoken": "vfsB+fVLPEG1lQKOy68OB7kkwH11MW6I",
                        "joinTime": 1712807123345,
                        "aid": "92fd798146787a1c5cf64b9e64cc824b"
                },
                "versionCode": 803020002,   //版本
                "VName": "8.3.2.2",
                "puid": "0191712815361058610001",
                "uuid": "d3MyyXYe7kGvxo8xCQYT8GuUXo9z7raGe7Y3XQe4clnTQHcDTVHKhlLvBuQi6VCCIhFWm9ubWcoqUIPTEXi82zk9B1Q=",
                "umid": "WV600011166175d68202106c7fd8675ee",
                "recognition": "0_0",
                "androidId": "d3MyyW\/UMjRMRPMKSOsC06dap5BuEa+tiejS+t8p1M+jewVb",
                "oaid": "d3MyyV7SnjREDnFEU6vOjqILv8mjuf6tk8DSVLi2a7lug7b4"
        },
        "data": {
                "offset": 0,
                "appId": 44751,    //app 的数字id
                "count": 20,       
                "packageName": "com.dianping.v1",
                "page": 1
        },
        "sign": "d027b1497fb4e705fde21728e7fc8d8c",   //貌似有个签名Sign 经过测试不加和错误的并不会返回所需要数据
        "encrypt": "md5"
    }

    5. 可以看到,Data数据udid utdid  aid uuid umid androidId oaid 基本都是固定信息了 最主要的是看到一个 "sign": "d027b1497fb4e705fde21728e7fc8d8c", 整个数据包的签名 直接反编译app搜索  sign
    2.png
    6. 跟进去这个方法createRequestHeader.put("sign", generateMD5Key(createRequestData));


    @Override // o.h.d.n.a
    public byte[] getRequestBytes() {   //疑似需要找的签名方法
        byte[] bArr;
        o.h.g.a.d dVar;
        byte[] bArr2 = this.mRequestByteCache;
        if (bArr2 != null) {
            return bArr2;
        }
        onRequestStart(this.mArgs);
        try {
            String str = (String) this.mArgs.get("opt_fields");
            if (!TextUtils.isEmpty(str)) {
                this.mArgs.remove("opt_fields");
            }
            Object createRequestData = createRequestData();
            JSONObject createRequestHeader = createRequestHeader(createRequestData);
            if (isEncryptByM9()) {
                createRequestHeader.put("sign", "");
                String jSONObject = createRequestHeader.toString();
                ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
                GZIPOutputStream gZIPOutputStream = new GZIPOutputStream(byteArrayOutputStream);
                gZIPOutputStream.write(jSONObject.getBytes("utf-8"));
                gZIPOutputStream.flush();
                gZIPOutputStream.close();
                byte[] byteArray = byteArrayOutputStream.toByteArray();
                byteArrayOutputStream.close();
                if (byteArray == null) {
                    bArr = null;
                } else {
                    o.h.g.a.c cVar = o.h.g.a.c.f;
                    if (cVar.b.f8191a) {
                        dVar = cVar.b;
                    } else {
                        dVar = cVar.f8189a;
                    }
                    bArr = dVar.a(byteArray);
                }
            } else {
                createRequestHeader.put("sign", generateMD5Key(createRequestData));  // 有点明显了  签名地方 继续跟进
                createRequestHeader.put(Body.CONST_ENCRYPT, "md5");
                if (!TextUtils.isEmpty(str)) {
                    createRequestHeader.put("optFields", str);
                }
                bArr = createRequestHeader.toString().getBytes();
            }
            this.mRequestByteCache = bArr;
            return bArr;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

7.继续跟进 generateMD5Key(createRequestData)方法

public String generateMD5Key(Object obj) {
        if (!(obj instanceof JSONObject)) {
            return "";
        }
        JSONObject jSONObject = (JSONObject) obj;
        StringBuilder S = o.e.a.a.a.S(sCaller);
        for (String str : getSortedKeys(jSONObject)) {
            o.e.a.a.a.S0(S, str, "=", getAsString(jSONObject.opt(str))); //把传进来的参数处理  "A=B"
        }
        return SignNative.getSign(S.toString(), 0); //继续调用方法签名  继续跟进
    }

8.跟进 SignNative.getSign(S.toString(), 0)
3.png
9.祭出frida 大法 直接hook

java.perform(function () {   
          var Myclass= Java.use('com.lib.common.SignNative')  
          Myclass.getSign.implementation = function (a,b) {  
          send(a)
          send(b)
          var ret = this.getSign(a,b)       
          send("ret: "+ret)                                
          return ret;
    };     
});

10. frida hook住 并且同时抓包也准备好 回到app   点击一下触发 打印参数   sign 结果出来了 与抓包的一致说明参数对了

C:\Users\LengLing\AppData\Local\Programs\Python\Python37-32\python.exe D:\python\wandoujia.py 
 Hook Start Running
 secret.wdj.clientappId=44751count=20offset=0packageName=com.dianping.v1page=1
 0
 ret: d027b1497fb4e705fde21728e7fc8d8c
========================================
抓包数据:
{
        "id": 3768127232815550033,
        "client": {
                "caller": "secret.wdj.client",
                "ex": {
                        "osVersion": 29,
                        "ch": "wandoujia_sem_default",
                        "productId": 2011,
                        "brand": "xiaomi",
                        "udid": "4058b1fee4ad4c0692b4dd42a7072fd15ec20b17",
                        "utdid": "ZgOaZ8nmo58DAHHI6Ld5O2tx",
                        "utoken": "vfsB+fVLPEG1lQKOy68OB7kkwH11MW6I",
                        "joinTime": 1712807123345,
                        "aid": "92fd798146787a1c5cf64b9e64cc824b"
                },
                "versionCode": 803020002,
                "VName": "8.3.2.2",
                "puid": "0191712815361058610001",
                "uuid": "d3MyyXYe7kGvxo8xCQYT8GuUXo9z7raGe7Y3XQe4clnTQHcDTVHKhlLvBuQi6VCCIhFWm9ubWcoqUIPTEXi82zk9B1Q=",
                "umid": "WV600011166175d68202106c7fd8675ee",
                "recognition": "0_0",
                "androidId": "d3MyyW\/UMjRMRPMKSOsC06dap5BuEa+tiejS+t8p1M+jewVb",
                "oaid": "d3MyyV7SnjREDnFEU6vOjqILv8mjuf6tk8DSVLi2a7lug7b4"
        },
        "data": {
                "offset": 0,
                "appId": 44751,
                "count": 20,
                "packageName": "com.dianping.v1",
                "page": 1
        },
        "sign": "d027b1497fb4e705fde21728e7fc8d8c", //发现和hook出来的签名一致
        "encrypt": "md5"
}

11.拿到了数据md5结果和hook出来的md5并不一致 说明so层对这个做了处理 要么就是魔改要么就是加盐了。所以必须跟进so层。打开ida64 pro  把so文件扔进去  定位到 JNI_OnLoad

jint __fastcall JNI_OnLoad(JavaVM *vm, void *reserved) //可能这个签名so文件没做处理  比较简单把
{
  jint result; // w0
  _jclass *v3; // x0
  JNIEnv *v4; // [xsp+8h] [xbp-38h] BYREF
  __int128 v5; // [xsp+10h] [xbp-30h] BYREF
  jstring (__fastcall *v6)(JNIEnv *, jobject, jstring, jint); // [xsp+20h] [xbp-20h]
  __int64 v7; // [xsp+28h] [xbp-18h]

  v7 = *(_QWORD *)(_ReadStatusReg(ARM64_SYSREG(3, 3, 13, 0, 2)) + 40);
  v5 = *(_OWORD *)off_3D90;  //方法结构体         
  v6 = getSign;
  if ( vm->functions->GetEnv(vm, (void **)&v4, 65540LL) )  //获取 GetEnv
    return -1;
  v3 = v4->functions->FindClass(v4, "com/lib/common/SignNative");  //拿到java 类
  if ( v4->functions->RegisterNatives(v4, v3, (const JNINativeMethod *)&v5, 1LL) < 0 )  。//注册方法
    result = -1;
  else
    result = 65542;
  return result;
}

12.跟到结构体然后定位到getSign方法

string __fastcall getSign(JNIEnv *env, jobject obj, jstring jstr, jint callerKey)   //
{
  char *v7; // x21
  unsigned int v8; // w0
  char *v9; // x22
  size_t v10; // x1
  unsigned int v11; // w0
  unsigned __int64 v12; // x1
  const unsigned __int8 *v13; // x2
  __int64 v14; // x24
  __int128 v16[2]; // [xsp+0h] [xbp-D0h] BYREF
  char v17; // [xsp+20h] [xbp-B0h]
  unsigned __int8 digest[8]; // [xsp+30h] [xbp-A0h] BYREF
  __int64 v19; // [xsp+38h] [xbp-98h]
  MD5_CTX context; // [xsp+40h] [xbp-90h] BYREF
  __int64 v21; // [xsp+98h] [xbp-38h]

  v21 = *(_QWORD *)(_ReadStatusReg(ARM64_SYSREG(3, 3, 13, 0, 2)) + 40);
  v7 = (char *)env->functions->GetStringUTFChars(env, jstr, 0LL);    //获取传进来的字符串
  *(_QWORD *)&context.buffer[56] = 0LL;
  *(_OWORD *)&context.buffer[24] = 0u;
  *(_OWORD *)&context.buffer[40] = 0u;
  *(_OWORD *)context.count = 0u;
  *(_OWORD *)&context.buffer[8] = 0u;
  *(_OWORD *)context.state = 0u;
  MD5Init(&context); //    跟进去发现初始化常量一样 确定是        md5初始化了
        0x01,0x23,0x45,0x67, 
        0x89,0xAB,0xCD,0xEF, 
        0xFE,0xDC,0xBA,0x98,
        0x76,0x54,0x32,0x10
        =======================================
        context->state[0] = 0x67452301;    //常量
        context->state[1] = 0xefcdab89;
        context->state[2] = 0x98badcfe;
        context->state[3] = 0x10325476;
        =======================================

  v8 = strlen(v7);   //获取长度
  MD5Update(&context, (unsigned __int8 *)v7, v8);  ////开始MD5Update 
  if ( callerKey == 1 )       //传进来的第二个参数为0  所以不会走这里
  {
    v9 = "dsfrvvbty55";  //这个好像是要拼接的字符串
    v10 = 12LL;
    goto LABEL_5;
  }
  if ( !callerKey )    //必须走这里来了
  {
    v9 = "LVJd97AbRtikeYRRhi3ocdwSD";   //拼接的字符串 也就是盐
    v10 = 26LL;
LABEL_5:
    v11 = __strlen_chk(v9, v10);   //长度
    MD5Update(&context, (unsigned __int8 *)v9, v11);  //开始MD5Update 
  }
  *(_QWORD *)digest = 0LL; 
  v19 = 0LL;
  MD5Final(digest, &context); //最后把拼接的参数和盐一起 md5 了
  v14 = 0LL;    //V14初始化为0 
  v17 = 0;
  v16[0] = 0u;  
  v16[1] = 0u;
  do
    sprintf((unsigned __int8 *const)v16, v12, v13, v16, digest[v14++]);  //结果格式化
  while ( v14 != 16 );     //V14 = 16 跳出
  env->functions->ReleaseStringUTFChars(env, jstr, (const unsigned __int8 *)v7); //释放字符串
  return env->functions->NewStringUTF(env, v16);   //返回32位的md5
}

13.最后用C语言 跑一遍 对比分析结果正确

int main()
{
    MD5_CTX md5;
    MD5Init(&md5);      //同样的初始化md5
    unsigned char encrypt1[100]="secret.wdj.clientappId=44751count=20offset=0packageName=com.dianping.v1page=1";
    unsigned char encrypt2[100]="LVJd97AbRtikeYRRhi3ocdwSD";
    unsigned char decrypt[16];
    MD5Update(&md5,encrypt1,strlen((char *)encrypt1));
    MD5Update(&md5,encrypt2,strlen((char *)encrypt2));
    MD5Final(decrypt,&md5);
    for(int i=0;i<16;i++)
        printf("%02x",decrypt[i]);
    printf("\n");
}
=============================================
C:\Users\LengLing\CLionProjects\untitled\cmake-build-debug\untitled.exe
d027b1497fb4e705fde21728e7fc8d8c  //与HOOK出来的结果一致

Process finished with exit code 0

总结

虽然这个app不算很难,分析经验够的话可能到了SO层看到字符串就知道是拼接然后md5了  但是对于经验不足的小白来说还是得慢慢跟进为好,从陌生到熟悉到直接上手一个过程,最后希望大家理解之后, 写个脚本下载自己所需要的旧版本app吧

免费评分

参与人数 10威望 +1 吾爱币 +30 热心值 +9 收起 理由
zhuzi77 + 1 + 1 我很赞同!
qtfreet00 + 1 + 20 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
城南520 + 1 + 1 我很赞同!
Sydyanlei0 + 1 用心讨论,共获提升!
LAOBILAXI233 + 1 我很赞同!
为之奈何? + 1 + 1 我很赞同!
fortytwo + 2 + 1 用心讨论,共获提升!
chen1987 + 1 + 1 用心讨论,共获提升!
gouzi123 + 1 + 1 谢谢@Thanks!
altTab + 2 + 1 用心讨论,共获提升!

查看全部评分

本帖被以下淘专辑推荐:

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

城南520 发表于 2024-4-17 12:10
西枫游戏 发表于 2024-4-17 08:28
从搜索那里抓个包 得到appid   md5 那里遍历所有的旧版本  剩下就是就是访问下载链接

不会Java,没有破解、逆向这方面的基础,但还是想批量获得下载链接,就怕后面下不到了。楼主能费心出个脚本吗,我可以开个悬赏
 楼主| 西枫游戏 发表于 2024-4-17 08:28
城南520 发表于 2024-4-14 14:36
最近想着把一些旧版APP下载下来保存着,但过程还是比较复杂,需要较高的技术含量,楼主能顺便出个脚本吗?

从搜索那里抓个包 得到appid   md5 那里遍历所有的旧版本  剩下就是就是访问下载链接
wasm2023 发表于 2024-4-11 18:32
collinchen1218 发表于 2024-4-11 18:49
为啥不能网页那里分析,app多麻烦
zhang37078381 发表于 2024-4-11 21:48
非常感谢分享!!!
shdndj56 发表于 2024-4-11 22:48
感谢分享思路
zjh889 发表于 2024-4-12 00:20
谢谢大师技术分享!
AMP 发表于 2024-4-12 08:20
感谢分享!大佬很厉害!
jsncy 发表于 2024-4-12 09:01
有用,谢谢分享。
szba1120 发表于 2024-4-12 09:33

谢谢大师技术分享!
tangzhiman 发表于 2024-4-12 10:29
学习到了,思路清晰,感谢楼主
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-12-15 05:48

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表