首页
课程
问答
CTF
社区
招聘
峰会
发现
排行榜
知识库
工具下载
看雪20年
看雪商城
证书查询
登录
注册
首页
社区
课程
招聘
发现
问答
CTF
排行榜
知识库
工具下载
峰会
看雪商城
证书查询
社区
逆向工程
发新帖
16
6
[分享]010算法分析,附带注册机,网络验证分析
发表于: 2026-1-21 10:33
3761
[分享]010算法分析,附带注册机,网络验证分析
毛民8868
2026-1-21 10:33
3761
# 010分析 本文对010 16.0.2版本进行分析,参考solly大佬的文章,经过验证,其注册算法与之前版本并未改变,新人第一次写贴,如有违规侵权,请联系会尽快处理。 ## 1.查看文件基本信息 用die对010分析查看基本信息  使用 Qt 6.4.3 图形界面库、由 Microsoft Visual Studio 2019(完整IDE)开发的程序,没有壳。 ## 2.010暴力破解 通过报错信息定位判断点  Ida中位置如下  使用x64dbg进行调试 在程序入口使用shift+D字符串搜索-invalid name   双击进入  查看交叉引用,跳转到引用点  到此处,可以看见上面有三个跳转,再jmp下面一行中继续查看应用跟踪  到达此处,这里跟上面情况一样,仍然是只做了判断,下断点之后继续往上跟  此处0xdb是成功关键位,流程图如下  红线是正确的走向,通过流程图可以对edi复制进行跟踪, 继续往上跟踪,发现有两个引用  跟到这里我们会发现两个跳转,首先看887d方法  其中反汇编如下, ``` __int64 __fastcall sub_7FF6484DC3B0(__int64 a1, __int64 a2, __int64 a3) { unsigned int v3; // edi int v6; // eax int v7; // eax int v8; // eax unsigned int v9; // ecx int v10; // eax unsigned int v11; // ecx int v12; // eax unsigned int v13; // ecx v3 = a2; if ( *(a1 + 124) ) return 0x113LL; v6 = sub_7FF6480A3D5F(a1, a2, a3); switch ( v6 ) { case 0x2D: return 0xDBLL; case 0x4E: v12 = sub_7FF6480AC879(a1, v3); v13 = 0x20C; if ( v12 != 23 ) return 0xED; return v13; case 0xE7: return 0x177LL; default: v7 = sub_7FF6480AC879(a1, v3); if ( v7 == 23 ) return 0x71LL; if ( v7 != 42 ) { if ( v7 == 0x138 ) { v8 = sub_7FF6480AEC00(a1); v9 = 0x2F; if ( v8 == 0x1A3 ) return 0xF9; return v9; } return 0x177LL; } v10 = sub_7FF6480AEC00(a1); v11 = 0x177; if ( v10 == 419 ) return 0xF9; return v11; } } ``` 可以看出对db有检测且v6调用3d5f方法,也就是上一个call,其具体为第一个call返回0x2d此处返回0xdb,验证成功。那么暴力破解的方法就很多了 此处更改这边的if跳转,与返回值 if ( *(a1 + 124) ) return 0x113LL; 具体如下 这种更改判断以及返回实现效果实现为不需要网络破解,(因为在010初始页面有剩余天数下,也就是在者许可证验证之前就有这个逻辑的实现,同样也是调用这个方法)  对应x64进行更改打补丁  成功破解  ## 3.算法分析 接下来我们开始跟踪其注册算法,先在这个位置下一个断点,如下图所示:  按 F9 运行该软件,在注册界面,输入注册码假码信息,如下图所示:  在上图中也可以看到目前的注册状态:Evaluation Version - 30 Days Left。运行,马上断了下来,如下图所示   在ida中代码如下  到这里  下面对方法进行讲解 第一个call中代码如下 主要是去除’ “ ``` v2 = *(int *)(a2 + 0x10); v3 = a1 + 24; QString::operator=(a1 + 24, a2); if ( (int)v2 > 2 ) { v5 = *(_WORD *)QChar::QChar((QChar *)&v12, '\''); if ( *(_WORD *)QString::operator[](a2, &v13, 0LL) == v5 ) { v6 = v2; v7 = *(_WORD *)QChar::QChar((QChar *)&v12, '\''); if ( *(_WORD *)QString::operator[](a2, &v13, v2 - 1) == v7 ) { LABEL_8: v10 = QString::mid(a2, v11, 1LL, (int)v2 - 2); QString::operator=(v3, v10); QString::~QString(v11); return; } } else { v6 = v2; } v8 = *(_WORD *)QChar::QChar((QChar *)&v12, '"'); if ( *(_WORD *)QString::operator[](a2, &v13, 0LL) == v8 ) { v9 = *(_WORD *)QChar::QChar((QChar *)&v12, '"'); if ( *(_WORD *)QString::operator[](a2, &v13, v6 - 1) == v9 ) goto LABEL_8; } } } ``` 第二个call如下,对0与l做替换 ``` v4 = 0LL; QString::QString(a2, a3); v17 = 1; v5 = *((_DWORD *)a2 + 4); if ( v5 > 0 ) { v6 = (unsigned int)v5; do { v7 = *(_WORD *)QChar::QChar((QChar *)v14, 'O'); if ( *(_WORD *)QString::operator[](a2, v4) == v7 || (v8 = *(_WORD *)QChar::QChar((QChar *)v15, 'o'), *(_WORD *)QString::operator[](a2, v4) == v8) ) { QChar::QChar((QChar *)&v18, 48); v9 = v18; *(_WORD *)QString::operator[](a2, v4) = v9; } v10 = *(_WORD *)QChar::QChar((QChar *)v16, 'l'); if ( *(_WORD *)QString::operator[](a2, v4) == v10 ) { QChar::QChar((QChar *)&v13, '1'); v11 = v13; *(_WORD *)QString::operator[](a2, v4) = v11; } ++v4; --v6; } while ( v6 ); } return a2; } ``` 第三个核心算法 ``` __int64 __fastcall hexing_7FF774DCB840(__int64 a1, unsigned int a2, unsigned int a3) { int v4; // ebp _QWORD *v5; // rbx char *v6; // rdi __int16 v7; // bx __int64 v8; // rax __int16 v9; // bx __int64 v10; // rax int v11; // r8d __int64 v12; // rax _BYTE *v13; // rdx char v14; // dl int v15; // r12d unsigned __int8 v16; // r9 unsigned __int8 v17; // al int v18; // ebx char v19; // di __int16 v20; // bx unsigned __int16 v21; // ax unsigned int v22; // ecx char v23; // r14 unsigned __int16 v24; // ax int v25; // ebx char *v26; // rax int v27; // eax unsigned int v28; // ebx int index_7FF6DED5B0C3; // eax unsigned int v31; // [rsp+20h] [rbp-78h] char v32[2]; // [rsp+24h] [rbp-74h] BYREF char v33[2]; // [rsp+26h] [rbp-72h] BYREF char v34[2]; // [rsp+28h] [rbp-70h] BYREF char v35[2]; // [rsp+2Ah] [rbp-6Eh] BYREF unsigned int v36; // [rsp+2Ch] [rbp-6Ch] unsigned int v37; // [rsp+30h] [rbp-68h] _BYTE v38[24]; // [rsp+38h] [rbp-60h] BYREF unsigned __int8 v39; // [rsp+50h] [rbp-48h] BYREF unsigned __int8 v40; // [rsp+51h] [rbp-47h] unsigned __int8 v41; // [rsp+52h] [rbp-46h] char v42; // [rsp+53h] [rbp-45h] unsigned __int8 v43; // [rsp+54h] [rbp-44h] unsigned __int8 v44; // [rsp+55h] [rbp-43h] unsigned __int8 v45; // [rsp+56h] [rbp-42h] char v46; // [rsp+57h] [rbp-41h] unsigned __int8 v47; // [rsp+58h] [rbp-40h] unsigned __int8 v48; // [rsp+59h] [rbp-3Fh] v37 = a3; v36 = a2; v4 = 0; // 初始化局部变量 v31 = 0; // 初始化配对索引 *(a1 + 128) = 0LL; // 清空结果字段1 *(a1 + 104) = 0; // 清空结果字段2 if ( *(a1 + 40) && *(a1 + 64) ) // 检查用户名和注册码是否都存在 { parse_dashed_hex_7FF6DED5E1DD(a1, &v39); // 解析注册码字符串 v5 = &off_7FF6BBF4A5C0; // 指向黑名单数组 do { if ( QString::operator==(a1 + 0x18, *v5) )// 比较用户名是否在黑名单中 return 0xE7LL; // 如果在,返回错误码0xE7 ++v5; } while ( v5 < &unk_7FF6BBF4A5D0 ); // 遍历整个黑名单数组 if ( *(a1 + 40) > 1 ) { v6 = &unk_7FF6BBF4A5D1; do { v7 = *QChar::QChar(v32, *(v6 - 1)); // 遍历所有特征条目 v8 = QString::operator[](a1 + 24, 0LL); // 取用户名第0位(第1个字符) if ( *QChar::toUpper(v8, v33) == v7 ) // 转大写后对比 { v9 = *QChar::QChar(v34, *v6); v10 = QString::operator[](a1 + 24, 1LL);// 取用户名第1位(第2个字符) if ( *QChar::toUpper(v10, v35) == v9 )// 转大写后对比 { v11 = 0; // 步骤3:匹配注册码的10个核心字节 v12 = 0LL; v13 = v6 + 10; // 指向特征条目中“注册码特征字节”的最后1位 do { // &v39:之前parse_dashed_hex解析出的注册码原始字节数组 if ( *(&v39 + v12) != *v13 ) // 逐字节对比注册码和特征值 break; ++v11; ++v12; --v13; // 反向对比(从后往前) } while ( v12 < 10 ); // 一共对比10个字节 if ( v11 == 10 ) // 如果10个字节全匹配,直接判定无效 return 0xE7LL; } } v6 += 12; // 每个特征条目占12字节,跳到下一个条目 } while ( v6 < byte_7FF6BBF4A5E9 ); } v14 = v46; // 字节7 v15 = v44; // 字节5 v16 = v41; // 字节2 v17 = v40; // 字节1 v18 = v39; // 字节0 v19 = v42; // 字节3(类型标识符) // 此处v42就是rsp也就是重新生成的密钥 switch ( v42 ) // 根据类型字节选择验证路径 { case 0x9C: v20 = (v44 ^ v41) + ((v46 ^ v40) << 8); // 普通许可证)这个是没有免费技术支持,没有免费升级的注册类型,注册码中有版本号检查,必须大于当前软件的版本,否则提示注册码过期。注册码只有4段。 // v20是 010 Editor 普通许可证的 16 位 “产品标识符”,由注册码 4 个固定字节异或计算得出; // 解释:步骤1: 计算许可证ID // v20 = ((v46 ^ v40) << 8) | (v44 ^ v41) // 即字节1^字节7作为高8位,字节5^字节2作为低8位 *(a1 + 108) = xor_add_transform_7FF6DED5AD3F(v45 ^ v39);// 步骤2: 计算有效期 // 有效期 = transform(字节6 ^ 字节0) // transform函数: ((x ^ 0x18) + 61) ^ 0xA7 v21 = validate_and_map_id_7FF6DED58C33(v20); *(a1 + 112) = v21; // 步骤3: 验证并映射ID // ID映射函数: // 1. tmp = ((v20 ^ 0x7892) + 19760) ^ 0x3421 // 2. 如果tmp能被11整除,返回tmp/11,否则返回0 v22 = *(a1 + 108); // 获取计算出的有效期 if ( v22 && v21 - 1 <= 0x3E7 ) // 步骤4: 检查有效期和ID是否有效 // 有效期非0且ID在1-1000范围内 { v23 = 0; if ( v22 < 2 ) v23 = *(a1 + 108); // 如果有效期小于2,v23=有效期,否则v23=0 LABEL_26: // 进入公共验证部分 QString::toUtf8(a1 + 24, v38); // 步骤1: 获取用户名的UTF-8编码 v25 = *(a1 + 112); // 获取映射后的ID LOBYTE(v4) = v19 != -4; // 设置标志位: 如果类型不是0xFC则为1,否则为0 v26 = QByteArray::data(v38); // 获取用户名数据指针 v27 = compute_custom_hash_7FF6DED5C97D(v26, v4, v23, v25);// 步骤2: 计算自定义哈希 // 参数: 用户名, 标志位, 有效期/索引, ID if ( v43 == v27 && v15 == BYTE1(v27) && v45 == BYTE2(v27) && v46 == HIBYTE(v27) )// 步骤3: 验证哈希匹配 // 哈希值的四个字节必须分别等于v43, v15, v45, v46 // 即: 字节4=哈希最低字节, 字节5=哈希第二字节, 字节6=哈希第三字节, 字节7=哈希最高字节 // { if ( v19 == '\x9C' ) { if ( v36 > *(a1 + 108) ) // 如果当前时间(v36)大于有效期 { v28 = 0x4E; // 返回错误码0x4E(可能表示过期) LABEL_41: // 清理并返回 QByteArray::~QByteArray(v38); return v28; } LABEL_33: v28 = 0x2D; // 否则返回成功码0x2D goto LABEL_41; } if ( v19 == -4 ) // 0xFC { index_7FF6DED5B0C3 = validate_pair_and_get_index_7FF6DED5B0C3(v39 + (v40 << 8) + (v41 << 16), v27);// 验证配对: 计算(字节0 + 字节1<<8 + 字节2<<16)和哈希值的关系 if ( index_7FF6DED5B0C3 ) // 如果配对有效 { *(a1 + 104) = index_7FF6DED5B0C3;// 存储配对索引 v28 = 0x93; // 返回成功码0x93 goto LABEL_41; } } else if ( v31 ) { if ( v37 > v31 ) { v28 = 0x4E; goto LABEL_41; } goto LABEL_33; } } v28 = '\xE7'; goto LABEL_41; } break; case 0xFC: v23 = -1; // 这个不算正式注册版本,是延长试用期版本,最长可延长为5年试用期(从发布日期算起),这个版本会有一些检查(注册表中的数据),保证延期合法。注册码只有4段 *(a1 + 108) = 255; *(a1 + 112) = 1; *(a1 + 128) = 1; goto LABEL_26; case 0xAC: *(a1 + 108) = 2; // 这个是有免费技术支持、有免费升级的完整注册类型,不过会检查注册码中的过期时间(天数,从发布时间算起),这些免费支持是有期限的,会过期。注册码有5段,是最完整的注册码。 // a1是注册码验证结构体指针,a1+108是 “类型 / 有效期标记字段”,固定赋值为 2; v24 = validate_and_map_id_7FF6DED58C33((v15 ^ v16) + ((v14 ^ v17) << 8));// 计算该类型的 16 位 “产品核心 ID”(类似 0x9C 分支的 v20,但验证逻辑更严格); *(a1 + 112) = v24; // 映射后的 ID 存入结构体a1+112字段(和 0x9C 分支存储映射 ID 的位置一致); if ( v24 - 1 <= 0x1387 ) // 验证映射后的 ID 是否在 “完整许可证有效范围” 内(比 0x9C 分支的 1~1000 范围大,对应 “更完整的注册码” 属性); { v31 = validate_pair_and_get_index_7FF6DED5B0C3(// validate_pair_and_get_index是 “配对验证 + 期限索引获取” 函数,输入 2 个参数,验证匹配则返回 “过期天数 / 支持期限索引”(非 0),失败则返回 0 (v45 ^ v18) + ((v15 ^ v48) << 16) + ((v47 ^ v43) << 8), 026706047); *(a1 + 132) = v31; v23 = v31; goto LABEL_26; } break; } return 0xE7LL; } return 0x93LL; } /* Orphan comments: 普通许可证)详细流程 */ ``` 对应汇编如下 ``` 000000014043B84 | mov qword ptr ss:[rsp+0x10],rbx | 000000014043B84 | mov qword ptr ss:[rsp+0x18],rbp | 000000014043B84 | push rsi | 000000014043B84 | push rdi | 000000014043B84 | push r12 | 000000014043B84 | push r14 | 000000014043B85 | push r15 | 000000014043B85 | sub rsp,0x70 | 000000014043B85 | mov rax,qword ptr ds:[0x140FF1FF8] | rax:EntryPoint 000000014043B85 | xor rax,rsp | rax:EntryPoint 000000014043B86 | mov qword ptr ss:[rsp+0x60],rax | rax:EntryPoint 000000014043B86 | mov dword ptr ss:[rsp+0x30],r8d | a3=0x4f4c 000000014043B86 | mov dword ptr ss:[rsp+0x2C],edx | a2 11 000000014043B86 | mov rsi,rcx | 000000014043B87 | xor ebp,ebp | v4=0 初始化局部变量 000000014043B87 | mov dword ptr ss:[rsp+0x20],ebp | 初始化配对索引 000000014043B87 | mov qword ptr ds:[rcx+0x80],rbp | a1 + 128 000000014043B87 | mov dword ptr ds:[rcx+0x68],ebp | a1 + 104 000000014043B88 | cmp qword ptr ds:[rcx+0x28],rbp | 000000014043B88 | je 010editor.14043BBA8 | 000000014043B88 | cmp qword ptr ds:[rcx+0x40],rbp | 000000014043B88 | je 010editor.14043BBA8 | 检查用户名和注册码是否都存在 000000014043B89 | lea rdx,qword ptr ss:[rsp+0x50] | rdx:EntryPoint 000000014043B89 | call 010editor.14000E1DD | decode_formatted_hex_id 序列号转换 000000014043B89 | lea rbx,qword ptr ds:[<sub_140E1A5C0>] | 0000000140E1A5C0:&"999" 黑名单用户名 000000014043B8A | lea rdi,qword ptr ds:[0x140E1A5D0] | 000000014043B8A | nop dword ptr ds:[rax],eax | 000000014043B8B | mov rdx,qword ptr ds:[rbx] | rdx:EntryPoint 000000014043B8B | lea rcx,qword ptr ds:[rsi+0x18] | 0x00000133F51AA700 000000014043B8B | call qword ptr ds:[<public: bool __cdecl QString: | 用户名比较 999与AnyOne 000000014043B8B | test al,al | al==0 用户名!=999 000000014043B8B | jne 010editor.14043BBA1 | al=1跳到注册码值正确 000000014043B8C | add rbx,0x8 | 000000014043B8C | cmp rbx,rdi | 000000014043B8C | jl 010editor.14043B8B0 | 000000014043B8C | cmp qword ptr ds:[rsi+0x28],0x1 | 1 000000014043B8D | jle 010editor.14043B98F | 000000014043B8D | lea rdi,qword ptr ds:[0x140E1A5D1] | v6 sint 3此处同样也是比较用户名,比较签名两个字符 43 ('c') 4F('0') 57 D6 30 E3 CA B9 AC AB A1 ('j') C4 ('u') 4A 55 59 2A 35 E2 C4 65 AC D3 A4 CB 000000014043B8E | lea r14,qword ptr ds:[0x140E1A5E9] | co 与ju sint 5 000000014043B8E | nop word ptr ds:[rax+rax],ax | 000000014043B8F | movzx edx,byte ptr ds:[rdi-0x1] | 遍历黑名单数组,每个12 000000014043B8F | lea rcx,qword ptr ss:[rsp+0x24] | b7 01 v32 000000014043B8F | call qword ptr ds:[<public: __cdecl QChar::QChar( | 取出c 000000014043B8F | movzx ebx,word ptr ds:[rax] | c 000000014043B90 | xor edx,edx | 000000014043B90 | lea rcx,qword ptr ds:[rsi+0x18] | unchar ch 000000014043B90 | call qword ptr ds:[<public: class QChar & __cdecl | 000000014043B90 | lea rdx,qword ptr ss:[rsp+0x26] | rdx:EntryPoint 000000014043B91 | mov rcx,rax | rax:EntryPoint 000000014043B91 | call qword ptr ds:[<public: class QChar __cdecl Q | 转换成大写用户名第一个字符 000000014043B91 | cmp word ptr ds:[rax],bx | rax:EntryPoint 000000014043B91 | jne 010editor.14043B982 | 000000014043B92 | movzx edx,byte ptr ds:[rdi] | 000000014043B92 | lea rcx,qword ptr ss:[rsp+0x28] | 000000014043B92 | call qword ptr ds:[<public: __cdecl QChar::QChar( | 000000014043B92 | movzx ebx,word ptr ds:[rax] | rax:EntryPoint 000000014043B93 | mov edx,0x1 | 000000014043B93 | lea rcx,qword ptr ds:[rsi+0x18] | 000000014043B93 | call qword ptr ds:[<public: class QChar & __cdecl | 000000014043B94 | lea rdx,qword ptr ss:[rsp+0x2A] | rdx:EntryPoint 000000014043B94 | mov rcx,rax | rax:EntryPoint 000000014043B94 | call qword ptr ds:[<public: class QChar __cdecl Q | 000000014043B94 | cmp word ptr ds:[rax],bx | rax:EntryPoint 000000014043B95 | jne 010editor.14043B982 | 000000014043B95 | mov r8d,ebp | 000000014043B95 | mov rax,rbp | rax:EntryPoint 000000014043B95 | lea rdx,qword ptr ds:[rdi+0xA] | rdx:EntryPoint 000000014043B95 | nop | 000000014043B96 | movzx ecx,byte ptr ds:[rdx] | rdx:EntryPoint 000000014043B96 | cmp byte ptr ss:[rsp+rax+0x50],cl | 000000014043B96 | jne 010editor.14043B978 | 000000014043B96 | inc r8d | 000000014043B96 | inc rax | rax:EntryPoint 000000014043B96 | dec rdx | rdx:EntryPoint 000000014043B97 | cmp rax,0xA | rax:EntryPoint, 0A:'\n' 000000014043B97 | jl 010editor.14043B960 | 000000014043B97 | cmp r8d,0xA | 0A:'\n' 000000014043B97 | je 010editor.14043BBA1 | 000000014043B98 | add rdi,0xC | 000000014043B98 | cmp rdi,r14 | 000000014043B98 | jl 010editor.14043B8F0 | 000000014043B98 | movzx edx,byte ptr ss:[rsp+0x57] | v46 字节7,密钥第八个 000000014043B99 | movzx r12d,byte ptr ss:[rsp+0x55] | v44 字节5 第6个 000000014043B99 | movzx r9d,byte ptr ss:[rsp+0x52] | v41 字节2 第3个 000000014043B9A | movzx eax,byte ptr ss:[rsp+0x51] | v40 字节1 第2个 000000014043B9A | movzx ebx,byte ptr ss:[rsp+0x50] | v39 字节0 第一个 000000014043B9A | movzx edi,byte ptr ss:[rsp+0x53] | dil(EDI 低 8 位) 字节3 第四个 000000014043B9A | cmp dil,0x9C | 000000014043B9B | jne 010editor.14043BA16 | swith 0xfc跳转 000000014043B9B | movzx ecx,bl | bl=v44,零扩展为32位存入ECX(ECX=0x000000[V44]) 000000014043B9B | xor cl,byte ptr ss:[rsp+0x56] | cl是ECX低8位(即v44),和rsp+0x56(v41=rsp+0x52)异或 → cl = v44 ^ v41 000000014043B9B | xor al,dl | al=v46,dl=v40 → al = v46 ^ v40(高8位原始值) 000000014043B9B | movzx ebx,al | 把al(v46^v40)零扩展为32位存入EBX(EBX=0x000000[V46^V40]) 000000014043B9C | mov eax,0x100 | EAX=0x100(十进制256,等价于2^8) 000000014043B9C | imul ebx,eax | EBX = EBX * EAX = (v46^v40) * 0x100 → 等价于 (v46^v40) << 8 000000014043B9C | movzx eax,r9b | r9b存储的是之前计算的v44^v41(cl的值),零扩展存入EAX 000000014043B9C | xor al,r12b | 校准:确保al=v44^v41 000000014043B9D | movzx edx,al | 把al(v44^v41)零扩展到EDX(EDX=0x000000[V44^V41]) 000000014043B9D | add bx,dx | bx=EBX低16位(即(v46^v40)<<8),dx=EDX低16位(即v44^v41)→ bx = 高8位 + 低8位 = v20 000000014043B9D | call 010editor.14000AD3F | xor_add_transform 000000014043B9D | movzx eax,al | 000000014043B9D | mov dword ptr ds:[rsi+0x6C],eax | 计算版本号 000000014043B9E | movzx ecx,bx | 000000014043B9E | call 010editor.140008C33 | validate_and_map_id 000000014043B9E | movzx eax,ax | 000000014043B9E | mov dword ptr ds:[rsi+0x70],eax | 验证产品ID 000000014043B9E | mov ecx,dword ptr ds:[rsi+0x6C] | 000000014043B9F | test ecx,ecx | 000000014043B9F | je 010editor.14043BBA1 | 000000014043B9F | dec eax | 000000014043B9F | cmp eax,0x3E7 | 000000014043BA0 | ja 010editor.14043BBA1 | 验证条件检查版本号必须非零:v22 != 0产品ID必须在1-1000范围内:v21 >= 1 && v21 <= 1000(0x3E7 = 999) 000000014043BA0 | mov r14d,ebp | 000000014043BA0 | cmp ecx,0x2 | 000000014043BA0 | cmovb r14d,ecx | 设置哈希参数 000000014043BA1 | jmp 010editor.14043BABD | 000000014043BA1 | cmp dil,0xFC | 比较0x fc 000000014043BA1 | jne 010editor.14043BA3C | 000000014043BA1 | mov r14d,0xFF | 000000014043BA2 | mov dword ptr ds:[rsi+0x6C],r14d | 000000014043BA2 | mov dword ptr ds:[rsi+0x70],0x1 | 000000014043BA2 | mov dword ptr ds:[rsi+0x80],0x1 | 000000014043BA3 | jmp 010editor.14043BABD | 000000014043BA3 | cmp dil,0xAC | 比较0xac 000000014043BA4 | jne 010editor.14043BBA1 | 000000014043BA4 | mov dword ptr ds:[rsi+0x6C],0x2 | 将2写入结构体的偏移108处(类型标记) 000000014043BA4 | xor al,dl | 计算 v14 ^ v17(字节7^字节1) 000000014043BA4 | movzx ecx,al | 000000014043BA5 | mov eax,0x100 | 左移8位 000000014043BA5 | imul ecx,eax | 000000014043BA5 | movzx eax,r9b | 000000014043BA5 | xor al,r12b | 计算 v15 ^ v16(字节5^字节2) 000000014043BA6 | movzx edx,al | 000000014043BA6 | add cx,dx | 计算16位产品id 两部分组合成16位数 000000014043BA6 | call 010editor.140008C33 | 计算该类型的 16 位 “产品核心 ID” 000000014043BA6 | movzx eax,ax | 000000014043BA6 | mov dword ptr ds:[rsi+0x70],eax | 映射后的 ID 存入结构体a1+112字段 000000014043BA7 | dec eax | 000000014043BA7 | cmp eax,0x1387 | 验证映射后的 ID 是否在 “完整许可证有效范围” 内(比 0x9C 分支的 1~1000 范围大,对应 “更完整的注册码” 属性) 000000014043BA7 | ja 010editor.14043BBA1 | 000000014043BA7 | movzx ecx,byte ptr ss:[rsp+0x54] | 加载v43(字节4 000000014043BA8 | movzx eax,byte ptr ss:[rsp+0x58] | 加载v47(字节8 000000014043BA8 | xor ecx,eax | 000000014043BA8 | shl ecx,0x8 | ecx = (v43 ^ v47) << 8 000000014043BA8 | movzx edx,byte ptr ss:[rsp+0x59] | 加载v48(字节9) 000000014043BA9 | xor edx,r12d | edx = v48 ^ r12d(r12d存放v15/字节5) 000000014043BA9 | shl edx,0x10 | edx = (v48 ^ v15) << 16 000000014043BA9 | add ecx,edx | ecx = ((v47 ^ v43) << 8) + ((v15 ^ v48) << 16) 000000014043BA9 | mov edx,ebx | ebx存放v18(字节0) 000000014043BA9 | movzx eax,byte ptr ss:[rsp+0x56] | 加载v45(字节6) 000000014043BAA | xor edx,eax | edx = v18 ^ v45 000000014043BAA | add ecx,edx | 000000014043BAA | mov edx,0x5B8C27 | 第二个固定参数:026706047(八进制) 000000014043BAA | call 010editor.14000B0C3 | 000000014043BAB | mov dword ptr ss:[rsp+0x20],eax | 000000014043BAB | mov dword ptr ds:[rsi+0x84],eax | 存储到结构体的偏移84h处 000000014043BAB | mov r14d,eax | 进入26分支 000000014043BAB | lea rdx,qword ptr ss:[rsp+0x38] | 目标地址,用于存储QByteArray 000000014043BAC | lea rcx,qword ptr ds:[rsi+0x18] | 用户名 000000014043BAC | call qword ptr ds:[<public: class QByteArray __cd | 序列号转UTF-8 000000014043BAC | nop | 000000014043BAC | mov ebx,dword ptr ds:[rsi+0x70] | 获取产品id 000000014043BAD | cmp dil,0xFC | 比较v19(类型)与0xFC 000000014043BAD | setne bpl | 如果v19!=0xFC,则bpl=1,否则bpl=0 000000014043BAD | lea rcx,qword ptr ss:[rsp+0x38] | 000000014043BAD | call qword ptr ds:[<public: char * __cdecl QByteA | rax指向用户名UTF-8字节数据 000000014043BAE | mov r9d,ebx | 第4个参数:产品ID 000000014043BAE | mov r8d,r14d | 第3个参数:v23(对于0xAC类型是配对索引) 000000014043BAE | mov edx,ebp | 第2个参数:标志位(bpl) 000000014043BAE | mov rcx,rax | 第1个参数:用户名数据指针 000000014043BAE | call 010editor.14000C97D | compute_custom_hash 000000014043BAF | mov edx,eax | 保存哈希值到edx 000000014043BAF | cmp byte ptr ss:[rsp+0x54],al | 比较v43(字节4)与哈希最低字节 000000014043BAF | jne 010editor.14043BB8D | 不匹配则跳转到失败处理(返回0xE7) 000000014043BAF | mov ecx,eax | 000000014043BB0 | shr ecx,0x8 | 右移8位,获取第二字节 000000014043BB0 | cmp r12b,cl | 比较v15(字节5)与哈希第二字节 000000014043BB0 | jne 010editor.14043BB8D | 000000014043BB0 | mov ecx,eax | 000000014043BB0 | shr ecx,0x10 | 右移16位,获取第三字节 000000014043BB1 | cmp byte ptr ss:[rsp+0x56],cl | 比较v45(字节6)与哈希第三字节 000000014043BB1 | jne 010editor.14043BB8D | 000000014043BB1 | mov ecx,eax | 000000014043BB1 | shr ecx,0x18 | 右移24位,获取第四字节 000000014043BB1 | cmp byte ptr ss:[rsp+0x57],cl | 比较v46(字节7)与哈希最高字节 000000014043BB2 | jne 010editor.14043BB8D | 计算hash匹配 000000014043BB2 | cmp dil,0x9C | 000000014043BB2 | jne 010editor.14043BB40 | 000000014043BB2 | mov eax,dword ptr ss:[rsp+0x2C] | 000000014043BB2 | cmp eax,dword ptr ds:[rsi+0x6C] | 000000014043BB3 | jbe 010editor.14043BB39 | 000000014043BB3 | mov ebx,0x4E | 4E:'N' 000000014043BB3 | jmp 010editor.14043BB92 | 000000014043BB3 | mov ebx,0x2D | 2D:'-' 000000014043BB3 | jmp 010editor.14043BB92 | 000000014043BB4 | cmp dil,0xFC | 000000014043BB4 | jne 010editor.14043BB72 | 000000014043BB4 | movzx ecx,byte ptr ss:[rsp+0x52] | 000000014043BB4 | shl ecx,0x10 | 000000014043BB4 | movzx eax,byte ptr ss:[rsp+0x51] | 000000014043BB5 | shl eax,0x8 | 000000014043BB5 | add ecx,eax | 000000014043BB5 | movzx eax,byte ptr ss:[rsp+0x50] | 000000014043BB5 | add ecx,eax | 000000014043BB5 | call 010editor.14000B0C3 | 000000014043BB6 | test eax,eax | 000000014043BB6 | je 010editor.14043BB8D | 000000014043BB6 | mov dword ptr ds:[rsi+0x68],eax | 000000014043BB6 | mov ebx,0x93 | 000000014043BB7 | jmp 010editor.14043BB92 | 000000014043BB7 | cmp dil,0xAC | 000000014043BB7 | jne 010editor.14043BB8D | 000000014043BB7 | mov eax,dword ptr ss:[rsp+0x20] | 000000014043BB7 | test eax,eax | 000000014043BB7 | je 010editor.14043BB8D | 000000014043BB8 | cmp dword ptr ss:[rsp+0x30],eax | 000000014043BB8 | jbe 010editor.14043BB39 | 000000014043BB8 | mov ebx,0x4E | 4E:'N' 000000014043BB8 | jmp 010editor.14043BB92 | 000000014043BB8 | mov ebx,0xE7 | 000000014043BB9 | lea rcx,qword ptr ss:[rsp+0x38] | 000000014043BB9 | call qword ptr ds:[<public: __cdecl QByteArray::~ | 000000014043BB9 | mov eax,ebx | 000000014043BB9 | jmp 010editor.14043BBAD | 000000014043BBA | mov eax,0xE7 | 赋值0xe7 000000014043BBA | jmp 010editor.14043BBAD | 000000014043BBA | mov eax,0x93 | 000000014043BBA | mov rcx,qword ptr ss:[rsp+0x60] | 000000014043BBB | xor rcx,rsp | 000000014043BBB | call 010editor.1400074A5 | security_check_cookie 000000014043BBB | lea r11,qword ptr ss:[rsp+0x70] | 000000014043BBB | mov rbx,qword ptr ds:[r11+0x38] | 000000014043BBC | mov rbp,qword ptr ds:[r11+0x40] | 000000014043BBC | mov rsp,r11 | 000000014043BBC | pop r15 | 000000014043BBC | pop r14 | 000000014043BBC | pop r12 | 000000014043BBD | pop rdi | 000000014043BBD | pop rsi | 000000014043BBD | ret | ``` 其中在compute_custom_hash_7FF6DED5C97D中对应映射表如下 ``` 0x39CB44B8, 0x23754F67, 0x5F017211, 0x3EBB24DA, 0x351707C6, 0x63F9774B, 0x17827288, 0x0FE74821, 0x5B5F670F, 0x48315AE8, 0x785B7769, 0x2B7A1547, 0x38D11292, 0x42A11B32, 0x35332244, 0x77437B60, 0x1EAB3B10, 0x53810000, 0x1D0212AE, 0x6F0377A8, 0x43C03092, 0x2D3C0A8E, 0x62950CBF, 0x30F06FFA, 0x34F710E0, 0x28F417FB, 0x350D2F95, 0x5A361D5A, 0x15CC060B, 0x0AFD13CC, 0x28603BCF, 0x3371066B, 0x30CD14E4, 0x175D3A67, 0x6DD66A13, 0x2D3409F9, 0x581E7B82, 0x76526B99, 0x5C8D5188, 0x2C857971, 0x15F51FC0, 0x68CC0D11, 0x49F55E5C, 0x275E4364, 0x2D1E0DBC, 0x4CEE7CE3, 0x32555840, 0x112E2E08, 0x6978065A, 0x72921406, 0x314578E7, 0x175621B7, 0x40771DBF, 0x3FC238D6, 0x4A31128A, 0x2DAD036E, 0x41A069D6, 0x25400192, 0x00DD4667, 0x6AFC1F4F, 0x571040CE, 0x62FE66DF, 0x41DB4B3E, 0x3582231F, 0x55F6079A, 0x1CA70644, 0x1B1643D2, 0x3F7228C9, 0x5F141070, 0x3E1474AB, 0x444B256E, 0x537050D9, 0x0F42094B, 0x2FD820E6, 0x778B2E5E, 0x71176D02, 0x7FEA7A69, 0x5BB54628, 0x19BA6C71, 0x39763A99, 0x178D54CD, 0x01246E88, 0x3313537E, 0x2B8E2D17, 0x2A3D10BE, 0x59D10582, 0x37A163DB, 0x30D6489A, 0x6A215C46, 0x0E1C7A76, 0x1FC760E7, 0x79B80C65, 0x27F459B4, 0x799A7326, 0x50BA1782, 0x2A116D5C, 0x63866E1B, 0x3F920E3C, 0x55023490, 0x55B56089, 0x2C391FD1, 0x2F8035C2, 0x64FD2B7A, 0x4CE8759A, 0x518504F0, 0x799501A8, 0x3F5B2CAD, 0x38E60160, 0x637641D8, 0x33352A42, 0x51A22C19, 0x085C5851, 0x032917AB, 0x2B770AC7, 0x30AC77B3, 0x2BEC1907, 0x035202D0, 0x0FA933D3, 0x61255DF3, 0x22AD06BF, 0x58B86971, 0x5FCA0DE5, 0x700D6456, 0x56A973DB, 0x5AB759FD, 0x330E0BE2, 0x5B3C0DDD, 0x495D3C60, 0x53BD59A6, 0x4C5E6D91, 0x49D9318D, 0x103D5079, 0x61CE42E3, 0x7ED5121D, 0x14E160ED, 0x212D4EF2, 0x270133F0, 0x62435A96, 0x1FA75E8B, 0x6F092FBE, 0x4A000D49, 0x57AE1C70, 0x004E2477, 0x561E7E72, 0x468C0033, 0x5DCC2402, 0x78507AC6, 0x58AF24C7, 0x0DF62D34, 0x358A4708, 0x3CFB1E11, 0x2B71451C, 0x77A75295, 0x56890721, 0x0FEF75F3, 0x120F24F1, 0x01990AE7, 0x339C4452, 0x27A15B8E, 0x0BA7276D, 0x60DC1B7B, 0x4F4B7F82, 0x67DB7007, 0x4F4A57D9, 0x621252E8, 0x20532CFC, 0x6A390306, 0x18800423, 0x19F3778A, 0x462316F0, 0x56AE0937, 0x43C2675C, 0x65CA45FD, 0x0D604FF2, 0x0BFD22CB, 0x3AFE643B, 0x3BF67FA6, 0x44623579, 0x184031F8, 0x32174F97, 0x4C6A092A, 0x5FB50261, 0x01650174, 0x33634AF1, 0x712D18F4, 0x6E997169, 0x5DAB7AFE, 0x7C2B2EE8, 0x6EDB75B4, 0x5F836FB6, 0x3C2A6DD6, 0x292D05C2, 0x052244DB, 0x149A5F4F, 0x5D486540, 0x331D15EA, 0x4F456920, 0x483A699F, 0x3B450F05, 0x3B207C6C, 0x749D70FE, 0x417461F6, 0x62B031F1, 0x2750577B, 0x29131533, 0x588C3808, 0x1AEF3456, 0x0F3C00EC, 0x7DA74742, 0x4B797A6C, 0x5EBB3287, 0x786558B8, 0x00ED4FF2, 0x6269691E, 0x24A2255F, 0x62C11F7E, 0x2F8A7DCD, 0x643B17FE, 0x778318B8, 0x253B60FE, 0x34BB63A3, 0x5B03214F, 0x5F1571F4, 0x1A316E9F, 0x7ACF2704, 0x28896838, 0x18614677, 0x1BF569EB, 0x0BA85EC9, 0x6ACA6B46, 0x1E43422A, 0x514D5F0E, 0x413E018C, 0x307626E9, 0x01ED1DFA, 0x49F46F5A, 0x461B642B, 0x7D7007F2, 0x13652657, 0x6B160BC5, 0x65E04849, 0x1F526E1C, 0x5A0251B6, 0x2BD73F69, 0x2DBF7ACD, 0x51E63E80, 0x5CF2670F, 0x21CD0A03, 0x5CFF0261, 0x33AE061E, 0x3BB6345F, 0x5D814A75, 0x257B5DF4, 0x0A5C2C5B, 0x16A45527, 0x16F23945 ``` ## 3.注册机 注册机最开始试着写了几个,没有成功,后面直接在solly大佬的基础上生成的,小编代码水平有限。 ``` import struct from typing import List # 类型定义 UCHAR = int UWORD = int ULONG = int UINT32 = int UINT64 = int # 常量定义 RLSDATE = 0x473C # 2019/12/06,发布日期 VERID = 0x0B # 当前版本ID FREE_UPGRADE_DAYS = 10549 # 免费升级天数 MAX_VERSION_ID = VERID + (FREE_UPGRADE_DAYS // 365) name_tables = [ 0x39CB44B8, 0x23754F67, 0x5F017211, 0x3EBB24DA, 0x351707C6, 0x63F9774B, 0x17827288, 0x0FE74821, 0x5B5F670F, 0x48315AE8, 0x785B7769, 0x2B7A1547, 0x38D11292, 0x42A11B32, 0x35332244, 0x77437B60, 0x1EAB3B10, 0x53810000, 0x1D0212AE, 0x6F0377A8, 0x43C03092, 0x2D3C0A8E, 0x62950CBF, 0x30F06FFA, 0x34F710E0, 0x28F417FB, 0x350D2F95, 0x5A361D5A, 0x15CC060B, 0x0AFD13CC, 0x28603BCF, 0x3371066B, 0x30CD14E4, 0x175D3A67, 0x6DD66A13, 0x2D3409F9, 0x581E7B82, 0x76526B99, 0x5C8D5188, 0x2C857971, 0x15F51FC0, 0x68CC0D11, 0x49F55E5C, 0x275E4364, 0x2D1E0DBC, 0x4CEE7CE3, 0x32555840, 0x112E2E08, 0x6978065A, 0x72921406, 0x314578E7, 0x175621B7, 0x40771DBF, 0x3FC238D6, 0x4A31128A, 0x2DAD036E, 0x41A069D6, 0x25400192, 0x00DD4667, 0x6AFC1F4F, 0x571040CE, 0x62FE66DF, 0x41DB4B3E, 0x3582231F, 0x55F6079A, 0x1CA70644, 0x1B1643D2, 0x3F7228C9, 0x5F141070, 0x3E1474AB, 0x444B256E, 0x537050D9, 0x0F42094B, 0x2FD820E6, 0x778B2E5E, 0x71176D02, 0x7FEA7A69, 0x5BB54628, 0x19BA6C71, 0x39763A99, 0x178D54CD, 0x01246E88, 0x3313537E, 0x2B8E2D17, 0x2A3D10BE, 0x59D10582, 0x37A163DB, 0x30D6489A, 0x6A215C46, 0x0E1C7A76, 0x1FC760E7, 0x79B80C65, 0x27F459B4, 0x799A7326, 0x50BA1782, 0x2A116D5C, 0x63866E1B, 0x3F920E3C, 0x55023490, 0x55B56089, 0x2C391FD1, 0x2F8035C2, 0x64FD2B7A, 0x4CE8759A, 0x518504F0, 0x799501A8, 0x3F5B2CAD, 0x38E60160, 0x637641D8, 0x33352A42, 0x51A22C19, 0x085C5851, 0x032917AB, 0x2B770AC7, 0x30AC77B3, 0x2BEC1907, 0x035202D0, 0x0FA933D3, 0x61255DF3, 0x22AD06BF, 0x58B86971, 0x5FCA0DE5, 0x700D6456, 0x56A973DB, 0x5AB759FD, 0x330E0BE2, 0x5B3C0DDD, 0x495D3C60, 0x53BD59A6, 0x4C5E6D91, 0x49D9318D, 0x103D5079, 0x61CE42E3, 0x7ED5121D, 0x14E160ED, 0x212D4EF2, 0x270133F0, 0x62435A96, 0x1FA75E8B, 0x6F092FBE, 0x4A000D49, 0x57AE1C70, 0x004E2477, 0x561E7E72, 0x468C0033, 0x5DCC2402, 0x78507AC6, 0x58AF24C7, 0x0DF62D34, 0x358A4708, 0x3CFB1E11, 0x2B71451C, 0x77A75295, 0x56890721, 0x0FEF75F3, 0x120F24F1, 0x01990AE7, 0x339C4452, 0x27A15B8E, 0x0BA7276D, 0x60DC1B7B, 0x4F4B7F82, 0x67DB7007, 0x4F4A57D9, 0x621252E8, 0x20532CFC, 0x6A390306, 0x18800423, 0x19F3778A, 0x462316F0, 0x56AE0937, 0x43C2675C, 0x65CA45FD, 0x0D604FF2, 0x0BFD22CB, 0x3AFE643B, 0x3BF67FA6, 0x44623579, 0x184031F8, 0x32174F97, 0x4C6A092A, 0x5FB50261, 0x01650174, 0x33634AF1, 0x712D18F4, 0x6E997169, 0x5DAB7AFE, 0x7C2B2EE8, 0x6EDB75B4, 0x5F836FB6, 0x3C2A6DD6, 0x292D05C2, 0x052244DB, 0x149A5F4F, 0x5D486540, 0x331D15EA, 0x4F456920, 0x483A699F, 0x3B450F05, 0x3B207C6C, 0x749D70FE, 0x417461F6, 0x62B031F1, 0x2750577B, 0x29131533, 0x588C3808, 0x1AEF3456, 0x0F3C00EC, 0x7DA74742, 0x4B797A6C, 0x5EBB3287, 0x786558B8, 0x00ED4FF2, 0x6269691E, 0x24A2255F, 0x62C11F7E, 0x2F8A7DCD, 0x643B17FE, 0x778318B8, 0x253B60FE, 0x34BB63A3, 0x5B03214F, 0x5F1571F4, 0x1A316E9F, 0x7ACF2704, 0x28896838, 0x18614677, 0x1BF569EB, 0x0BA85EC9, 0x6ACA6B46, 0x1E43422A, 0x514D5F0E, 0x413E018C, 0x307626E9, 0x01ED1DFA, 0x49F46F5A, 0x461B642B, 0x7D7007F2, 0x13652657, 0x6B160BC5, 0x65E04849, 0x1F526E1C, 0x5A0251B6, 0x2BD73F69, 0x2DBF7ACD, 0x51E63E80, 0x5CF2670F, 0x21CD0A03, 0x5CFF0261, 0x33AE061E, 0x3BB6345F, 0x5D814A75, 0x257B5DF4, 0x0A5C2C5B, 0x16A45527, 0x16F23945 ] def get_uncheck2(ul: ULONG) -> UWORD: """计算用户数的校验""" a = ul * 11 return ((a ^ 0x3421) - 0x4D30) ^ 0x7892 def get_uncheck3(base: ULONG, rlsdate: ULONG) -> ULONG: """计算免费升级天数的校验(AC类型)""" end_of_date = rlsdate + FREE_UPGRADE_DAYS return (((end_of_date * 0x11) ^ 0xFFE53167) + 0x0002C175) ^ 0x0022C078 ^ base def get_name_check(name: str, is_registed: ULONG, free_days: ULONG, user_count: ULONG) -> ULONG: """计算用户名的校验值""" name_check = 0 idx3 = 0 idx2 = 0 n = len(name) idx4 = user_count * 15 idx1 = free_days * 17 for i in range(n): ch = name[i] idx0 = ord(ch.upper()) & 0xFF name_check += name_tables[idx0] if is_registed != 0: # 正式注册版 name_check ^= name_tables[(idx0 + 0x0D) & 0xFF] name_check *= name_tables[(idx0 + 0x2F) & 0xFF] name_check += name_tables[idx1 & 0xFF] name_check += name_tables[idx4 & 0xFF] name_check += name_tables[idx2 & 0xFF] else: # 扩展评估版 name_check ^= name_tables[(idx0 + 0x3F) & 0xFF] name_check *= name_tables[(idx0 + 0x17) & 0xFF] name_check += name_tables[idx1 & 0xFF] name_check += name_tables[idx4 & 0xFF] name_check += name_tables[idx3 & 0xFF] idx2 = (idx2 + 0x13) & 0xFF idx1 = (idx1 + 0x09) & 0xFF idx4 = (idx4 + 0x0D) & 0xFF idx3 = (idx3 + 0x07) & 0xFF return name_check & 0xFFFFFFFF # 确保是32位 def generate_ac_license(name: str, user_count: int = 1000) -> str: """ 生成 AC 类型(全功能版,支持免费升级)的注册码 Args: name: 用户名 user_count: 用户数量(1=单用户,2-999=多用户,1000=站点许可证) """ # 初始化 SN 数组 sn_int = [0x11, 0x11, 0x22, 0x22, 0x33, 0x33, 0x44, 0x44, 0x55, 0x55] # 设置许可证类型为 AC(全功能版,支持免费升级) license_type = 0xAC sn_int[3] = license_type # 计算免费升级结束日期 license_free_upgrade_end_of_date = RLSDATE + FREE_UPGRADE_DAYS print(f"免费升级结束日期: 0x{license_free_upgrade_end_of_date:08X}") # 计算用户名校验 license_register_type = 1 # 正式注册版 name_check = get_name_check(name, license_register_type, license_free_upgrade_end_of_date, user_count) print(f"用户名校验值: 0x{name_check:08X}") # 将用户名校验值写入 SN sn_int[4] = (name_check) & 0xFF sn_int[5] = (name_check >> 8) & 0xFF sn_int[6] = (name_check >> 16) & 0xFF sn_int[7] = (name_check >> 24) & 0xFF # 计算用户数校验 user_count_encoded = get_uncheck2(user_count) print(f"用户数校验: 0x{user_count_encoded:04X}") # 将用户数校验写入 SN sn_int[1] = ((user_count_encoded >> 8) & 0xFF) ^ sn_int[7] sn_int[2] = (user_count_encoded & 0xFF) ^ sn_int[5] # 计算免费升级天数校验 sn_date = get_uncheck3(0x005B8C27, RLSDATE) sn_date &= 0x00FFFFFF print(f"免费升级天数编码: 0x{sn_date:08X}") # 将免费升级天数校验写入 SN sn_int[0] = (sn_date & 0xFF) ^ sn_int[6] sn_int[8] = ((sn_date >> 8) & 0xFF) ^ sn_int[4] sn_int[9] = ((sn_date >> 16) & 0xFF) ^ sn_int[5] # 格式化为注册码字符串 license_key = f"{sn_int[0]:02X}{sn_int[1]:02X}-{sn_int[2]:02X}{sn_int[3]:02X}-" \ f"{sn_int[4]:02X}{sn_int[5]:02X}-{sn_int[6]:02X}{sn_int[7]:02X}-" \ f"{sn_int[8]:02X}{sn_int[9]:02X}" return license_key def main(): """主函数""" print("=== AC 类型(全功能版,支持免费升级)注册机 ===\n") # 获取用户名 name = input("请输入用户名: ").strip() if not name: name = "admin" # 获取用户数量 try: user_input = input("请输入用户数量 (1=单用户, 2-999=多用户, 1000=站点许可证) [默认1000]: ").strip() if not user_input: user_count = 1000 else: user_count = int(user_input) except ValueError: print("输入无效,使用默认值1000") user_count = 1000 # 确保用户数量在有效范围内 if user_count < 1: user_count = 1 elif user_count > 1000: user_count = 1000 print(f"\n生成注册信息...") print(f"用户名: {name}") print(f"用户数量: {user_count}") # 生成注册码 license_key = generate_ac_license(name, user_count) print(f"\n生成的注册码:") print(f"SN: {license_key}") # 显示注册信息 print(f"\n注册信息:") print(f"- 许可证类型: AC (全功能版,支持免费升级)") print(f"- 发布日期: 2019/12/06") print(f"- 免费升级截止: 2048/10/24") print(f"- 用户数量: {user_count}") print(f"- 版本ID: {VERID}") if __name__ == "__main__": main() ```   ## 5.网络认证 定位 ``` 00000001402B1BF8 41:B8 4C4F0000 mov r8d,0x4F4C 18236 00000001402B1BFE 48:8B0D B371D400 mov rcx,qword ptr ds:[0x140FF8DB8] 第3次引用140ff8db8 00000001402B1C05 E8 5521D5FF call 010editor.140003D5F 注册码检查 00000001402B1C0A 44:8BF0 mov r14d,eax 00000001402B1C0D BA 11000000 mov edx,0x11 00000001402B1C12 41:B8 4C4F0000 mov r8d,0x4F4C 18236 00000001402B1C18 48:8B0D 9971D400 mov rcx,qword ptr ds:[0x140FF8DB8] 第4次引用140ff8db8 00000001402B1C1F E8 596CD5FF call 010editor.14000887D 注册码检查 及 试用期扩充的检查 00000001402B1C24 8BD8 mov ebx,eax 00000001402B1C26 48:8B0D 8B71D400 mov rcx,qword ptr ds:[0x140FF8DB8] 第5次引用140ff8db8 00000001402B1C2D 41:81FE E7000000 cmp r14d,0xE7 00000001402B1C34 0F84 43010000 je 010editor.1402B1D7D 成功标志3, r14d=0xe7 00000001402B1C3A 8379 7C 00 cmp dword ptr ds:[rcx+0x7C],0x0 是否进行网络验证,0-不验证,1-验证 00000001402B1C3E 0F84 39010000 je 010editor.1402B1D7D 成功标志4,与3选一 00000001402B1C44 33D2 xor edx,edx 00000001402B1C46 48:8BCE mov rcx,rsi 00000001402B1C49 E8 CDCDD5FF call 010editor.14000EA1B 网络验证 00000001402B1C4E 85C0 test eax,eax eax 为返回验证id 00000001402B1C50 0F89 E0000000 jns 010editor.1402B1D36 非负跳转,eax非负,需要再次判断 00000001402B1C56 41:B8 0A000000 mov r8d,0xA 0A:'\n' 00000001402B1C5C 8BD0 mov edx,eax 00000001402B1C5E 48:8D4D E7 lea rcx,qword ptr ss:[rbp-0x19] ``` 再ida中网络验证代码如下 ``` // Hidden C++ exception states: #wind=5 __int64 __fastcall sub_7FF6BB3E4320(int a1, int a2) { QByteArray *v4; // rax char *v5; // rax _BYTE *v6; // rdx char v7; // cl __int64 v8; // rax __int64 v9; // rax QByteArray *v10; // rax char *v11; // rax _BYTE *v12; // rdx char v13; // cl char *v14; // rcx char *v15; // rcx char *v16; // rcx char *v17; // rcx char *v18; // rdx char *v19; // rcx char *v20; // rcx char *v21; // rcx char *v22; // rcx char *v23; // rcx char *v24; // rcx char *v25; // rcx char *v26; // rcx char *v27; // rcx char *v28; // rcx char *v29; // rcx char *v30; // rcx char *v31; // rcx char *v32; // rcx char *v33; // rcx char *v34; // rcx char *v35; // rcx char *v36; // rcx char *v37; // rcx char *v38; // rcx char *v39; // rcx char *v40; // rcx char *v41; // rcx char *v42; // rcx char *v43; // rcx char *v44; // rcx char *v45; // rcx int v46; // ebx unsigned int v47; // ebx unsigned int v49; // [rsp+30h] [rbp-D0h] BYREF void *Block; // [rsp+38h] [rbp-C8h] BYREF _BYTE v51[24]; // [rsp+40h] [rbp-C0h] BYREF _BYTE v52[24]; // [rsp+58h] [rbp-A8h] BYREF _BYTE v53[31]; // [rsp+70h] [rbp-90h] BYREF char v54; // [rsp+8Fh] [rbp-71h] BYREF char v55[2048]; // [rsp+90h] [rbp-70h] BYREF char Buffer[1024]; // [rsp+890h] [rbp+790h] BYREF _BYTE v57[512]; // [rsp+C90h] [rbp+B90h] BYREF _BYTE v58[512]; // [rsp+E90h] [rbp+D90h] BYREF Block = 0LL; v49 = 1024; if ( qword_7FF6BC128DB8 ) { QString::QString(v51, (qword_7FF6BC128DB8 + 24)); v4 = QString::toUtf8(v51, v52); v5 = QByteArray::data(v4); v6 = (v57 - v5); do { v7 = *v5; v5[v6] = *v5; ++v5; } while ( v7 ); QByteArray::~QByteArray(v52); QString::~QString(v51); v8 = sub_7FF6BB138C0B(qword_7FF6BC128DB8, v53); v9 = replaceConfusingCharacters_7FF6DED588EB(qword_7FF6BC128DB8, v51, v8); v10 = QString::toLocal8Bit(v9, v52); v11 = QByteArray::data(v10); v12 = (v58 - v11); do { v13 = *v11; v11[v12] = *v11; ++v11; } while ( v13 ); QByteArray::~QByteArray(v52); QString::~QString(v51); QString::~QString(v53); sub_7FF6BB1396BF(qword_7FF6BC128DB8, v57); strcpy(v55, "https://www.sweetscape.com/"); v14 = &v54; do ++v14; while ( *v14 ); strcpy(v14, "cgibin"); v15 = &v54; do ++v15; while ( *v15 ); *v15 = 47; v16 = &v54; do ++v16; while ( *v16 ); strcpy(v16, "010editor"); v17 = &v54; do ++v17; while ( *v17 ); *v17 = 95; v18 = &v54; do ++v18; while ( *v18 ); strcpy(v18, "check"); v19 = &v54; do ++v19; while ( *v19 ); *v19 = 95; v20 = &v54; do ++v20; while ( *v20 ); *v20 = 0x65736E6563696CLL; v21 = &v54; do ++v21; while ( *v21 ); *v21 = 95; v22 = &v54; do ++v22; while ( *v22 ); *v22 = 57; v23 = &v54; do ++v23; while ( *v23 ); *v23 = 98; v24 = &v54; do ++v24; while ( *v24 ); *v24 = 46; v25 = &v54; do ++v25; while ( *v25 ); *v25 = 7366768; v26 = &v54; do ++v26; while ( *v26 ); strcpy(v26, "?"); v27 = &v54; do ++v27; while ( *v27 ); *v27 = 116; v28 = &v54; do ++v28; while ( *v28 ); *v28 = 61; sub_7FF6BB13C82E(v57, Buffer); v29 = &v54; do ++v29; while ( *v29 ); strcpy(v29, Buffer); v30 = &v54; do ++v30; while ( *v30 ); strcpy(v30, "&"); v31 = &v54; do ++v31; while ( *v31 ); *v31 = 7173491; v32 = &v54; do ++v32; while ( *v32 ); *v32 = 61; sub_7FF6BB13C82E(v58, Buffer); v33 = &v54; do ++v33; while ( *v33 ); strcpy(v33, Buffer); v34 = &v54; do ++v34; while ( *v34 ); strcpy(v34, "&"); v35 = &v54; do ++v35; while ( *v35 ); strcpy(v35, "id"); v36 = &v54; do ++v36; while ( *v36 ); *v36 = 61; sub_7FF6BB13D319(Buffer, "%d"); v37 = &v54; do ++v37; while ( *v37 ); strcpy(v37, Buffer); v38 = &v54; do ++v38; while ( *v38 ); strcpy(v38, "&"); v39 = &v54; do ++v39; while ( *v39 ); *v39 = 7039075; v40 = &v54; do ++v40; while ( *v40 ); *v40 = 61; sub_7FF6BB13D319(Buffer, "%d"); v41 = &v54; do ++v41; while ( *v41 ); strcpy(v41, Buffer); v42 = &v54; do ++v42; while ( *v42 ); strcpy(v42, "&"); v43 = &v54; do ++v43; while ( *v43 ); *v43 = 7371124; v44 = &v54; do ++v44; while ( *v44 ); *v44 = 61; sub_7FF6BB13D319(Buffer, "%d"); v45 = &v54; do ++v45; while ( *v45 ); strcpy(v45, Buffer); QString::QString(v51, v55); if ( a2 ) { sub_7FF6BB13F8B7(qword_7FF6BC128DD8, v51, 0, a1, "1On_HttpCheckLicenseFinished(char*,int)"); QString::~QString(v51); return 0LL; } v46 = sub_7FF6BB1399F3(qword_7FF6BC128DD8, v51, &Block, &v49, 1, 0LL); QString::~QString(v51); if ( !v46 ) { v47 = sub_7FF6BB132B44(qword_7FF6BC128DB8, Block, v49); j_j_j_j_free_0(Block); return v47; } } return 0xFFFFFFFFLL; } ``` 错误提示如下  下面对里面函数解析 其中在sub_7FF6BB13C82E(v58, Buffer);这个里面主要是对密钥与字符串做的替换 ``` // Hidden C++ exception states: #wind=5 void __fastcall sub_7FF6BB6014C0(char *a1, _BYTE *a2) { char v2; // r10 char v3; // r11 char *v4; // r9 char v5; // al unsigned __int8 v6; // r8 char v7; // cl unsigned __int8 v8; // r8 char v9; // al v2 = *a1; v3 = 0; v4 = a1; if ( *a1 ) { do { ++v4; a2 += 2; v5 = 48; v6 = v2 ^ (93 - 83 * v3); v7 = v6 >> 4; if ( (v6 >> 4) >= 0xAu ) v5 = 55; v8 = v6 & 0xF; *(a2 - 2) = v7 + v5; v9 = 48; if ( v8 >= 0xAu ) v9 = 55; ++v3; *(a2 - 1) = v8 + v9; v2 = *v4; } while ( *v4 ); *a2 = 0; } else { *a2 = 0; } } ``` 函数在栈上构造一个URL字符串,格式如下: <mark class="encrypted">3ccK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6%4N6%4N6Q4x3X3g2K6N6$3g2W2N6s2y4U0j5i4m8W2i4K6u0W2j5$3!0E0i4K6u0r3j5$3N6A6j5X3W2F1i4K6u0r3x3o6p5H3k6h3c8A6N6r3!0J5i4K6g2X3j5$3S2W2j5$3E0Q4y4h3k6D9K9h3y4W2L8Y4y4W2i4K6g2X3z5h3u0Q4x3X3g2H3K9s2m8Q4x3@1k6@1i4K6y4p5i4K6g2n7N6X3q4D9N6h3g2Q4y4f1c8Q4x3U0k6K6i4K6y4p5i4K6g2n7N6X3q4D9N6h3g2Q4y4f1c8Q4x3U0k6A6k6q4)9K6c8q4)9#2b7Y4k6S2L8s2g2W2i4K6g2p5i4K6t1$3N6X3g2J5i4K6y4p5i4K6g2n7N6X3q4D9N6h3g2Q4y4f1c8Q4x3U0k6T1K9h3c8Q4x3@1c8Q4y4f1u0$3j5h3I4#2k6g2)9#2c8l9`.`.</mark> 测试 00000045D10F48A8 0000020D5EB625B0 L"<mark class="encrypted">deeK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6%4N6%4N6Q4x3X3g2K6N6$3g2W2N6s2y4U0j5i4m8W2i4K6u0W2j5$3!0E0i4K6u0r3j5$3N6A6j5X3W2F1i4K6u0r3x3o6p5H3k6h3c8A6N6r3!0J5i4K6g2X3j5$3S2W2j5$3E0Q4y4h3k6D9K9h3y4W2L8Y4y4W2i4K6g2X3z5h3u0Q4x3X3g2H3K9s2m8Q4x3@1k6@1i4K6y4p5x3K6x3$3x3@1x3J5x3o6j5%4z5o6R3^5y4f1b7J5c8g2)9J5y4Y4y4#2L8g2)9K6c8o6k6o6x3@1t1^5x3e0t1J5x3@1x3^5b7K6f1^5y4e0V1^5y4U0g2r3y4f1b7^5c8e0c8p5y4U0c8r3c8f1t1I4x3e0R3&6b7@1t1$3x3e0W2m8y4@1x3^5y4@1k6p5c8W2)9J5y4X3W2V1i4K6y4p5x3q4)9J5y4X3y4Z5K9#2)9K6c8o6f1K6x3K6j5%4i4K6t1$3N6s2W2H3i4K6y4p5x3l9`.`.</mark>"  下面我们进行破解就很简单,直接改跳转  0x00000001402B1D36将这个地址改成上面两个跳转的地址0x00000001402B1D7D 对响应进行分析 ``` __int64 __fastcall sub_7FF67E9FD990(__int64 a1, const char *a2, int a3) { size_t v3; // rbx char *v6; // rsi __int64 v8; // r8 __int64 v9; // rcx char v10; // al __int64 v11; // rax __int64 v12; // rcx bool v13; // zf int v14; // eax int v15; // eax __int64 v16; // rdx __int64 v17; // rcx __int64 v18; // r8 __int64 v19; // r9 int v20; // eax char String[1040]; // [rsp+30h] [rbp-438h] BYREF v3 = a3; v6 = j_j_j___2_YAPEAX_K_Z(a3 + 1LL); strncpy(v6, a2, v3); v6[v3] = 0; if ( sub_7FF67E5C5A6F(a1, v6, "ss", String, 1024) >= 0 ) { v8 = 0LL; v9 = 0LL; while ( 1 ) { v10 = String[v9++]; if ( v10 != aError_1[v9 - 1] ) // 判断是不是error break; if ( v9 == 6 ) { j_j_j_j_free_0(v6); return 0xFFFFFFFALL; } } v11 = 0LL; while ( 1 ) { v12 = String[v11++]; v13 = v12 == aInvalid[v11 - 1]; // 判断invalid 无效 if ( v12 != aInvalid[v11 - 1] ) break; if ( v11 == 8 ) { v13 = v12 == aInvalid[7]; break; } } LOBYTE(v8) = v13; *(a1 + 124) = v8; v14 = sub_7FF67E5C3FA3(v12, String, v8, "invalid"); sub_7FF67E5C2860(v14); if ( *(a1 + 124) == 1 ) { j_j_j_j_free_0(v6); return 0LL; } else if ( sub_7FF67E5C5A6F(a1, v6, &word_7FF67ED820A0, String, 1024) >= 0 )// 读取并验证ID字段 { v15 = atoi(String); *(a1 + 120) = v15; if ( v15 ) { v20 = sub_7FF67E5C3FA3(v17, v16, v18, v19); sub_7FF67E5C2EE6(v20); // 注册表写入id与1690216811 j_j_j_j_free_0(v6); return *(a1 + 120); // 返回ID } else { j_j_j_j_free_0(v6); return 0xFFFFFFFDLL; // ID为0,无效 } } else { j_j_j_j_free_0(v6); return 4294967292LL; } } else { j_j_j_j_free_0(v6); return 4294967289LL; } } ``` 本地做一个验证服务器 ``` import socket import sys import threading def handle_client(client_socket, client_addr): """处理客户端连接""" try: # 接收数据 request_data = client_socket.recv(999).decode('utf-8') if not request_data: return print(f"接收来自 {client_addr} 的数据:") print(request_data) print("-" * 50) # 检查是否包含退出指令 if "?quit=1" in request_data: print("收到退出指令,服务器将关闭") return "quit" # 准备响应数据 response_body = "<ss><id>33333</id></ss>" response_headers = [ "HTTP/1.0 200 OK", "Content-type: text/html", f"Content-length: {len(response_body)}", "", response_body ] response = "\r\n".join(response_headers) print("发送响应数据:") print(response) print("-" * 50) # 发送响应 client_socket.send(response.encode('utf-8')) except Exception as e: print(f"处理客户端 {client_addr} 时出错: {e}") finally: # 关闭连接 try: client_socket.shutdown(socket.SHUT_RDWR) except: pass client_socket.close() def start_http_server(host='127.0.0.1', port=80): """启动HTTP服务器""" # 创建socket try: server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) except socket.error as e: print(f"创建socket失败: {e}") return False # 绑定地址和端口 try: server_socket.bind((host, port)) server_socket.listen(10) except socket.error as e: print(f"绑定端口 {port} 失败: {e}") print("请确保有权限使用80端口,或尝试使用其他端口") server_socket.close() return False print(f"WebServer 已启动...") print(f"访问 http://{host}:{port}") print(f"退出服务器,请访问 http://{host}:{port}/?quit=1") print("-" * 60) try: while True: try: # 接受客户端连接 client_socket, client_addr = server_socket.accept() print(f"新连接来自: {client_addr}") # 处理客户端请求 result = handle_client(client_socket, client_addr) if result == "quit": break except socket.error as e: print(f"接受连接时出错: {e}") continue except KeyboardInterrupt: print("\n收到中断信号,服务器关闭") except Exception as e: print(f"服务器运行出错: {e}") finally: server_socket.close() print("服务器已关闭") return True def start_server_with_fallback(): """尝试启动服务器,如果80端口被占用则使用备用端口""" # 尝试的端口列表 ports_to_try = [80, 8080, 8000, 8888] for port in ports_to_try: print(f"尝试启动在端口 {port}...") if start_http_server('127.0.0.1', port): break else: print(f"端口 {port} 启动失败,尝试下一个端口...") if port == ports_to_try[-1]: print("所有端口尝试失败,请检查系统权限或端口占用情况") if __name__ == "__main__": print("Python HTTP 服务器") print("=" * 60) # 检查管理员权限(在Windows上需要管理员权限才能绑定80端口) if sys.platform == "win32": try: # 尝试绑定80端口来检查权限 test_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) test_socket.bind(('127.0.0.1', 80)) test_socket.close() print("检测到有权限使用80端口") start_http_server() except PermissionError: print("没有管理员权限,无法使用80端口") print("将尝试使用备用端口...") start_server_with_fallback() except Exception as e: print(f"检测权限时出错: {e}") start_server_with_fallback() else: # 在Linux/macOS上,可能需要sudo权限 start_server_with_fallback() ``` 然后hosts文件中加上如下数据: 127.0.0.1 <mark class="encrypted">18dK9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8Y4N6%4N6#2)9J5k6i4y4%4k6h3g2@1M7$3y4S2M7r3g2Q4x3X3g2U0L8$3@1`.</mark> 经过调试  此处发送数据包 对应v46 = sub_7FF67E5C99F3(qword_7FF67F5B8DD8, v51, &Block, &v49, 1, 0LL);// HTTP请求发送  然后再此处写入id可对应注册表查看  如果是 1690216811,表示成功,这个数字解码后是 0,如果是 1690236428,则表示失败,这个数字解码后是 1(这块解码没有跟进,直接照搬)   最后成功,也就是注册表中有这两个注册信息就可以绕过网络验证了 Solly大佬文章链接:<mark class="encrypted">68bK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6%4N6%4N6Q4x3X3f1#2x3Y4m8G2K9X3W2W2i4K6u0W2j5$3&6Q4x3V1k6@1K9s2u0W2j5h3c8Q4x3X3b7I4x3o6V1#2z5o6f1#2i4K6u0V1x3g2)9J5k6o6q4Q4x3X3g2Z5N6r3#2D9</mark>
冰与火的战歌:Windows内核攻防实战高级班!从零到实战,融合AI与Windows内核攻防全技术栈,打造具备自动化能力的内核开发高手。
#调试逆向
收藏
・
16
点赞
・
6
打赏
分享
分享到微信
分享到QQ
分享到微博
赞赏记录
参与人
雪币
留言
时间
abxy
感谢你的积极参与,期待更多精彩内容!
2026-1-27 09:55
AStephen
非常支持你的观点!
2026-1-26 08:53
东方玻璃
期待更多优质内容的分享,论坛有你更精彩!
2026-1-23 23:08
jyotidwi
+1
感谢你的积极参与,期待更多精彩内容!
2026-1-23 10:29
axiong
+6
为你点赞!
2026-1-21 14:32
Mezone0
谢谢你的细致分析,受益匪浅!
2026-1-21 13:08
查看更多
赞赏
×
1 雪花
5 雪花
10 雪花
20 雪花
50 雪花
80 雪花
100 雪花
150 雪花
200 雪花
支付方式:
微信支付
赞赏留言:
快捷留言
感谢分享~
精品文章~
原创内容~
精彩转帖~
助人为乐~
感谢分享~
最新回复
(
6
)
xingbing
雪 币:
158
活跃值:
(5786)
能力值:
( LV2,RANK:10 )
在线值:
发帖
2
回帖
1400
粉丝
2
关注
私信
xingbing
2
楼
学习!!!
2026-1-21 16:00
0
mb_mstvogpf
雪 币:
能力值:
( LV1,RANK:0 )
在线值:
发帖
0
回帖
1
粉丝
0
关注
私信
mb_mstvogpf
3
楼
nb
2026-1-22 17:07
0
jyotidwi
雪 币:
1349
能力值:
( LV1,RANK:0 )
在线值:
发帖
3
回帖
121
粉丝
3
关注
私信
jyotidwi
4
楼
Study!!!
2026-1-23 10:29
0
逆天而行
雪 币:
4725
活跃值:
(7322)
能力值:
( LV6,RANK:90 )
在线值:
发帖
12
回帖
124
粉丝
247
关注
私信
逆天而行
1
5
楼
你这名字取的 很牛马
2026-1-24 16:22
0
毛民8868
雪 币:
766
活跃值:
(576)
能力值:
( LV2,RANK:10 )
在线值:
发帖
2
回帖
7
粉丝
2
关注
私信
毛民8868
6
楼
逆天而行
你这名字取的 很牛马
牛马人过牛马年取牛马名做牛马活
2026-1-28 15:20
0
凌ice
雪 币:
61
活跃值:
(605)
能力值:
( LV2,RANK:10 )
在线值:
发帖
0
回帖
5
粉丝
0
关注
私信
凌ice
7
楼
牛哇
2026-4-20 23:53
0
游客
登录
|
注册
方可回帖
回帖
表情
雪币赚取及消费
高级回复
返回
毛民8868
2
发帖
7
回帖
10
RANK
关注
私信
他的文章
[原创]某平台文件加密分析
1606
[分享]010算法分析,附带注册机,网络验证分析
3761
关于我们
联系我们
企业服务
看雪公众号
专注于PC、移动、智能设备安全研究及逆向工程的开发者社区
看原图
赞赏
×
雪币:
+
留言:
快捷留言
为你点赞!
返回
顶部