TencentPediyKeygenMe — 逆向分析

在这里插入图片描述

目标:求用户名 KCTF 的合法 License。
结论(满足程序魔改后验签的正确答案):

用户名 : KCTF
License: DQF030H7-7X8P0YMA-WHZNQEGF-CFEVXVTA

工具:IDA Pro (MCP) 静态分析 + Python 复现 + C 多线程 Pollard’s rho 解 64 位 ECDLP。


目录

  1. 整体逻辑与校验入口
  2. License 格式
  3. 变换链(签名 ⇄ License)
  4. 各加解密算法与“魔改”
  5. ECDSA 验签与曲线参数
  6. ⭐ ECC 验签魔改点 sub_404AD0(重点)
  7. 利用:强制 r=1 与 64 位 ECDLP
  8. 注册机算法(正向/逆向)
  9. 结果与验证
  10. 关键常量速查表

1. 整体逻辑与校验入口

成功提示字符串 "Congratulations! \n You will be the keygen machine!"0x46A3C4)被
sub_4082700x408270)引用 —— 这就是注册校验主函数。

hWnd        : 用户名输入框, WM_GETTEXT 上限 0x21(33),长度需 != 0
dword_46C6E8: License 输入框, WM_GETTEXT 上限 0x24(36),长度必须 == 35
成功         : MessageBoxA(... , Text="Congratulations!...", Caption="Success!")

主函数流水线(成功路径需层层 al!=0):

sub_4490B0(&v30, 32, &v15/*var84*/, cfg);   // base32 解码 32 字符 -> 20 字节
sub_408060();                                // SM4 解密
for(i<16)  var94[i] = sub_4486D0(var94[i]);  // GF(2^8) 逆元 S盒  (16 字节)
for(j<4)   var84[16+j] = sub_4486D0(...);    // GF(2^8) 逆元 S盒  (尾 4 字节)
sub_407FD0(&v9/*var94*/);                    // Blowfish 类CBC 加密 -> v8(16字节)
if ( (v8d0^v8d1^v8d2^v8d3) == var84[16:20] ) // 校验和
   sub_403210(username, len, &v8);           // ECDSA 验签

2. License 格式

  • 35 字符;横杠位置 byte_46A3F8 = {0x08, 0x11, 0x1A} = 8 / 17 / 26,即 XXXXXXXX-XXXXXXXX-XXXXXXXX-XXXXXXXX8-8-8-8)。
  • 其余 32 个字符是 base32,字母表
    ABCDEFGHJKMNPQRSTVWXYZ1234567890(去掉 I/L/O/U,Crockford 风格),MSB-first
  • 32 个 base32 符号 × 5 bit = 160 bit = 20 字节(解码缓冲 var84)。

3. 变换链(签名 ⇄ License)

栈缓冲区映射(sub_408270 帧内偏移):

缓冲 偏移 含义
var84 ebp-0x84 base32 解码 20 字节([0:16]=SM4区,[16:20]=校验/IV 区)
var94 ebp-0x94 SM4 输出 / S盒中间 16 字节
var_A4 ebp-0xA4 v8 = ECDSA 签名 16 字节 = r(8B大端) ‖ s(8B大端)

正向(校验)数据流

base32 → var84(20B)
var94[0:16] = SM4_dec(var84[0:16])                 (key="Security@Tencent")
var94[i]    = GFinv(var94[i])      for i in 0..15
var84[16+j] = GFinv(var84[16+j])   for j in 0..3
v8          = Blowfish_CBClike_enc(var94, IV=var84[16:20]) (key="DEADBEEF")
assert XOR(v8 的4个dword) == var84[16:20]
ECDSA_verify("KCTF", v8)

逆向(注册机):从合法签名 v8 出发

cks(4B)         = XOR(v8 的4个dword)          # == var84[16:20] 的“S盒后”值(也是 IV)
var84[16:20]    = GFinv(cks)                  # base32 原始尾4字节
var94_sb        = Blowfish_CBClike_dec(v8, IV=cks)
var94_raw       = GFinv(var94_sb)
var84[0:16]     = SM4_enc(var94_raw)
20B             = var84[0:16] ‖ var84[16:20]
base32 编码 → 32 字符,按 8/17/26 插 '-'

Blowfish 的类 CBC 模式(sub_448A90):out_i = BF(in_i) XOR prev_inprev_in(-1)=IV
上下文(sub_407FD0 内)显式置 expanded=0 / dir=0sub_448B50(0,...) 不交换 P 数组 ⇒ 正向是 Blowfish 加密,逆向用解密。

4. 各加解密算法与“魔改”

原语 函数 结论 / 魔改
SM4 sub_448EE0(密钥扩展) / sub_448F90(分组) 系统参数 FK=A3B1BAC6/56AA3350/677D9197/B27022DC、标准 S盒(byte_456ED0)、标准 L/L′。魔改:密钥扩展去掉 CK 轮常量。分组用轮密钥逆序 ⇒ 即“解密”。Key="Security@Tencent"
Blowfish sub_448710 + 表 unk_455DF0(P[18]) / unk_455E38(S[4][256]) 算法/密钥扩展结构标准,魔改:P/S 初始表被替换P[0]=0xF35AF301,非 π 常数 0x243F6A88)。Key="DEADBEEF",IV 取自校验区 4 字节。
字节 S盒 sub_4486D0sub_4485D0(GF 乘) x^254 mod 0x11B = GF(2⁸) 乘法逆元(无仿射)。自逆GFinv(GFinv(x))==x,逆向时再作用一次即可。
SHA1 sub_405C60 标准 IV 67452301/EFCDAB89/98BADCFE/10325476/C3D2E1F0,未改。
base32 sub_4490B0 标准 MSB-first,自定义字母表。

5. ECDSA 验签与曲线参数

sub_403210 构造 CryptoPP DL_VerifierImpl< ... DL_Keys_ECDSA<ECP>, DL_Algorithm_ECDSA<ECP>, DL_SignatureMessageEncodingMethod_DSA, SHA1 >,即 ECDSA / ECP / SHA1

曲线(64 位素数阶自定义曲线):

p  = 0xC564EEF070E69193
a  = -3  (= p-3)
b  = 0x22996B9C33AEEFDB
G  = (0x2223A1D595845FA2, 0x1C4EEE9222DDDA62)
n  = 0xC564EEF19A080B07     (素数, 阶)
Q  = (0x297A4A1E5B1FC99B, 0x880198E3724F9FEE)   公钥

校验:G,Q 均在曲线上,n·G=On·Q=O

嵌入的私钥0x46A54C 处 8 字节 54 65 6E 63 65 6E 74 20 = ASCII "Tencent "
d = 0x54656E63656E7420,并验证 d·G == Q ✓(紧邻其后即公钥 Qx@0x46A554Qy@0x46A55C)。

消息表示:阶为 64 bit ⇒ e = int(SHA1(username)[0:8], 'big')sub_4051C0(bitlen+7)>>3 = 8)。
签名编码(sub_4050E0 InputSignature):v8[0:8]=r(大端)、v8[8:16]=s(大端)。

6. ⭐ ECC 验签魔改点 sub_404AD0(重点)

6.1 定位

算法对象由 sub_406F90 返回,其 vtable = off_45789C。验签 sub_4051C0 调用
(*(algo->vtable[1]))(...),而 *(off_45789C+4) = 0x4578A0 → sub_404AD0
sub_404AD0 就是 DL_Algorithm_ECDSA<ECP>::VerifyDigest(params, pubkey, e, r, s) —— ECDSA 验签核心,参数 a4 = ra5 = s

6.2 魔改内容

sub_419EF0 是大整数幅值比较(返回 −1/0/+1)。VerifyDigest 开头对 r,s 做范围校验
1 ≤ r,s < n。其中对 r 下界 的判断被篡改:

原版 CryptoPP :  if ( Compare(r, 1) >= 0 ) ...   // r >= 1 视为合法
魔改后(本样本):  if ( Compare(r, 1) == 0 ) ...   // 仅 r == 1 视为合法

汇编证据(0x404B760x404B96):

404b76  mov edx, esi          ; esi = &Integer(1)
404b78  mov eax, ebx          ; ebx = r
404b7a  call sub_419EF0       ; Compare(r, 1)
404b94  test eax, eax
404b96  jnz  loc_404BD3       ; 结果 != 0 → 置失败标志(拒绝)
                              ; 即:只有 Compare(r,1)==0(r==1)才继续

(同函数 0x404B4Ajger < n 上界检查,未改a5=s1≤s<n
sub_401E40 / sub_401E90 完成,亦未改。)

6.3 影响

验签等式 (u1·G + u2·Q).x mod n == r 本身不变,但额外强制 r == 1

  • 普通 ECDSA 签名 r 是随机值 ⇒ 能过原版验签,但被魔改版直接拒绝
    (这正是初版 license 4Q6PXSE2-...(r≠1)在真机失败的原因。)
  • 想通过,必须构造 r == 1 的合法签名

7. 利用:强制 r=1 与 64 位 ECDLP

r=1,验签计算 u1=e·s⁻¹, u2=r·s⁻¹=s⁻¹,要求

P = u1·G + u2·Q = s⁻¹·(e·G + Q) = s⁻¹·(e+d)·G   的 x 坐标 ≡ 1 (mod n)

因坐标 x < p < nx ≡ 1 (mod n) 只能是 x = 1。该 x=1 的曲线点存在:

y² = 1 + a·1 + b = b - 2 (mod p)  → 是 QR
R = (1, 0x381C2B17A752591E)

W = e·G + Q = (e+d)·G(标量已知)。需要 s⁻¹·W = R,即需 ρ0 = dlog_G(R)
这是一次 64 位 ECDLP(n 为素数,Pohlig–Hellman 无效)。用 16 线程 Pollard’s rho + 蒙哥马利批量求逆 + 区分点,约 70 秒求得:

ρ0 = dlog_G(R) = 0x6CBF49B72611C49A      (已验证 ρ0·G = (1, y0))

取 nonce k = ρ0(则 r = (k·G).x mod n = 1),

e = int(SHA1("KCTF")[0:8],'big') = 0x0C5E14D370BD9A84
s = k⁻¹·(e + r·d) mod n = k⁻¹·(e + d) mod n = 0x120BAA4576344E44
v8 = r(8B) ‖ s(8B) = 0000000000000001 120BAA4576344E44

正确性:P = s⁻¹·(e+d)·G = k·G = R,故 P.x mod n = 1 = r ✓,且 r==1 满足魔改约束。

8. 注册机算法(正向/逆向)

  1. e = SHA1("KCTF")[0:8](大端整数)。
  2. 解 ECDLP 得 ρ0 = dlog_G((1,y0))k=ρ0r=1s=k⁻¹(e+d) mod n
  3. v8 = r.to_bytes(8,'big') + s.to_bytes(8,'big')
  4. cks = XOR(v8 的4个小端dword)var84[16:20] = GFinv(cks_bytes)IV = cks
  5. Blowfish 逆:var94_sb = BF_dec(v8[0:8]^IV) ‖ BF_dec(v8[8:16]^v8[0:8])
  6. var94_raw = GFinv(var94_sb)var84[0:16] = SM4_enc(var94_raw)
  7. 20 字节 base32 编码(MSB-first)→ 32 字符,按 8/17/26 插 -

注:以上 SM4 / Blowfish / GF 表均直接从二进制 dumpbyte_456ED0 / unk_455DF0 / unk_455E38),与样本一致。

9. 结果与验证

用从二进制 dump 的真实表,在 Python 中完整复现正向校验链并施加魔改后规则:

license   = DQF030H7-7X8P0YMA-WHZNQEGF-CFEVXVTA
var84(20) = ... (base32 解码)
v8(还原)  = 0000000000000001120BAA4576344E44   ← 与构造一致
r == 1            : True   (满足魔改约束)
s in [1,n)        : True
checksum          : True
ECDSA P.x%n == r  : True
==> MODIFIED_VERIFY_PASS = True

最终答案:用户名 KCTF → License DQF030H7-7X8P0YMA-WHZNQEGF-CFEVXVTA

作废版本:4Q6PXSE2-D3JPFV8A-S1XGZPAA-VDMPR3QF(r≠1,仅过原版 ECDSA,被魔改版拒绝)。

10. 关键常量速查表

值 / 地址
校验主函数 sub_408270
ECDSA 验签构造 sub_403210
VerifyDigest(魔改点) sub_404AD0(algo vtable off_45789C[1]=0x4578A0),jnz @0x404B96 强制 r==1
私钥 d 0x46A54C = "Tencent " = 0x54656E63656E7420
公钥 Q Qx@0x46A554, Qy@0x46A55C
曲线 p,n,b 0xC564EEF070E69193, 0xC564EEF19A080B07, 0x22996B9C33AEEFDB
G (0x2223A1D595845FA2, 0x1C4EEE9222DDDA62)
x=1 点 R (1, 0x381C2B17A752591E)
ρ0 = dlogG® 0x6CBF49B72611C49A
SM4 key / S盒 "Security@Tencent" / byte_456ED0(标准,密钥扩展去 CK)
Blowfish key / 表 "DEADBEEF" / unk_455DF0,unk_455E38(魔改表)
字节 S盒 sub_4486D0 = GF(2⁸) 逆元(0x11B),自逆
base32 字母表 ABCDEFGHJKMNPQRSTVWXYZ1234567890,横杠位 8/17/26
签名 v8 r=0x1,s=0x120BAA4576344E440000000000000001120BAA4576344E44
Logo

欢迎加入 MCP 技术社区!与志同道合者携手前行,一同解锁 MCP 技术的无限可能!

更多推荐