学习破解 Navicat Premium 17.3.4
2025-10-03 16:18:00

摸着石头过河,参照以下文章自己手动实践一次:
XX XX v12.0.23.0 破解教程x86,x64通用,手动破解,不要补丁,无病毒
Navicat 17 破解教程
Navicat for Sqlite 17.3.2 英文版 一字节替换RSA公钥
记破解Navicat Premium 17.3.4过程


软件下载地址:https://www.navicat.com.cn/download/navicat-premium

找到公钥

根据前人的经验,用 x64dbg 在 libcc.dll 模块中调试搜索汇编特征在“搜索匹配特征”窗口中,使用十六进制搜索模式查找字节序列41 FF D0 90 48 8B D0 48 8B CF,该字节序列对应以下汇编指令。

1
2
3
4
call r8
nop
mov rdx,rax
mov rcx,rdi

到这里

1
2
3
4
5
00007FF8C33EE637 | 41:FFD0                  | call r8      // 拼接公钥字符串
00007FF8C33EE63A | 90 | nop
00007FF8C33EE63B | 48:8BD0 | mov rdx,rax
00007FF8C33EE63E | 48:8BCF | mov rcx,rdi
00007FF8C33EE641 | FFD3 | call rbx

libcc+214E63A处设置断点,也就是call r8之后的一行,此时在rax中就是公钥。

1
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAw1dqF3SkCaAAmMzs889IqdW9M2dIdh3jG9yPcmLnmJiGpBF4E9VHSMGe8oPAy2kJDmdNt4BcEygvssEfginva5t5jm352UAoDosUJkTXGQhpAWMF4fBmBpO3EedG62rOsqMBgmSdAyxCSPBRJIOFR0QgZFbRnU0frj34fiVmgYiLuZSAmIbs8ZxiHPdp1oD4tUpvsFci4QJtYNjNnGU2WPH6rvChGl1IRKrxMtqLielsvajUjyrgOC6NmymYMvZNER3htFEtL1eQbCyTfDmtYyQ1Wt4Ot12lxf0wVIR5mcGN7XCXJRHOFHSf1gzXWabRSvmt1nrl7sW6cjxljuuQawIDAQAB

最开始我直接从 x64dbg 中拷贝出公钥,新建了一个不带换行的公钥文件

1
2
3
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAw1dqF3SkCaAAmMzs889IqdW9M2dIdh3jG9yPcmLnmJiGpBF4E9VHSMGe8oPAy2kJDmdNt4BcEygvssEfginva5t5jm352UAoDosUJkTXGQhpAWMF4fBmBpO3EedG62rOsqMBgmSdAyxCSPBRJIOFR0QgZFbRnU0frj34fiVmgYiLuZSAmIbs8ZxiHPdp1oD4tUpvsFci4QJtYNjNnGU2WPH6rvChGl1IRKrxMtqLielsvajUjyrgOC6NmymYMvZNER3htFEtL1eQbCyTfDmtYyQ1Wt4Ot12lxf0wVIR5mcGN7XCXJRHOFHSf1gzXWabRSvmt1nrl7sW6cjxljuuQawIDAQAB
-----END PUBLIC KEY-----

这和我们常见的密钥文件不同,它没有换行,于是有些在线工具不兼容,比如 http://tool.chacuo.net/cryptrsakeyparse
但是有的在线兼容性更强,比如 https://www.oren.net.cn/rsa/info.html
不过无论如何,这种一行 Base64 编码的证书是不标准的。
AI对此的回答:

PEM 格式最初是在 RFC 1421 中定义的,它的全称是“隐私增强邮件”(Privacy-Enhanced Mail)。这个标准被设计用来在电子邮件中安全地传输加密数据。
标准规定:该标准明确规定,Base64 编码后的数据文本,每行长度不应超过 64 个字符。
行业惯例:因此,所有标准的加密工具(如 OpenSSL)在生成 PEM 格式的密钥或证书时,都会严格遵守这个规定,自动在每 64 个字符后添加一个换行符 (\n)。

所以标准的公钥文件内容应该是这样的:

1
2
3
4
5
6
7
8
9
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAw1dqF3SkCaAAmMzs889I
qdW9M2dIdh3jG9yPcmLnmJiGpBF4E9VHSMGe8oPAy2kJDmdNt4BcEygvssEfginv
a5t5jm352UAoDosUJkTXGQhpAWMF4fBmBpO3EedG62rOsqMBgmSdAyxCSPBRJIOF
R0QgZFbRnU0frj34fiVmgYiLuZSAmIbs8ZxiHPdp1oD4tUpvsFci4QJtYNjNnGU2
WPH6rvChGl1IRKrxMtqLielsvajUjyrgOC6NmymYMvZNER3htFEtL1eQbCyTfDmt
YyQ1Wt4Ot12lxf0wVIR5mcGN7XCXJRHOFHSf1gzXWabRSvmt1nrl7sW6cjxljuuQ
awIDAQAB
-----END PUBLIC KEY-----

通过前面的在线工具分解出模数(N):

1
C3576A1774A409A00098CCECF3CF48A9D5BD336748761DE31BDC8F7262E7989886A4117813D54748C19EF283C0CB69090E674DB7805C13282FB2C11F8229EF6B9B798E6DF9D940280E8B142644D7190869016305E1F0660693B711E746EB6ACEB2A30182649D032C4248F0512483854744206456D19D4D1FAE3DF87E256681888BB994809886ECF19C621CF769D680F8B54A6FB05722E1026D60D8CD9C653658F1FAAEF0A11A5D4844AAF132DA8B89E96CBDA8D48F2AE0382E8D9B299832F64D111DE1B4512D2F57906C2C937C39AD6324355ADE0EB75DA5C5FD3054847999C18DED70972511CE14749FD60CD759A6D14AF9ADD67AE5EEC5BA723C658EEB906B

指数(e):

1
65537 (0x10001)

构造新的私钥、公钥

下载 RSA 一字节替换工具:
https://www.52pojie.cn/thread-2008824-1-1.html
https://www.chinapyg.com/thread-158380-1-1.html


计算得到两种结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
替换位置 0 :C3 --> BD
new N: BD576A1774A409A00098CCECF3CF48A9D5BD336748761DE31BDC8F7262E7989886A4117813D54748C19EF283C0CB69090E674DB7805C13282FB2C11F8229EF6B9B798E6DF9D940280E8B142644D7190869016305E1F0660693B711E746EB6ACEB2A30182649D032C4248F0512483854744206456D19D4D1FAE3DF87E256681888BB994809886ECF19C621CF769D680F8B54A6FB05722E1026D60D8CD9C653658F1FAAEF0A11A5D4844AAF132DA8B89E96CBDA8D48F2AE0382E8D9B299832F64D111DE1B4512D2F57906C2C937C39AD6324355ADE0EB75DA5C5FD3054847999C18DED70972511CE14749FD60CD759A6D14AF9ADD67AE5EEC5BA723C658EEB906B
new P: 11
new Q: B234279D9AF4BDC3C45393B1D668BCDC1475D606DAC9858A749359D511CAE9F8F7310161F48C7F53890E116CF1B062DB58D9B28E96ED3F34E1992E3BC5CD1D9274362BB2CD08B4DA680A6D5131BB62DABD2E7B50D4A605ABD651F2BB8E0ABEE0A820F25C9AEE211A98BD1E6A7CB804F7C7A6041579FD75C376D0E9E023334CBCBFBDB8F1807EFD1FA23E3961548DA68FB9AF782D7F2FE2D51BA671B27513F6EA4D282C2DC4CD84F8B9195B7B280ADC26FCEEBD044A82B4E99539FB727120E7D0101C1FB8C4DF3B9DB51A844E93091BA89A8C91C1EFBBA36ED875D3225E9090B62B39D36113D485B8E63C14C0CAAEBB1F559FB2ABBEF6865FBE89A24177744BB
new D: 119EB68C0ED6442D272C5C5E2925120770BE75FB1786F57D4B2986A85AAA05986858A01A5F63C4F32D8A068F0F16B1AF9E8967EE515E03F1E69C8EC5BA391DC36D5A2DD7F00DFC53DC15ABD96B616BD4D4FFD3919A750FFA7DA67CA9AED1C12DD571EC84DB8CD019212111F0D5841C79A373F6D368CC76E86CC71B58DE22246445512D0833FC5A6AD64FF0BC7EEFC2985373D61E298542986FB3756A7FC90991013007AE3ED7535E846ECD6CCFF8C2EBD7F68319F98412FF4592061ABAB4D636EC60DB821975391FCED8605CA48D31FEE9C91C6060A3DCF508D21D9E385B72F390329783F1C201B6D68CFA4055CA11167810E249B90807215065AB756DAA421
校验结果:成功!

替换位置 0 :C3 --> CC
new N: CC576A1774A409A00098CCECF3CF48A9D5BD336748761DE31BDC8F7262E7989886A4117813D54748C19EF283C0CB69090E674DB7805C13282FB2C11F8229EF6B9B798E6DF9D940280E8B142644D7190869016305E1F0660693B711E746EB6ACEB2A30182649D032C4248F0512483854744206456D19D4D1FAE3DF87E256681888BB994809886ECF19C621CF769D680F8B54A6FB05722E1026D60D8CD9C653658F1FAAEF0A11A5D4844AAF132DA8B89E96CBDA8D48F2AE0382E8D9B299832F64D111DE1B4512D2F57906C2C937C39AD6324355ADE0EB75DA5C5FD3054847999C18DED70972511CE14749FD60CD759A6D14AF9ADD67AE5EEC5BA723C658EEB906B
new P: 35
new Q: 3DB0200714B8C41CFB5999A34E6EE0CDD642C710B070F0DF208FDE058CF3CD767F96F6C879E9714B18A3E89BB2F4F43CB23C2AC84D643FBED944744379686EEB5A6851A39D8A09B52633A57AA5B0078E9D48DF19ED43BE3249930A3C28BAFE6EB3835291A0C9F74C2754CFCB367EA5D1E443BDAFF1FF2F6EFF7824600B4A6ABA20854EA46802173A72D52A8E5040C18036BAB29F7FBD3F18E23A37CA258DA624830339A46A98DD5E4508222C554BEFABE6DD7B6BA3F99F7664FF46FE10F73BDD4D9EC1B8D9B6B284B2D8341933FE175CB8D1509051B9C553DB2A9F7EF2DC41BCD8BB9ABE88C69527FC90DB2F59298E22339D7CEEA2AFAD8424F703BE0E2A21F
new D: 6B6659CC9A3A394ED019A650D97C57AE674C29F2978E7AFFAE5C6C4B33A540E1FFAFA10756A1A55AFE9258A7C381903CA07FFA6873BD86F24AA950D62CE932BC55D1EBD935AE1FCAA0B128790941195725E5BA7014536E2F7543287DA6D831A420AC11B473C14DE9EB4C50DED788D64040EFE3B0115E9D81DE0C9920126844BABB65FC8E667AC560408F7B20FCFE51265DA2DCDCAEF49887454FE0D3F45FD2941CF91F816F09A7D7B9CE385901444D09F7CF42531CD0BCDDCF67577D7DA2443ABF2A18D33151512B9188B3E3F14E7FF431DE532A80EF336C14D6F78B795F1AA0A4A2B3FEE878FD34FA186553E123654F8C97B3EC7197F53C6D7316B3ED4B5361
校验结果:成功!

计算成功总数:2
总耗时:0 秒 141 毫秒
线程数: 20
素数集:2~251 区间内的素数
素数个数:54

计算出新的参数后就可以构造出公钥和私钥文件了
创建一个 python 脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.primitives import serialization
import os

# --- 输出文件名设置 ---
private_key_filename = "private_key.pem"
public_key_filename = "public_key.pem"

# --- RSA 参数 ---
n = int("BD576A1774A409A00098CCECF3CF48A9D5BD336748761DE31BDC8F7262E7989886A4117813D54748C19EF283C0CB69090E674DB7805C13282FB2C11F8229EF6B9B798E6DF9D940280E8B142644D7190869016305E1F0660693B711E746EB6ACEB2A30182649D032C4248F0512483854744206456D19D4D1FAE3DF87E256681888BB994809886ECF19C621CF769D680F8B54A6FB05722E1026D60D8CD9C653658F1FAAEF0A11A5D4844AAF132DA8B89E96CBDA8D48F2AE0382E8D9B299832F64D111DE1B4512D2F57906C2C937C39AD6324355ADE0EB75DA5C5FD3054847999C18DED70972511CE14749FD60CD759A6D14AF9ADD67AE5EEC5BA723C658EEB906B", 16)
e = int("10001", 16) # e = 65537
d = int("119EB68C0ED6442D272C5C5E2925120770BE75FB1786F57D4B2986A85AAA05986858A01A5F63C4F32D8A068F0F16B1AF9E8967EE515E03F1E69C8EC5BA391DC36D5A2DD7F00DFC53DC15ABD96B616BD4D4FFD3919A750FFA7DA67CA9AED1C12DD571EC84DB8CD019212111F0D5841C79A373F6D368CC76E86CC71B58DE22246445512D0833FC5A6AD64FF0BC7EEFC2985373D61E298542986FB3756A7FC90991013007AE3ED7535E846ECD6CCFF8C2EBD7F68319F98412FF4592061ABAB4D636EC60DB821975391FCED8605CA48D31FEE9C91C6060A3DCF508D21D9E385B72F390329783F1C201B6D68CFA4055CA11167810E249B90807215065AB756DAA421", 16)
p = int("11", 16)
q = int("B234279D9AF4BDC3C45393B1D668BCDC1475D606DAC9858A749359D511CAE9F8F7310161F48C7F53890E116CF1B062DB58D9B28E96ED3F34E1992E3BC5CD1D9274362BB2CD08B4DA680A6D5131BB62DABD2E7B50D4A605ABD651F2BB8E0ABEE0A820F25C9AEE211A98BD1E6A7CB804F7C7A6041579FD75C376D0E9E023334CBCBFBDB8F1807EFD1FA23E3961548DA68FB9AF782D7F2FE2D51BA671B27513F6EA4D282C2DC4CD84F8B9195B7B280ADC26FCEEBD044A82B4E99539FB727120E7D0101C1FB8C4DF3B9DB51A844E93091BA89A8C91C1EFBBA36ED875D3225E9090B62B39D36113D485B8E63C14C0CAAEBB1F559FB2ABBEF6865FBE89A24177744BB", 16)

# 验证RSA参数
print("正在验证 RSA 参数...")
if p * q != n:
raise ValueError(f"p * q ≠ n\np*q = {p * q}\nn = {n}")
phi = (p - 1) * (q - 1)
if (e * d) % phi != 1:
raise ValueError(f"e * d ≢ 1 mod φ(n)\n(e*d) % phi = {(e*d) % phi}")

# 计算 CRT 参数
print("正在计算 CRT 参数...")
dmp1 = d % (p - 1)
dmq1 = d % (q - 1)
try:
iqmp = pow(q, -1, p)
except ValueError:
raise ValueError("q has no inverse mod p (gcd(q,p) ≠ 1?)")

# 构造密钥
try:
print("正在构造密钥对象...")
private_numbers = rsa.RSAPrivateNumbers(
p=p,
q=q,
d=d,
dmp1=dmp1,
dmq1=dmq1,
iqmp=iqmp,
public_numbers=rsa.RSAPublicNumbers(e=e, n=n)
)
private_key = private_numbers.private_key()
print("已成功构造私钥!")

# 序列化为 PEM 格式的字节串
pem_private = private_key.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.PKCS8,
encryption_algorithm=serialization.NoEncryption()
)
pem_public = private_key.public_key().public_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PublicFormat.SubjectPublicKeyInfo
)

# 写入私钥文件
with open(private_key_filename, "wb") as f:
f.write(pem_private)
print(f"\n私钥已成功保存到文件:{private_key_filename}")

# 写入公钥文件
with open(public_key_filename, "wb") as f:
f.write(pem_public)
print(f"公钥已成功保存到文件:{public_key_filename}")

except Exception as ex:
print("创建密钥失败:", str(ex))

新的公钥文件:

1
2
3
4
5
6
7
8
9
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvVdqF3SkCaAAmMzs889I
qdW9M2dIdh3jG9yPcmLnmJiGpBF4E9VHSMGe8oPAy2kJDmdNt4BcEygvssEfginv
a5t5jm352UAoDosUJkTXGQhpAWMF4fBmBpO3EedG62rOsqMBgmSdAyxCSPBRJIOF
R0QgZFbRnU0frj34fiVmgYiLuZSAmIbs8ZxiHPdp1oD4tUpvsFci4QJtYNjNnGU2
WPH6rvChGl1IRKrxMtqLielsvajUjyrgOC6NmymYMvZNER3htFEtL1eQbCyTfDmt
YyQ1Wt4Ot12lxf0wVIR5mcGN7XCXJRHOFHSf1gzXWabRSvmt1nrl7sW6cjxljuuQ
awIDAQAB
-----END PUBLIC KEY-----

私钥文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
-----BEGIN PRIVATE KEY-----
MIIEPAIBADANBgkqhkiG9w0BAQEFAASCBCYwggQiAgEAAoIBAQC9V2oXdKQJoACY
zOzzz0ip1b0zZ0h2HeMb3I9yYueYmIakEXgT1UdIwZ7yg8DLaQkOZ023gFwTKC+y
wR+CKe9rm3mObfnZQCgOixQmRNcZCGkBYwXh8GYGk7cR50bras6yowGCZJ0DLEJI
8FEkg4VHRCBkVtGdTR+uPfh+JWaBiIu5lICYhuzxnGIc92nWgPi1Sm+wVyLhAm1g
2M2cZTZY8fqu8KEaXUhEqvEy2ouJ6Wy9qNSPKuA4Lo2bKZgy9k0RHeG0US0vV5Bs
LJN8Oa1jJDVa3g63XaXF/TBUhHmZwY3tcJclEc4UdJ/WDNdZptFK+a3WeuXuxbpy
PGWO65BrAgMBAAECggEAARnraMDtZELScsXF4pJRIHcL51+xeG9X1LKYaoWqoFmG
hYoBpfY8TzLYoGjw8Wsa+eiWfuUV4D8eacjsW6OR3DbVot1/AN/FPcFavZa2Fr1N
T/05GadQ/6faZ8qa7RwS3VceyE24zQGSEhEfDVhBx5o3P202jMduhsxxtY3iIkZE
VRLQgz/Fpq1k/wvH7vwphTc9YeKYVCmG+zdWp/yQmRATAHrj7XU16Ebs1sz/jC69
f2gxn5hBL/RZIGGrq01jbsYNuCGXU5H87YYFykjTH+6ckcYGCj3PUI0h2eOFty85
Ayl4PxwgG21oz6QFXKERZ4EOJJuQgHIVBlq3VtqkIQIBEQKCAQALI0J52a9L3DxF
OTsdZovNwUddYG2smFinSTWdURyun49zEBYfSMf1OJDhFs8bBi21jZso6W7T804Z
kuO8XNHZJ0NiuyzQi02mgKbVExu2LavS57UNSmBavWUfK7jgq+4Kgg8lya7iEamL
0eany4BPfHpgQVef11w3bQ6eAjM0y8v7248YB+/R+iPjlhVI2mj7mveC1/L+LVG6
ZxsnUT9upNKCwtxM2E+LkZW3soCtwm/O69BEqCtOmVOftycSDn0BAcH7jE3zudtR
qETpMJG6iajJHB77ujbth10yJekJC2KznTYRPUhbjmPBTAyq67H1Wfsqu+9oZfvo
miQXd0S7AgEBAoIBAAEZ62jA7WRC0nLFxeKSUSB3C+dfsXhvV9SymGqFqqBZhoWK
AaX2PE8y2KBo8PFrGvnoln7lFeA/HmnI7Fujkdw21aLdfwDfxT3BWr2Wtha9TU/9
ORmnUP+n2mfKmu0cEt1XHshNuM0BkhIRHw1YQceaNz9tNozHbobMcbWN4iJGRFUS
0IM/xaatZP8Lx+78KYU3PWHimFQphvs3Vqf8kJkQEwB64+11NehG7NbM/4wuvX9o
MZ+YQS/0WSBhq6tNY27GDbghl1OR/O2GBcpI0x/unJHGBgo9z1CNIdnjhbcvOQMp
eD8cIBttaM+kBVyhEWeBDiSbkIByFQZat1bapCECAQc=
-----END PRIVATE KEY-----

对比新旧公钥文件可以发现替换了两个字节:

1
2
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA【w1】dqF3SkCaAAmMzs889I
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA【vV】dqF3SkCaAAmMzs889I

w1改为vV

查找公钥修改点

回到libcc+214E637处,进入函数一条条分析,很容易可以看出这个函数在拼接公钥字符串,我们一步步走直到出现w1,对应HEX为0x7731
libcc+2156FD4处就是了!

1
2
3
4
5
6
7
8
9
10
00007FF8C3076FD4 | 66:C785 20010000 7731    | mov word ptr ss:[rbp+120],3177          | 字符"w1"
00007FF8C3076FDD | C685 22010000 64 | mov byte ptr ss:[rbp+122],64 |
00007FF8C3076FE4 | 48:8D85 20010000 | lea rax,qword ptr ss:[rbp+120] |
00007FF8C3076FEB | 48:8985 60050000 | mov qword ptr ss:[rbp+560],rax |
00007FF8C3076FF2 | 48:8D85 23010000 | lea rax,qword ptr ss:[rbp+123] |
00007FF8C3076FF9 | 48:8985 68050000 | mov qword ptr ss:[rbp+568],rax |
00007FF8C3077000 | 48:8D95 60050000 | lea rdx,qword ptr ss:[rbp+560] |
00007FF8C3077007 | 49:8BCF | mov rcx,r15 |
00007FF8C307700A | E8 B1F7FFFF | call libcc.7FF8C30767C0 |
00007FF8C307700F | 48:8D15 6EFDFA03 | lea rdx,qword ptr ds:[7FF8C7026D84] |

将操作数3177替换为5676即可。

1
mov word ptr ss:[rbp+120],5676

但是修改了字符后,函数返回的公钥完全是错误的:

1
2
3
4
5
6
7
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvVdqF3SkCaAAmMzs889I
qdW9M2dId3jG9yPcmLnmJiGpBF4E9VHSMGe8oPAy2kJDmdNt4BcEygvssEfginva
t5jm352UAoDosUJkTXGQhpAWMF4fBmBpO3EedG62rOsqMBgmSdAyxCSPBRJIOFR0
QgZFbRnU0frj34fiVmgYiLuZSAmIbs8ZxiHPdpVoD4tUpvsFci4QJtYNjNnGU2WP
H6rvChGl1IRKrxMtqLielsvajUjyrgOC6NmymYMvZNER3htFEtL1eQbCyTfDmtYy
Q1Wt4Ot12lxf0wVIR5mcGN7XCXJRHOFHSf1gzXWabRSvmt1nrl7sW6cjxljuuQaw
IDAQAB

陷阱一:

1
2
3
4
00007FF8C307724D | 0FB610                   | movzx edx,byte ptr ds:[rax]             | rax:"1dqF3SkCaAAmMzs889IqdW9M2dId"
00007FF8C3077250 | 80C2 37 | add dl,37
00007FF8C3077253 | 49:8BCF | mov rcx,r15
00007FF8C3077256 | E8 054C52FF | call libcc.7FF8C259BE60

这里做了一个加法运算,修改了公钥后这里的结果就不一样了。只需要让其在add操作后保持一致就行了。
正常时:31(1) + 37
修改后:56(V) + 37

37 - (56 - 31) = 12,所以这里改为:

1
add dl,12

陷阱二:

1
2
3
4
5
6
7
8
9
10
11
12
13
00007FF8C3078545 | 0FB610                   | movzx edx,byte ptr ds:[rax]        // 取了修改处的"V"
00007FF8C3078548 | 49:8BCF | mov rcx,r15
00007FF8C307854B | E8 103952FF | call libcc.7FF8C259BE60

-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAw【1】dqF3SkCaAAmMzs889I
qdW9M2dIdh3jG9yPcmLnmJiGpBF4E9VHSMGe8oPAy2kJDmdNt4BcEygvssEfginv
a5t5jm352UAoDosUJkTXGQhpAWMF4fBmBpO3EedG62rOsqMBgmSdAyxCSPBRJIOF
R0QgZFbRnU0frj34fiVmgYiLuZSAmIbs8ZxiHPdp【1】oD4tUpvsFci4QJtYNjNnGU2
WPH6rvChGl1IRKrxMtqLielsvajUjyrgOC6NmymYMvZNER3htFEtL1eQbCyTfDmt
YyQ1Wt4Ot12lxf0wVIR5mcGN7XCXJRHOFHSf1gzXWabRSvmt1nrl7sW6cjxljuuQ
awIDAQAB
-----END PUBLIC KEY-----

它从前面修改处取字符赋值给后面的字符,原来是1时就对了,修改后导致第4行括号那里变成了V。。。看得出来作者也是用1字节替换RSA工具计算过的。。。

1
2
3
4
5
6
movzx edx,byte ptr ds:[rax]

改为

push 0x31
pop edx

测试可行。
根据 AI 分析,7FF8C259BE60就是追加字符的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
_QWORD *__fastcall sub_18167BE60(_QWORD *a1, __int64 a2)
{
unsigned __int64 v2; // r8
unsigned __int64 v4; // r9
_QWORD *v5; // rax

v2 = a1[2];
v4 = a1[3];
if ( v2 >= v4 )
{
sub_181683AE0(a1, a2, v2, (unsigned __int8)a2);
return a1;
}
else
{
v5 = a1;
a1[2] = v2 + 1;
if ( v4 > 0xF )
v5 = (_QWORD *)*a1;
*((_BYTE *)v5 + v2) = a2;
*((_BYTE *)v5 + v2 + 1) = 0;
return a1;
}
}

制作一个.1377补丁文件

1
2
3
4
5
6
7
>libcc.dll
0000000002156FDB:77->76
0000000002156FDC:31->56
0000000002157252:37->12
0000000002158545:0F->6A
0000000002158546:B6->31
0000000002158547:10->5A

至此,公钥就替换成功了。

计算序列号

这个软件在离线激活前必须先输入正确的序列号,而序列号是在本地计算出来的。
我们在输入序列号时会实时的检测输入内容是否正确,正确后才会启用“激活”按钮
为了找到事件发生的位置,我用GetWindowTextAGetWindowTextW都没有断下来,于是我全内存搜索输入的序列号,找到了

分析发现可以在WideCharToMultiByte上设置断点,返回几层后来到这里

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
000000000168FE8F | E8 0CBB33FF              | call navicat.9CB9A0                                   | Unicode转ANSI
000000000168FE94 | C685 E7000000 00 | mov byte ptr ss:[rbp+E7],0 |
000000000168FE9B | C785 E0000000 00000000 | mov dword ptr ss:[rbp+E0],0 |
000000000168FEA5 | 48:8B9D F0000000 | mov rbx,qword ptr ss:[rbp+F0] | [rbp+F0]:"NAVPLTBQ2X7G7BNS"
000000000168FEAC | 48:85DB | test rbx,rbx | rbx:"NAVPLTBQ2X7G7BNS"
000000000168FEAF | 74 09 | je navicat.168FEBA |
000000000168FEB1 | 8B43 FC | mov eax,dword ptr ds:[rbx-4] |
000000000168FEB4 | 8985 E0000000 | mov dword ptr ss:[rbp+E0],eax |
000000000168FEBA | 83BD E0000000 10 | cmp dword ptr ss:[rbp+E0],10 | 序列号长度必须是16位
000000000168FEC1 | 75 33 | jne navicat.168FEF6 |
000000000168FEC3 | 48:8B8D F0000000 | mov rcx,qword ptr ss:[rbp+F0] | [rbp+F0]:"NAVPLTBQ2X7G7BNS"
000000000168FECA | E8 B12AE3FE | call navicat.4C2980 |
000000000168FECF | 48:8B8D 20010000 | mov rcx,qword ptr ss:[rbp+120] | [rbp+120]:&L"圐n"
000000000168FED6 | 48:8B89 580B0000 | mov rcx,qword ptr ds:[rcx+B58] |
000000000168FEDD | 48:89C2 | mov rdx,rax |
000000000168FEE0 | 4C:8D85 E8000000 | lea r8,qword ptr ss:[rbp+E8] |
000000000168FEE7 | 48:8B05 BADF3002 | mov rax,qword ptr ds:[399DEA8] | 验证序列号的函数
000000000168FEEE | FF10 | call qword ptr ds:[rax] | 验证序列号
000000000168FEF0 | 8885 E7000000 | mov byte ptr ss:[rbp+E7],al | 成功时al=1
000000000168FEF6 | 80BD E7000000 00 | cmp byte ptr ss:[rbp+E7],0 |
000000000168FEFD | 0F84 1C020000 | je navicat.169011F |

跟进分析

1
2
3
4
5
00007FFBC8E2E8F8 | 48:8B01                  | mov rax,qword ptr ds:[rcx]                            |
00007FFBC8E2E8FB | 4C:8D4424 38 | lea r8,qword ptr ss:[rsp+38] | [rsp+38]:L"談椃"
00007FFBC8E2E900 | 48:8D5424 60 | lea rdx,qword ptr ss:[rsp+60] | [rsp+60]:"NAVPLTBQ2X7G7BNS"
00007FFBC8E2E905 | FF50 10 | call qword ptr ds:[rax+10] | 验证序列号
00007FFBC8E2E908 | 0FB6D8 | movzx ebx,al | ebx:"NAVPLTBQ2X7G7BNS"

再跟进后的伪代码,用 IDA 查看

1
2
3
4
5
// Hidden C++ exception states: #wind=1
char __fastcall sub_182149DE0(__int64 a1, __int64 a2, _QWORD *a3)
{
// 省略...
}

丢给 AI 分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
**这是一个注册中心的关键检查和保存函数**,从函数名和代码内容可以看出这是 `CSRegistrationCenter::checkAndSaveKey` 函数。

### 主要功能:

1. **输入验证**:
- 检查输入字符串的长度是否为16字节(`*(_QWORD *)(a2 + 16) == 16`)
- 如果不是16字节,直接返回失败(0)

2. **密钥验证**:
- 调用虚函数进行密钥验证(`*(_QWORD *)a1 + 48LL`)
- 如果验证失败,进入错误处理流程

3. **成功处理流程**:
- 获取当前时间戳
- 调用UI相关函数更新状态
- 检查验证结果的状态码(`sub_18214EB60`)
- 根据状态码决定是否调用特定的回调函数

4. **时间管理**:
- 获取或设置时间戳
- 如果提供了输出参数(`a3`),计算30天后的时间戳(`v8 + 2592000`)

5. **日志记录**:
- 记录函数调用信息到日志系统
- 包含文件名:`"D:\\dev\\navicatlibs\\libcc\\src\\cs_registration_center.cpp"`
- 包含函数签名:`"bool __cdecl CSRegistrationCenter::checkAndSaveKey(...)"`

6. **状态保存**:
- 将验证结果保存到对象的状态字段中
- 调用清理函数

### 关键特征:

- **安全保护**:使用栈保护cookie防止缓冲区溢出
- **虚函数调用**:通过虚函数表调用不同的验证方法
- **错误处理**:完整的错误处理和资源清理
- **时间计算**:2592000秒 = 30天的计算
- **日志系统**:完整的调试日志记录

### 返回值:
- 成功时返回 1
- 失败时返回 0

### 总结:
这是一个**软件注册验证函数**,用于检查用户提供的注册密钥是否有效,并保存相关的验证状态。函数包含完整的输入验证、密钥验证、状态管理和错误处理机制,是软件授权系统的核心组件。

根据 AI 给出的提示,关键在a1 + 48LL这个验证函数

1
2
3
4
5
6
7
8
9
10
11
12
00007FFBC9879E0A | 0F85 C5010000            | jne libcc.7FFBC9879FD5                                | 序列号必须是16位
00007FFBC9879E10 | 0F57C0 | xorps xmm0,xmm0 |
00007FFBC9879E13 | 0F1145 C8 | movups xmmword ptr ss:[rbp-38],xmm0 |
00007FFBC9879E17 | 66:0F6F0D D1952204 | movdqa xmm1,xmmword ptr ds:[7FFBCDAA33F0] |
00007FFBC9879E1F | F3:0F7F4D D8 | movdqu xmmword ptr ss:[rbp-28],xmm1 |
00007FFBC9879E24 | C645 C8 00 | mov byte ptr ss:[rbp-38],0 |
00007FFBC9879E28 | 0F1145 E8 | movups xmmword ptr ss:[rbp-18],xmm0 |
00007FFBC9879E2C | 48:8B01 | mov rax,qword ptr ds:[rcx] |
00007FFBC9879E2F | 4C:8D45 C8 | lea r8,qword ptr ss:[rbp-38] |
00007FFBC9879E33 | FF50 30 | call qword ptr ds:[rax+30] | 验证序列号
00007FFBC9879E36 | 85C0 | test eax,eax |
00007FFBC9879E38 | 0F85 59010000 | jne libcc.7FFBC9879F97 |

伪代码

1
2
3
4
5
// Hidden C++ exception states: #wind=5
__int64 __fastcall sub_182149FF0(__int64 a1, __int64 a2, __int64 a3)
{
// 省略...
}

AI 给出的总结:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
**这是一个复杂的密钥验证和解析函数**,主要用于验证和解析某种格式的注册密钥。

### 主要功能:

1. **密钥预处理**:
- 将输入字符串中的字符进行替换:'8' → 'I','9' → 'O'
- 检查密钥格式是否以 "h*" 开头(h=0x68, *=0x2A)
- 如果是正确格式,移除前两个字符

2. **密钥解码**:
- 调用解码函数处理处理后的密钥
- 提取8字节的密钥数据

3. **版本和类型检查**:
- 检查密钥的第6字节的高4位,确保版本号 >= 17 (0x11)
- 根据密钥的不同字节进行复杂的验证逻辑

4. **许可证类型验证**:
- 根据密钥的第5字节('e', 'f', 'g')确定许可证类型
- 调用不同的验证函数检查许可证状态

5. **产品类型映射**:
- 根据密钥的第7字节(高字节)映射到不同的产品类型:
- 0xFF: 默认类型
- 0xFE: 类型9
- 0xFD: 类型6或8
- 0xFC: 类型5或7
- 0xFB: 类型4
- 0xFA: 类型3
- 其他: 类型1

6. **全局数据表查找**:
- 在两个全局数据表中查找匹配的条目
- 使用线程局部存储进行线程安全的初始化

7. **结果输出**:
- 将验证结果保存到输出结构体中
- 包含产品类型、许可证状态、版本信息等

### 关键特征:

- **字符替换**:将容易混淆的字符进行标准化
- **格式验证**:严格的密钥格式检查
- **多级验证**:版本、类型、许可证状态的多重验证
- **线程安全**:使用线程局部存储和初始化锁
- **错误处理**:完整的错误码返回机制

### 返回值:
- 0: 成功
- 1: 格式错误
- 2: 版本过低
- 3: 未找到匹配项
- 4: 验证失败

### 总结:
这是一个**软件注册密钥验证和解析函数**,负责验证用户输入的注册密钥的有效性,解析其中的产品类型、许可证信息等,并将结果保存到输出结构体中。函数包含完整的格式检查、版本验证、许可证状态检查等机制。

关键在解码函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
00007FFBC987A11F | 48:8D55 40               | lea rdx,qword ptr ss:[rbp+40]                         | 序列号
00007FFBC987A123 | 48:8D4D F0 | lea rcx,qword ptr ss:[rbp-10] | 准备输出缓冲区
00007FFBC987A127 | E8 241F0000 | call libcc.7FFBC987C050 | Base32 解码
00007FFBC987A12C | 90 | nop |
00007FFBC987A12D | 48:8B55 08 | mov rdx,qword ptr ss:[rbp+8] |
00007FFBC987A131 | 4C:8B45 F0 | mov r8,qword ptr ss:[rbp-10] |
00007FFBC987A135 | 48:837D 00 0A | cmp qword ptr ss:[rbp],A |
00007FFBC987A13A | 0F85 96020000 | jne libcc.7FFBC987A3D6 |
00007FFBC987A140 | 48:8D45 F0 | lea rax,qword ptr ss:[rbp-10] |
00007FFBC987A144 | 48:83FA 0F | cmp rdx,F |
00007FFBC987A148 | 49:0F47C0 | cmova rax,r8 |
00007FFBC987A14C | 8038 68 | cmp byte ptr ds:[rax],68 | 第1个字符是否等于'h'
00007FFBC987A14F | 0F85 81020000 | jne libcc.7FFBC987A3D6 |
00007FFBC987A155 | 48:8D45 F0 | lea rax,qword ptr ss:[rbp-10] |
00007FFBC987A159 | 48:83FA 0F | cmp rdx,F |
00007FFBC987A15D | 49:0F47C0 | cmova rax,r8 |
00007FFBC987A161 | 8078 01 2A | cmp byte ptr ds:[rax+1],2A | 第2个字符是否等于'*'
00007FFBC987A165 | 0F85 6B020000 | jne libcc.7FFBC987A3D6 |
00007FFBC987A16B | 48:8D4D F0 | lea rcx,qword ptr ss:[rbp-10] |
00007FFBC987A16F | 48:83FA 0F | cmp rdx,F |
00007FFBC987A173 | 49:0F47C8 | cmova rcx,r8 |
00007FFBC987A177 | 48:8D51 02 | lea rdx,qword ptr ds:[rcx+2] |
00007FFBC987A17B | 41:B8 09000000 | mov r8d,9 |
00007FFBC987A181 | E8 5C1BA601 | call <JMP.&memcpy> |
00007FFBC987A186 | B9 08000000 | mov ecx,8 |
00007FFBC987A18B | 48:894D 00 | mov qword ptr ss:[rbp],rcx |
00007FFBC987A18F | 48:8B03 | mov rax,qword ptr ds:[rbx] |
00007FFBC987A192 | 894C24 20 | mov dword ptr ss:[rsp+20],ecx |
00007FFBC987A196 | 4C:8D0D 037F9C04 | lea r9,qword ptr ds:[7FFBCE2420A0] | DES Key
00007FFBC987A19D | 4C:8D45 F0 | lea r8,qword ptr ss:[rbp-10] |
00007FFBC987A1A1 | 48:8D55 10 | lea rdx,qword ptr ss:[rbp+10] | 去除头2字节的序列号 Hex
00007FFBC987A1A5 | 48:8BCB | mov rcx,rbx |
00007FFBC987A1A8 | FF50 60 | call qword ptr ds:[rax+60] | DES 解密
00007FFBC987A1AB | 90 | nop | RAX = 序列号 Hex

DES密钥是硬编码:E9 7F B0 60 77 45 90 AE


至于序列号的一些含义可以参考其他文章,这里不展开了。
生成序列号的流程是这样:

  1. 根据各个位的含义,手动构造一个 8 字节的 Hex。
  2. 用 DES 对上面的 Hex 进行加密,DES 密钥是E97FB060774590AE
  3. 得到解密后的 Hex 后在前面追加固定 2 个字节0x682A
  4. 将上面得到的 Hex 用 Base32 编码,就得到了一个序列号。

解密请求码

断网,输入有效序列号开始激活,出现离线激活对话框
http://tool.chacuo.net/cryptrsaprikey 用自己的私钥来解密请求码,得到一个类似这样的 JSON 内容:

1
{"K":"NAVPLTBQ2X7G7BNS", "DI":"0A4897AEF970D541827A", "P":"WIN"}

根据前人的经验,只需要在 JSON 中加入NOT即可

1
{"K":"NAVPLTBQ2X7G7BNS", "DI":"0A4897AEF970D541827A", "P":"WIN", "N":"admin", "O":"google", "T":1516280990}

最后用私钥文件加密这个 JSON 就能得到最终的激活码了。