首页
社区
课程
招聘
[原创] 某外卖软件密钥生成算法分析
发表于: 2026-10-9 15:31 495

[原创] 某外卖软件密钥生成算法分析

2026-10-9 15:31
495

一、AES识别

AES的算法源码就不贴了,直接讲讲是怎么发现的:
图片描述
这里每个 S-box 值重复四次,组成一个 32 位表项,例如 0x63636363。按每四字节取一个值,还原出的完整 256 项与标准 AES S-box 一致。
根据这个数组检查交叉引用发现了sub_12FB04,可以进一步发现三个特征:

  • 接受 128 / 192 / 256 位密钥,对应 10 / 12 / 14 轮。
  • 将前一轮密钥的字节轮换后,通过这张表进行替换,再异或生成新轮密钥。
  • 使用 0x1EB090 的轮常量表,内容为:
    图片描述
    这些对应 AES 密钥扩展中的 RotWord、SubWord 和 Rcon。

二、密钥来源

我们直接追踪sub_12FB04的交叉引用,找到了函数sub_8B0F4。该函数将传入的密钥交给密钥扩展函数,随后调用 CBC 分组处理函数。沿密钥参数继续回溯,可以找到 sub_11F614。
图片描述
这个函数主要完成配置项名称解混淆、配置读取和结果复制,没有直接生成 AES 密钥。
首先,函数检查 byte_265A70,通过初始化标志避免重复执行下面的字符串解混淆:

sub_41CE8(&asc_261ED5, &unk_262094, 5, 2);

这里 unk_262094 是混淆后的数据,asc_261ED5 是输出缓冲区,5 是解码初始参数,2 是处理长度,解码规则是:

输出[i] = 输入[i] ^ (5 + 3*i);

这两个输入字节是 64 31,计算得到:

0x64 ^ 0x05 = 0x61 → 'a'
0x31 ^ 0x08 = 0x39 → '9'

解码后得到字符串 a9。
随后,函数将 a9 构造成临时字符串对象 v4,并传给 loc_8E010:

sub_16FE60((int)v4, &asc_261ED5);
loc_8E010(v5, a1, v4, 0);

其中,a1 是向下传递的上下文参数,v4 指定要读取的配置项,读取结果通过返回对象 v5 接收。
最后,函数复制配置字符串,并销毁临时对象:

v2 = strdup(v5[0]);
sub_1700BC(v5);
return v2;

至此,可以确认 sub_11F614 返回的是内部配置项 a9 的字符串值。结合后续 AES 调用可知,该字符串的前 16 个 ASCII 字节用于 AES-128 密钥扩展。接下来需要继续追踪:这份配置从哪里来,又是如何恢复出来的?
配置读取入口位于 loc_8E010,它根据内部状态选择不同的读取分支,具体的配置加载和字段查找由下游函数完成。这部分存在较多混淆,下面不再逐段贴出伪代码,而是沿当前 APK 的图片配置路径说明处理过程:程序先从图片资源中提取隐藏数据,派生配置解密密钥,再解密、解压得到配置 JSON,最后读取其中的 a9 字段并返回。

三、加密配置恢复

获取证书 DER 的 MD5,拼接包名和固定字符串,计算 SHA-256 并做常量表变换。
这里主要是需要看这个函数

bool __fastcall sub_8BD58(_QWORD *a1, __int64 a2)
{
  _QWORD *v4; // x21
  __int64 v5; // x0
  _BOOL4 v6; // w20
  int v7; // w21
  __int64 v8; // x0
  __int64 v10; // [xsp+8h] [xbp-58h] BYREF
  _QWORD v11[2]; // [xsp+10h] [xbp-50h] BYREF

  v11[1] = *(_QWORD *)(_ReadStatusReg(ARM64_SYSREG(3, 3, 13, 0, 2)) + 40);
  v4 = a1 + 4;
  if ( !*(_QWORD *)(a1[4] - 24LL) )
  {
    sub_4EBC0(v11, a2);
    sub_171A94(v4, v11);
    sub_1700BC(v11);
    if ( !*(_QWORD *)(*v4 - 24LL) )
      return 0;
  }
  if ( !*(_QWORD *)(a1[5] - 24LL) )
  {
    sub_4F480(v11, a2);                         // get der md5 upper
    sub_171A94(a1 + 5, v11);
    sub_1700BC(v11);
    if ( !*(_QWORD *)(a1[5] - 24LL) )
      return 0;
  }
  if ( !*(_QWORD *)(a1[2] - 24LL) )
  {
    sub_F1A20(v11, a2);
    sub_171A94(a1 + 2, v11);
    sub_1700BC(v11);
    if ( !*(_QWORD *)(a1[2] - 24LL) )
      return 0;
  }
  if ( !*(_QWORD *)(a1[3] - 24LL) )
  {
    sub_F1ACC(v11, a2);
    sub_171A94(a1 + 3, v11);
    sub_1700BC(v11);
    if ( !*(_QWORD *)(a1[3] - 24LL) )
      return 0;
  }
  if ( *(_QWORD *)(a1[10] - 24LL) )
    goto LABEL_10;
  v11[0] = &byte_266358;
  v7 = sub_170478(v11, v4);
  if ( (byte_264491 & 1) == 0 )
  {
    sub_41CE8(
      &asc_243A9D,
      aTsixj,                                   // "TSIXJ\\"
      3LL,
      6LL);                                     // ";"
    byte_243AA3 = 0;
  }
  byte_264491 = 1;
  v8 = sub_170564(v7, &asc_243A9D);             // ";"
  sub_170478(v8, a1 + 5);
  if ( !*(_QWORD *)(v11[0] - 24LL) )
  {
    sub_1700BC(v11);
    return 0;
  }
  sub_63A2C(&v10);
  sub_171A94(a1 + 10, &v10);
  sub_1700BC(&v10);
  sub_1700BC(v11);
  if ( !*(_QWORD *)(a1[10] - 24LL) )
    return 0;
LABEL_10:
  v11[0] = 0LL;
  v5 = sub_8C628(a1, v11);
  v6 = 0;
  v10 = v5;
  if ( v11[0] && v5 )
    v6 = (sub_8C6F4(a1, v5) & 1) != 0 && a1[6] != a1[7] && sub_8C744(a1) != 0;
  sub_4756C(&v10);
  return v6;
}

摘要输入由以下三部分拼接而成:

包名 + "WU@TEN" + 签名证书 DER 的 MD5 大写字符串
  • 包名获取:sub_8BD58 调用 sub_4EBC0(v11, a2),获取结果保存到 a1[4]。
  • 证书摘要计算:调用 sub_4F480(v11, a2),结果保存到 a1[5]。其下游通过 sub_4E61C → sub_4DEF4 获取证书 DER,最终由 sub_63660 计算 MD5 并转换为大写十六进制字符串。
  • 字符串拼接:直接在 sub_8BD58 中完成:
v7 = sub_170478(v11, v4);          // 追加包名,v4 = a1 + 4
v8 = sub_170564(v7, &asc_243A9D);  // 追加解混淆后的 "WU@TEN"
sub_170478(v8, a1 + 5);           // 追加证书 MD5 大写字符串

拼接结果随后传入 sub_63A2C:
图片描述
sub_63A2C 调用 sub_8B4A0,先计算 SHA-256,再对摘要的 32 个字节做加法和异或变换。
其等价逻辑为:

data = (包名 + "WU@TEN" + 证书MD5大写字符串).encode()
D = SHA256(data)

for i in range(32):
    M[i] = ((D[i] + B[i]) & 0xFF) ^ A[i]

其中,D 为 SHA-256 摘要,A、B 为两张内置的 32 字节常量表。变换结果转为十六进制字符串,缓存到 a1[10],作为后续图片查表派生密钥的输入。

得到摘要变换结果后,程序从 APK 的 assets 目录下读取图片。该文件没有图片扩展名,实际是一张 60×60 的 PNG。

解码后,程序取得 RGB 像素数据和图片宽高,将像素作为密钥派生的查找表。

接着,将前面缓存的摘要变换结果从十六进制字符串还原为字节,取前 16 字节。对每个字节 b,计算像素坐标并读取蓝色通道:

x = (b + 1803) % 60
y = (b + 3011) % 60
key[i] = b & 图片像素(x, y).蓝色通道 & 0xFE

重复 16 次,得到 16 字节的配置解密密钥。

同一张图片的 RGB 最低位中还隐藏着加密配置。程序提取这些最低位,还原出带 .PIC 头部的数据并完成校验,再使用上述密钥和固定 IV 0102030405060708 进行 AES-128-CBC 解密,这里 IV 是 16 个 ASCII 字节。。去除填充、执行 zlib 解压后,得到包含 a9 的配置 JSON。

总结

最终恢复出来的流程就是:

  1. 对 APK 签名证书的 DER 数据计算 MD5,转成大写十六进制字符串然后按照以下方法拼接:
包名 + "WU@TEN" + 证书MD5大写字符串
  1. 对拼接结果计算 SHA-256,再经过两张常量表的加法、异或变换,取变换结果前 16 字节。
  2. 根据Manifest的配置,从 assets 读取 PNG,解码为 RGB 像素。
  3. 用前面的 16 字节查图片像素,派生配置解密密钥:
b = M[i]
x = (b + 1803) % width
y = (b + 3011) % height
key[i] = b & 图片像素(x, y).蓝色通道 & 0xFE
  1. 另外提取 RGB 的最低位(LSB),将这些比特按顺序组合成字节,恢复图片中隐藏的 .PIC 数据。这里恢复的是加密配置数据。

  2. 校验后,用第 5 步得到的密钥和固定 ASCII IV,进行 AES-CBC 解密 → 去填充 → zlib 解压 → JSON 解析。

  3. 从 JSON 取出 a9,其字符串的前 16 个 ASCII 字节才是请求使用的 AES-128 密钥。


冰与火的战歌:Windows内核攻防实战高级班!从零到实战,融合AI与Windows内核攻防全技术栈,打造具备自动化能力的内核开发高手。

最后于 2026-10-9 23:53 被mb_hfjfruhl编辑 ,原因: 补充了总结部分
收藏
点赞 ・3
打赏
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回