吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 962|回复: 8
收起左侧

[Web逆向] 某抑云eapi加密分析

  [复制链接]
xww2652008969 发表于 2026-2-9 01:11

1.前言

最近在写qq机器人,然后爬某抑云music的api,虽然网页端千年不变,但是抱着学的态度,想试试爬客户端,所以有就记录下踩坑记录,然后潜水好几年了就来记录记录。

2.逆向环境介绍

工具 名称
系统 win11
逆向工具 frida,ida
软件版本 pc端 3.1.28

3.逆向记录

哎没啥准备环境的 frida和ida 论坛一大堆教程

3.1逆向js代码

查看软件目录知道是类electron的程序所以先看看js代码,好消息是可以轻松的打开远程调试功能

cloudmusic.exe --remote-debugging-port=9222

然后使用edge附加,好消息js代码混淆不是很严重直接在搜索栏点击事件断点,追踪到下面代码

            serialData(t) {
                return u(this, void 0, void 0, (function*() {
                    const n = (...t) => u(this, void 0, void 0, (function*() {
                        return yield(t => u(this, void 0, void 0, (function*() {
                            return window.channel.serialData(t)
                        })))(...t)
                    }));
                    return yield n(t)
                }))
            }

大概是这样子的 window.channel.serialData(t)函数是一个c++本地函数,需要找c++实现,先记录传入的参数t=["/api/search/pc/result/tab","jsonXX"]第一个是接口名称,第二个是jsondata字符串

3.2寻找serialData对应的c++核心函数

寻找资源库没有*.node,然后查资料发现cloudmusic.dll为核心库, 先踩一个坑,我使用frida hook 一直触发不了frida  "C:\Program Files\NetEase\CloudMusic\cloudmusic.exe" -l 163.js原因是frida没有附加对线程,这里有4个相关的进程,我花费了很久,找到内存占用最大的那个dll附加就好了

cloudmusic.dll导出函数没有serialData相关的函数,然后搜索字符串发现以下代码

    {
        sub_180063FB0(v63, "encodeAnonymousId2");
        v62 = sub_18033E000(a2, v63);
        sub_180063E30(v63);
        if ( v62 )
        {
          v11 = sub_180DB73A0(&unk_181DDE678);
          sub_180E0AD00(v11, a4, a5);
          v64 = 1;
          sub_18006F2E0(v53);
          sub_1804DB440(a3);
          return v64;
        }
        else
        {
          sub_180063FB0(v66, "encryptId");
          v65 = sub_18033E000(a2, v66);
          sub_180063E30(v66);
          if ( v65 )
          {
            v12 = sub_180DB73A0(&unk_181DDE678);
            sub_180E0AB10(v12, a4, a5);
            v67 = 1;
            sub_18006F2E0(v53);
            sub_1804DB440(a3);
            return v67;
          }
          else
          {
            sub_180063FB0(v69, "serialData");
            v68 = sub_18033E000(a2, v69);
            sub_180063E30(v69);
            if ( v68 )
            {
              v13 = sub_180DB73A0(&unk_181DDE678);
              sub_180E0D3A0(v13, a4, a5);
              v70 = 1;
              sub_18006F2E0(v53);
              sub_1804DB440(a3);
              return v70;
            }
            else
            {
              sub_180063FB0(v72, "serialData2");
              v71 = sub_18033E000(a2, v72);
              sub_180063E30(v72);
              if ( v71 )
              {
                v14 = sub_180DB73A0(&unk_181DDE678);
                sub_180E0D0C0(v14, a4, a5);
                v73 = 1;
                sub_18006F2E0(v53);
                sub_1804DB440(a3);
                return v73;
              }
              else
              {
                sub_180063FB0(v75, "deSerialData");
                v74 = sub_18033E000(a2, v75);
                sub_180063E30(v75);
                if ( v74 )
                {

hook180E0D3A0函数发送网络请求会触发就进一步追踪

__int64 __fastcall sub_180E0D3A0(__int64 a1, __int64 a2, __int64 a3)
    v18 = sub_180CAD730((int)v14, (int)v15, (__int64)v13);
    if ( v18 != 1 )
    {
      if ( (int)sub_1805D35A0() <= 2 )
      {
        v30 = sub_1805D1BD0(
                (__int64)v25,
                (__int64)"D:\\jenkins\\workspace\\18_11_IOS_PACKER_CI_PC3fabu\\music_pc\\setup\\build\\na\\orpheus\\src\\"
                         "framework\\framework\\client\\render_process_center.cpp",
                dword_181D7549C + 16,
                2);
        v31 = v30;
        v29 |= 1u;
        LocaleT = _LocaleUpdate::GetLocaleT(v30);
        v7 = sub_180067D20(LocaleT, "Serial:");
        v8 = unknown_libname_3022(v7, v14);
        v9 = sub_180067D20(v8, " error:");
        v32 = sub_1800642E0(v9, v18);
        v10 = sub_180060B00((__int64)v24);
        nullsub_1(v10, v32);
      }
      if ( (v29 & 1) != 0 )
      {
        v29 &= ~1u;
        sub_1805D2020(v25);
      }
    }

如果sub_180CAD730返回为!1就会报错那说明180CAD730在处理加密逻辑继续追踪180CAD730发现下面代码

__int64 __fastcall sub_180CB26A0(int a1, int a2, int a3, int a4, __int64 a5, __int64 a6)
{
  size_t v7; // [rsp+20h] [rbp-28h]

  LODWORD(v7) = a4;
  return sub_180CB2910((int)L"y1LN8qzeNzxTWX6dVeyshvKmXJRQRfkZy9Y7e7fao6g=", a1, a2, a3, v7, a5, a6);
}

发现很像是加密函数点进去

  else
    {
      if ( v34 == 1 )
      {
        if ( (int)AES_set_encrypt_key() < 0 )
        {
          v34 = 6;
        }
        else if ( Size_4 )
        {
          v7 = ((Size_4 - 1) >> 4) + 1;
          do
          {
            AES_encrypt();
            v25 += 16;
            --v7;
          }
          while ( v7 );
        }
      }
      v35 = Block;
      if ( !Block )
        goto LABEL_55;
    }
    free(v35);

然后hook180CB2910发现结果 a1 a2 a3 v7  是之前类似于t=["/api/search/pc/result/tab","jsonXX"]的字符串已经其大小

好好看见AES_encrypt()就是加密代码了

  LODWORD(Block) = sub_180CB3D00(L"qGWDhNWDRGvh421GZVutvg==", 0i64, &v46);
  if ( (_DWORD)Block == 2 )
  {
    v11 = j__malloc_base((unsigned int)v46);
    LODWORD(Block) = sub_180CB3D00(L"qGWDhNWDRGvh421GZVutvg==", v11, &v46);
    if ( (_DWORD)Block != 1 )
    {
      if ( v11 )
        free(v11);
    }
  }
  LODWORD(v49) = sub_180CB3D00(L"ErCUMN/gBpmtg+wmLZrDCA==", 0i64, &v46);
  if ( (_DWORD)v49 == 2 )
  {
    v8 = j__malloc_base((unsigned int)v46);
    LODWORD(v49) = sub_180CB3D00(L"ErCUMN/gBpmtg+wmLZrDCA==", v8, &v46);
    if ( (_DWORD)v49 != 1 )
    {
      if ( v8 )
        free(v8);
    }
  }
  v14 = sub_180CB3D00(L"xKlkMXZUU8J2uUH2ZfmYmQ==", 0i64, &v46);
  if ( v14 == 2 )
  {
    v9 = j__malloc_base((unsigned int)v46);
    v14 = sub_180CB3D00(L"xKlkMXZUU8J2uUH2ZfmYmQ==", v9, &v46);
    if ( v14 != 1 )
    {
      if ( v9 )
        free(v9);
    }
  }
  v15 = sub_180CB3D00(L"eOYLRn09GyKgAzkfn3pLFA==", 0i64, &v46);
  if ( v15 == 2 )
  {
    v10 = j__malloc_base((unsigned int)v46);
    v15 = sub_180CB3D00(L"eOYLRn09GyKgAzkfn3pLFA==", v10, &v46);
    if ( v15 != 1 )
    {
      if ( v10 )
        free(v10);
    }
  }

这里180CB3D00点进去是有解密函数那么,肯定是将数据提取出来变成 v8 v9 v10 v11

  if ( (_DWORD)Block == 1 && (_DWORD)v49 == 1 && v14 == 1 && v15 == 1 )
  {
    v16 = Size;
    v49 = Size + (_DWORD)a5 + 22;
    v57 = 0i64;
    v17 = (char *)j__malloc_base(v49);
    v18 = v17;
    if ( !v17 )
    {
      if ( v11 )
        free(v11);
      if ( v8 )
        free(v8);
      if ( v9 )
        free(v9);
      if ( v10 )
        free(v10);
      return 4i64;
    }
    v19 = Src;
    *(_DWORD *)v17 = *(_DWORD *)v8;
    *((_WORD *)v17 + 2) = v8[2];
    v54 = v16;
    memmove(v17 + 6, v19, v16);
    v20 = (unsigned int)(v16 + 6);
    v21 = v52;
    *(_WORD *)&v18[v20] = *(_WORD *)v9;
    v18[v20 + 2] = v9[2];
    LODWORD(v20) = v20 + 3;
    memmove(&v18[(unsigned int)v20], v21, (unsigned int)a5);
    v22 = (unsigned int)(v20 + a5);
    *(_QWORD *)&v18[v22] = *v10;
    *(_DWORD *)&v18[v22 + 8] = *((_DWORD *)v10 + 2);
    v18[v22 + 12] = *((_BYTE *)v10 + 12);
    sub_180CB6E50(v56);
    sub_180CB6E80(v56, v18, v49);
    sub_180CB6B40(&v57, v56);
    free(v18);
    sub_180CB2060(&v57, &v58);
    *(_QWORD *)&v57 = Size_4;
    v23 = j__malloc_base(Size_4);
    v46 = (size_t)v23;
    v24 = v23;
    if ( !v23 )
      return 8i64;
    v25 = v23;
    Block = 0i64;
    LODWORD(v49) = Size_4 - (Size + a5 + 58);
    memmove(v23, Src, Size);
    v26 = v54;
    v27 = v52;
    *(_QWORD *)&v24[v54] = *v11;
    *(_DWORD *)&v24[v26 + 8] = *((_DWORD *)v11 + 2);
    v24[v26 + 12] = *((_BYTE *)v11 + 12);
    memmove(&v24[Size + 13], v27, (unsigned int)a5);
    v28 = Size;
    v29 = v59;
    v30 = (unsigned int)a5 + Size + 13;
    *(_QWORD *)&v24[v30] = *v11;
    v31 = v58;
    *(_DWORD *)&v24[v30 + 8] = *((_DWORD *)v11 + 2);
    v24[v30 + 12] = *((_BYTE *)v11 + 12);
    v32 = (unsigned int)(v30 + 13);
    *(_OWORD *)&v24[v32] = v31;
    *(_OWORD *)&v24[v32 + 16] = v29;
    v33 = v32 + 32;
    if ( v33 != v28 + (_DWORD)a5 + 58 )
    {
      free(v11);
      free(v8);
      free(v9);
      free(v10);
      return 7i64;
    }
    memset(&v24[v33], v49, (int)v49);
      v34 = sub_180CB3D00(v53, 0i64, &v49);
    if ( v34 == 2
      && (Block = j__malloc_base((unsigned int)v49), v34 = sub_180CB3D00(v53, Block, &v49), v34 != 1)
      && Block )
    {
      free(Block);
      v35 = Block;
    }
    else
    {
      if ( v34 == 1 )
      {
        if ( (int)AES_set_encrypt_key() < 0 )
        {
          v34 = 6;
        }
        else if ( Size_4 )
        {
          v7 = ((Size_4 - 1) >> 4) + 1;
          do
          {
            AES_encrypt();
            v25 += 16;
            --v7;
          }
          while ( v7 );
        }
      }
      v35 = Block;
      if ( !Block )
        goto LABEL_55;
    }

这里sub_180CB6E50(v56);sub_180CB6E80(v56, v18, v49);sub_180CB6B40(&v57, v56);点击去发现是Md5计算然后转化为消息16进制,因为我不知道sub_180CB3D00函数返回的数据所以在计算md5前 hook

v34 = sub_180CB3D00(v53, Block, &v49)这里是取出真正的key hook直接提取出来,这里似乎是ECB加密,PaddingMode.PKCS7 填充(因为之前很多逆向发现很多是PaddingMode.PKCS7 ,因为我需要逐步计算 就hook了AES_encrypt()函数发现的逻辑整理用c#在下面表示

    private static string Md5String(string input)
    {
        var data = Encoding.UTF8.GetBytes(input);
        var hash = MD5.HashData(data);
        var sb = new StringBuilder();

        Console.WriteLine();
        foreach (var b in hash) sb.Append(b.ToString("x2")); // 小写 32 位

        return sb.ToString();
    }
    private class AesEncryptionHelper
    {
        private static readonly byte[] key = new byte[]
        {
            0x65, 0x38, 0x32, 0x63, 0x6b, 0x65, 0x6e, 0x68,
            0x38, 0x64, 0x69, 0x63, 0x68, 0x65, 0x6e, 0x38
        };

        private static readonly string hea = "nobody";
        private static readonly string en = "use";
        private static readonly string end = "md5forencrypt";

        public static string EncryptAes(string a1, string a2)
        {
            var s = hea + a1 + en + a2 + end;

            var ddd = a1 + "-36cd479b6b5-" + a2 + "-36cd479b6b5-" + Md5String(s);
            var ecb = AesEncryptEcb(Encoding.UTF8.GetBytes(ddd));
            return BytesToHex(ecb);
        }

        private static byte[] AesEncryptEcb(byte[] raw)
        {
            using var aes = Aes.Create();
            aes.Key = key; // 
            aes.Mode = CipherMode.ECB;
            aes.Padding = PaddingMode.PKCS7;
            aes.BlockSize = 128;
            using var encryptor = aes.CreateEncryptor();
            return encryptor.TransformFinalBlock(raw, 0, raw.Length);
        }

        public static byte[] AesDecryptEcb(byte[] cipher)
        {
            using var aes = Aes.Create();
            aes.Key = key; 
            aes.Mode = CipherMode.ECB;
            aes.Padding = PaddingMode.PKCS7; 
            aes.BlockSize = 128;

            using var decryptor = aes.CreateDecryptor();
            return decryptor.TransformFinalBlock(cipher, 0, cipher.Length);
        }

        private static string BytesToHex(byte[] bytes)
        {
            var hex = new StringBuilder(bytes.Length * 2);
            foreach (var b in bytes) hex.Append($"{b:X2}");
            return hex.ToString();
        }

        public static byte[] HexToBytes(string hex)
        {
            var len = hex.Length;
            var bytes = new byte[len / 2];

            for (var i = 0; i < len; i += 2) bytes[i / 2] = Convert.ToByte(hex.Substring(i, 2), 16);

            return bytes;
        }
    }

然后通过控制台调用传入参数发现一模一样加密逻辑解决,后面解密第一步尝试用相同key解密看看,结果就是同一个key就直接不逆向了

免费评分

参与人数 7威望 +1 吾爱币 +25 热心值 +7 收起 理由
涛之雨 + 1 + 20 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
a927 + 1 + 1 热心回复!
sifeng + 1 + 1 谢谢@Thanks!
fakejoker + 1 + 1 我很赞同!
杨辣子 + 1 + 1 谢谢@Thanks!
jk998 + 1 + 1 我很赞同!
leitianshuo + 1 我很赞同!

查看全部评分

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

leitianshuo 发表于 2026-2-9 13:08
wyy好像用的是网心云的cdn,顺着网心云sdk(一个dll)找call应该也是个好方法
jk998 发表于 2026-2-9 14:46
bfbkj998 发表于 2026-2-9 15:05
huffmankim 发表于 2026-2-10 09:02
我能不能理解为app其实就是网页版的集合,本质通过可视化图片加代码进行运行的?
kulouxiaohai 发表于 2026-2-11 11:25
分析后是不是可以免费下载音乐?
kingmars 发表于 2026-2-11 12:21
标记一下
titaz 发表于 2026-2-11 22:04
标记一下
 楼主| xww2652008969 发表于 2026-2-11 22:33
huffmankim 发表于 2026-2-10 09:02
我能不能理解为app其实就是网页版的集合,本质通过可视化图片加代码进行运行的?

app的api是可以抓高音质的,网页版的我没测试反正app的加密api是可以的 这是我认为的
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

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

GMT+8, 2026-2-14 14:29

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

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