看了 iwolf 大佬的 Navicat 17 破解教程 觉得受益匪浅,写的很好。
正好 macOS 上几乎没人弄非商店版的 Navicat,便想试试能不能移植过来,顺便试试新的替换公钥方法。
替换公钥
相信很多人都注意到了 Navicat 安装目录下的 libcrypto,但非常可惜,经过验证它并没有调用里面的 RSA 相关函数
这里特别感谢大佬 Flamingo 提供的思路,发现其实用的是 libcf 中的 RSA 加解密的函数。
我们用 IDA 看一下 libcf 的导出表,可以看出来其实用的是 Crypto++ 这个开源库

Screenshot 2025-10-06 at 11.46.52.png (116.45 KB, 下载次数: 2)
下载附件
2025-10-6 17:26 上传
既然是开源的那就很好办了,根据前面的分析我们知道公钥是用二进制的DER格式存储的,只需要Hook BERDecodePublicKey 函数就可以了
用 OpenSSL 生成一对自己的 n 和 d ,e 就和 Navicat 的公钥保持一致就行,然后在动态库中把旧的模数替换掉就行
const char *rsa_n = "00A1E96A057F38361EF91D6B81D4C5592C394565CBF4AD80F51596B8388DC047D9A41356F54F53CFE0CE83ABFE439FC2D1E63821292E512062F043CAEC57EEE33B21C5D8549C6545CD143EFAE79D1FEC6F998B11532E870CED27894652FF6CF80AB354D5C49270D8B20DAF64499BF2A8D7B62C1FEF58F69C005F10098322DA7D6B19DC39C883E171A8BD2ADEF35749856F7F3004B8421DC444494F1764AB99328C87B78FDAF844531F2601EB95E52BD92752F0528E4FE6D1FC1B61FE1F9058E56C271907B56D23B177E270CF16EF74E9E0165C82AD564D9F36B5B62A0E0DCA54043301B9633BF265135F7A1A4FDFEE0E08DB7C212DA764EEB1EBF1F188729947EFh";
// 下面两个等会要用
const char *rsa_e = "0x10001";
const char *rsa_d = "00856594D74090A46E725A5DD7D2E0616EC124BF1101DEED2F7F80F4F88B394F392E284CEDC154D1216BA42A514B8FD5E82FA0311A07B20957DA92501ED6F7D7B941430ACFF326B1129CCAA2D7AEA9BD97D8CE2E10F575891DDF0407AF9C0840783875FDC57DCD818B7920F7247A5ABBE7358D3726708A85CEFC836F02DEF55A7C5245AAC8392413BABEA5BE8AA3F4D41128B1525315AD96A9C3D6DB5F6DAA0E6B1A28405FA4E3C81BE8F43EC3A1307891D1F1E259E7EFEEBAFDEC2BB803CD55990C08CA9CAD8ED3566F155E5A55E649B5DD9F46625FDA9479B9B45ACBA9EE2B82BA5DBAABAA97F28363F9171F5717A02848114E4AEF923BA0B9EEDFEE8AA24139h";
extern void BERDecodePublicKey(RSA::PublicKey *self, void *bt, bool param, size_t size)
asm("__ZN8CryptoPP11RSAFunction18BERDecodePublicKeyERNS_22BufferedTransformationEbm");
void (*OrigBERDecodePublicKey)(RSA::PublicKey *self, void *bt, bool param, size_t size);
void MyBERDecodePublicKey(RSA::PublicKey *self, void *bt, bool param, size_t size){
// 先调用原函数初始化好公钥
OrigBERDecodePublicKey(self, bt, param, size);
// 再替换为我们自己的模数
self->SetModulus(Integer(rsa_n));
return;
}
这里为了方便Hook,重新写了一下函数的声明,用 asm 来使最终链接到需要Hook的函数地址
原本的函数签名展开后是这样的 CryptoPP::RSAFunction::BERDecodePublicKey(CryptoPP::BufferedTransformation&, bool, unsigned long)
这里用的Inline Hook框架是 tinyhook,在 constructor 函数里加上这样一行就可以了
__attribute__((constructor(0)))
void load() {
tiny_hook((void *)BERDecodePublicKey, (void *)MyBERDecodePublicKey, (void **)&OrigBERDecodePublicKey);
return;
}
编译的时候需要引入 cryptopp 的头文件,然后直接链接到 libcf.dylib 就可以找到我们用到的所有函数
然后用 insert_dylib 把编译好的动态库注入到主程序就可以了,我们来启动验证一下
这是上面的 n, e, d 对应的私钥,先保存成private.pem备用
-----BEGIN RSA PRIVATE KEY-----
MIIEpQIBAAKCAQEAoelqBX84Nh75HWuB1MVZLDlFZcv0rYD1FZa4OI3AR9mkE1b1
T1PP4M6Dq/5Dn8LR5jghKS5RIGLwQ8rsV+7jOyHF2FScZUXNFD76550f7G+ZixFT
LocM7SeJRlL/bPgKs1TVxJJw2LINr2RJm/Ko17YsH+9Y9pwAXxAJgyLafWsZ3DnI
g+FxqL0q3vNXSYVvfzAEuEIdxERJTxdkq5kyjIe3j9r4RFMfJgHrleUr2SdS8FKO
T+bR/Bth/h+QWOVsJxkHtW0jsXficM8W73Tp4BZcgq1WTZ82tbYqDg3KVAQzAblj
O/JlE196Gk/f7g4I23whLadk7rHr8fGIcplH7wIDAQABAoIBAQCFZZTXQJCkbnJa
XdfS4GFuwSS/EQHe7S9/gPT4izlPOS4oTO3BVNEha6QqUUuP1egvoDEaB7IJV9qS
UB7W99e5QUMKz/MmsRKcyqLXrqm9l9jOLhD1dYkd3wQHr5wIQHg4df3Ffc2Bi3kg
9yR6WrvnNY03JnCKhc78g28C3vVafFJFqsg5JBO6vqW+iqP01BEosVJTFa2WqcPW
219tqg5rGihAX6TjyBvo9D7DoTB4kdHx4lnn7+66/ewruAPNVZkMCMqcrY7TVm8V
XlpV5km13Z9GYl/alHm5tFrLqe4rgrpduquql/KDY/kXH1cXoChIEU5K75I7oLnu
3+6KokE5AoGBANRLyXdvDzRmzPzcg6iTQULJ1eOCh5X5x5h5xfGlkajkJyxuvnDM
F8dSAJsfKmjcNcB2tX65HgtdS55vNKL5dIz+/ApFges8reENa65Dt9ODNBPoC4b4
fsAbigAZlwGZLp7z1BcUbNKSeONjHU9CTaXQWwggFQcwgmK+r+rceiM1AoGBAMM+
U3Pz/LYdpvRiYu0kQIiR5S8HSyNRqnlUf9JJs+cl9qgzIhbejlMdeA75dopVBOd9
lJqHJNZAnFhUJl6jUFEkIaSMHSNVsfhrOmiWzHIlh3Ei55vA4f33gJQGRyVAeXLE
3zLNUplu+0R5g0JMozSS+IVpWiYy9NZkrJzPmF8TAoGARoerNje6eHFS1ws33nCV
tOezXLOH8iaazihev+p+2vp5nURplrXnjHvM4bxX7aCDZx7JK4G63pGvRsKxXRe9
Rf6Mo6j2Ab4WEnfP94Rd9TJYwehMtBmompBLp77YsVo/5+Uf6E8L3GV3LixGl4dy
noz7QVbPRaUzHDU34rI/DaUCgYEAl8foEIhouRssI2gpB7nbAVCKHplI7Fgcct4h
0FTDqrp0miXGJok1k5+hKeL9KGUXvu59i/PryzPHV1Nz0LadRbcVAFp8fG+uPzT8
3zn8DfDm7ij4bLjx9wFlz61hua/5uiMacN/1ipogdAcS54O0jLaExRI1puSOOe1h
0zX/ekkCgYEAlpACNpfGmsxJctC3l0XrU0tyWCQRRnq9GVSLbqUdL/UT1VPTAt2h
PFMBwzho0W13lj1ZE0Jz82VL4nuT6YP8dj9OwD/ZWg9FxLojFNTS9jVOTogfVJGT
KwUNq9BTtdxS0oZxYz3IHjEX4Xe5iyTSc5+t4lw+0gsXT4WWPWDvp3k=
-----END RSA PRIVATE KEY-----
断网后输入一个激活码,选择离线激活,把请求码复制出来,先用 base64 解码
$ echo "QClZInZ0LYK..." | base64 -d > request.bin
然后用用 openssl 解密试试
$ openssl pkeyutl -decrypt -inkey private.pem -in request.bin
{"K":"NAVPE4AMHQYM4SVI", "DI":"271E3B24BEXXXXXXXXXX", "P":"MAC"}
可以解密就说明我们替换公钥成功了,来试试生成对应的激活码吧
给这个JSON加上 N, O, T 字段,保存到 response.txt 里
$ echo '{"K":"NAVPE4AMHQYM4SVI", "DI":"271E3B24BEXXXXXXXXXX", "P":"MAC", "N":"skrets", "O":"52pojie.cn", "T":1761408000}' > response.txt
再使用 openssl 加密,注意这里要用 -sign 来指定用私钥加密
$ openssl pkeyutl -sign -inkey private.pem -in response.txt | base64
es7ywFxtex1cQoHl....
输出的就是我们需要的离线激活码了,拿去试试

Screenshot 2025-10-06 at 13.21.24.png (131.96 KB, 下载次数: 1)
下载附件
2025-10-6 17:26 上传
ok,成功激活
这里没有涉及到写死的地址,通过导出的符号表进行Hook,所以理论上可以通杀后续的版本
那么,这就结束了吗?并非,手动弄这么一大通未免太麻烦了一点。
既然 Navicat 已经给我们提供了 RSA 的函数,那我们直接在动态库里调用计算好不就行了
自动计算离线激活码
那我们首先需要拿到请求码里面的JSON数据,好办,只要Hook公钥加密的函数就好了
分析一下就能知道它用的是这个函数 CryptoPP::TF_EncryptorBase::Encrypt(CryptoPP::RandomNumberGenerator&, unsigned char const*, unsigned long, unsigned char*, CryptoPP::NameValuePairs const&) const
extern void TF_EncryptorEncrypt(void *self, void *rng, const byte *plaintext, size_t plaintextLength, byte *ciphertext, void *param)
asm("__ZNK8CryptoPP16TF_EncryptorBase7EncryptERNS_21RandomNumberGeneratorEPKhmPhRKNS_14NameValuePairsE");
void (*OrigTF_EncryptorEncrypt)(void *self, void *rng, const byte *plaintext, size_t plaintextLength, byte *ciphertext, void *param);
void MyTF_EncryptorEncrypt(void *self, void *rng, const byte *plaintext, size_t plaintextLength, byte *ciphertext, void *param) {
if (plaintext[0] == '{') {
// 计算好离线激活码
SaveActivationCode(plaintext, plaintextLength);
}
return OrigTF_EncryptorEncrypt(self, rng, plaintext, plaintextLength, ciphertext, param);
}
SaveActivationCode 的具体实现如下,基本差不多,先拼接,再进行加密
比较坑的是 Crypto++ 并没有直接用私钥加密的函数,要把数据转换成 Integer 进行RSA底层运算
并且要手动进行 PKSC #1 填充,我开始忘记了导致一直激活失败
NSString *activation_code;
static void SaveActivationCode(const byte *request, size_t requestLength) {
// 拼接出完整注册信息JSON数据
string reg_info = ", \"N\":\"skrets\", \"O\":\"52pojie.cn\", \"T\":1761408000}";
size_t responseLength = requestLength + reg_info.length() - 1;
byte *response = new byte[responseLength];
memcpy(response, request, requestLength - 1);
memcpy(response + requestLength - 1, reg_info.data(), reg_info.length());
// PKCS #1 Padding
byte *padded = new byte[256]; // 2048-bit
padded[0] = byte{0};
padded[1] = byte{1};
memset(padded + 2, 0xff, 256 - 3 - responseLength);
padded[256 - responseLength - 1] = byte{0};
memcpy(padded + 256 - responseLength, response, responseLength);
delete[] response;
// 初始化RSA私钥以及随机数生成器
AutoSeededRandomPool prng;
RSA::PrivateKey priv_key;
priv_key.Initialize(Integer(rsa_n), Integer(rsa_e), Integer(rsa_d));
// 进行底层的RSA模幂运算
Integer m(padded, 256);
Integer c = priv_key.CalculateInverse(prng, m);
byte *result = new byte[256];
c.Encode(result, 256);
delete[] padded;
// Base64编码
NSData *res_data = [NSData dataWithBytes:result length:256];
activation_code = [res_data base64EncodedStringWithOptions:0];
delete[] result;
}
算好的激活码怎么用呢,我心有一计
自动填入离线激活码
我们用 Hopper 来分析一下主程序,根据联网失败的弹窗来搜索一下这个字符串
Your license could not be activated because the activation server is temporarily unavailable. Try manual activation, or try again later.
可以看到有两个引用,我们看看来自 -[RegistrationSubscriptionWindowController activate] 的引用
else {
r0 = [NSBundle mainBundle];
r0 = [r0 retain];
r23 = r0;
r24 = [[r0 localizedStringForKey:@"Your license could not be activated because the activation server is temporarily unavailable. Try manual activation, or try again later." value:@"" table:0x0] retain];
r0 = [NSBundle mainBundle];
r0 = [r0 retain];
r20 = r0;
r21 = [[r0 localizedStringForKey:@"If this problem persists, contact Navicat Support at https://help.navicat.com/hc/en-us/requests/new." value:@"" table:0x0] retain];
r22 = [[NSString stringWithFormat:@"%@\n%@", r3, r4] retain];
[r21 release];
[r20 release];
[r24 release];
[r23 release];
r0 = [NSBundle mainBundle];
r0 = [r0 retain];
r23 = r0;
r24 = [[r0 localizedStringForKey:@"Activation Failed" value:@"" table:0x0] retain];
r9 = decomp_var_118;
r8 = &decomp_var_118;
if (sign_extend_64(decomp_var_101) >= 0x0) {
r2 = r8;
}
else {
r2 = r9;
}
r25 = [[NSString stringWithUTF8String:r2] retain];
[[NSString stringWithFormat:@"%@\n%@", r3, r4] retain];
r28 = [[[[NSBundle mainBundle] retain] localizedStringForKey:@"OK" value:@"" table:0x0] retain];
r0 = [NSBundle mainBundle];
r0 = [r0 retain];
r20 = r0;
r21 = [[r0 localizedStringForKey:@"Manual Activation" value:@"" table:0x0] retain];
r26 = [[NSAlert alertWithQuestion:r24 details:decomp_var_168 firstButtonTitle:r28 secondButtonTitle:r21] retain];
[decomp_var_158 release];
[r21 release];
[r20 release];
[r28 release];
[decomp_var_170 release];
[decomp_var_168 release];
}
可以看到这里就是创建了一个带有两个按钮的弹窗,其中一个就是手动激活的
那我们往下翻翻,应该就可以找到手动激活的窗口所在位置了
if (r20 == 0x3e9) {
r23 = [ManualActivationWindowController alloc];
r20 = [[NSValue valueWithPointer:decomp_var_160] retain];
r24 = [[r19->_registrationViewController inputtedKey] retain];
r21 = [r23 initWithDialogInfoValue:r20 key:r24];
[r24 release];
[r20 release];
[r21 retainUntilWindowClosed];
r0 = [r21 window];
r0 = [r0 retain];
r20 = r0;
[r0 makeKeyAndOrderFront:r21];
[r20 release];
r23 = **_NSApp;
r20 = [[r21 window] retain];
r23 = [r23 runModalForWindow:r20];
[r20 release];
if (r23 == 0x1) {
[r19 showPerpectualInfoView];
}
[r21 release];
}
到这里就很显然了,ManualActivationWindowController 就是我们要找的,去看看这个类有什么成员吧
@Class ManualActivationWindowController : NSWindowController {
ivar _requestCodeTextField
ivar _activationCodeTextField
ivar _dialogInfoValue
ivar _key
-windowDidLoad
-windowShouldClose:
-initWithDialogInfoValue:key:
-activate:
-cancel:
-.cxx_destruct
}
一目了然啊,这个 _activationCodeTextField 就是我们想要的,这是一个 NSTextField 对象,我们可以直接设置它的内容
那现在就是要找一个合适的时机,通过Hook拿到这个 _activationCodeTextField 并写入我们的激活码
看回上面的伪代码,通过调试我们可以知道,请求码在 retainUntilWindowClosed 的时候会被算好,也就意味着我们的激活码也算好了,再往下看看,目标就很明确了
[r21 retainUntilWindowClosed];
r0 = [r21 window];
r0 = [r0 retain];
r20 = r0;
[r0 makeKeyAndOrderFront:r21]; // 就是你了!
函数原型是这样的 - (void) makeKeyAndOrderFront:(id) sender;
这个 sender 就是我们要的 ManualActivationWindowController,那么利用 ObjC 的运行时机制,Hook就很好写了
void (*makeKeyAndOrderFront)(id cls, SEL sel, id sender);
void my_makeKeyAndOrderFront(id cls, SEL sel, id sender) {
if (strcmp(object_getClassName(sender), "ManualActivationWindowController") == 0) {
// 往文本框里填入之前算好的激活码
Ivar ivar = class_getInstanceVariable(object_getClass(sender), "_activationCodeTextField");
NSTextField *code_field = object_getIvar(sender, ivar);
[code_field setStringValue:activation_code];
}
return makeKeyAndOrderFront(cls, sel, sender);
}
这里就没有定义自己的 ObjC 方法然后再 exchange 了,直接用 setImplementation 即可
#define HOOK_INSTANCEM(cls, sel, imp) method_setImplementation(class_getInstanceMethod(objc_getClass(cls), sel_registerName(sel)),(IMP)imp)
makeKeyAndOrderFront = (void (*)(id, SEL, id))
HOOK_INSTANCEM("NSWindow", "makeKeyAndOrderFront:", my_makeKeyAndOrderFront);
那我们来注入试试吧

Screenshot 2025-10-06 at 15.28.36.png (191.96 KB, 下载次数: 1)
下载附件
2025-10-6 17:26 上传
可以看到弹出的窗口已经是填入了有效激活码的,直接点Activate就可以了
都已经到这一步了,那顺便把网络屏蔽也在注入库里实现好了
屏蔽联网验证
用 Proxyman 抓包可以看到请求激活的域名是 activate.navicat.com,但非常不幸的是二进制里搜索不到这个字符串

Screenshot 2025-10-06 at 15.37.26.png (37.17 KB, 下载次数: 2)
下载附件
2025-10-6 17:26 上传
不过注意到上面的 UA 里面有 CFNetwork,说明它用的是系统提供的网络请求框架,那么大概率就是 NSURLSession 了
我们可以在 lldb 中下断点验证,确实用的是 NSURLSession,并且调用了 -[NSURLRequest initWithURL:] 来初始化
那我们就直接拦截这个函数,把请求改掉就好了,非常简单
id (*initWithURL)(id cls, SEL sel, NSURL *URL);
id my_initWithURL(id cls, SEL sel, NSURL *URL) {
if ([URL.host isEqualToString:@"activate.navicat.com"]) {
// 改成主机环回地址使请求失败
NSURL *newURL = [NSURL URLWithString:@"http://127.0.0.1"];
return initWithURL(cls, sel, newURL);
}
return initWithURL(cls, sel, URL);
}
结语
那么到这里本文就差不多结束了
通过Hook导出表函数以及ObjC运行时实现了理论通杀,并且简化了很多步骤
现在你不需要屏蔽联网,也不用拿私钥加密解密,只需要
[ol]
[/ol]
这里放几个生成的激活码
cs
NAVN-BB6P-AN5N-TSJR
NAVJ-NVYH-36YH-FXO5
NAVI-DWA4-Q6GJ-QIXC
en
NAVJ-37MI-MXVH-BP2X
NAVE-CWN6-HDXB-O74J
NAVB-HHYW-5BCQ-3ARW
以及完整的注入库代码
#include "tinyhook.h"
#include "cryptopp/rsa.h"
#include "cryptopp/osrng.h"
#include
#include
#import
#import
#define HOOK_INSTANCEM(cls, sel, imp) method_setImplementation(class_getInstanceMethod(objc_getClass(cls), sel_registerName(sel)),(IMP)imp)
#define HOOK_CLASSM(cls, sel, imp) method_setImplementation(class_getClassMethod(objc_getClass(cls), sel_registerName(sel)),(IMP)imp)
using namespace std;
using namespace CryptoPP;
const char *rsa_n = "00A1E96A057F38361EF91D6B81D4C5592C394565CBF4AD80F51596B8388DC047D9A41356F54F53CFE0CE83ABFE439FC2D1E63821292E512062F043CAEC57EEE33B21C5D8549C6545CD143EFAE79D1FEC6F998B11532E870CED27894652FF6CF80AB354D5C49270D8B20DAF64499BF2A8D7B62C1FEF58F69C005F10098322DA7D6B19DC39C883E171A8BD2ADEF35749856F7F3004B8421DC444494F1764AB99328C87B78FDAF844531F2601EB95E52BD92752F0528E4FE6D1FC1B61FE1F9058E56C271907B56D23B177E270CF16EF74E9E0165C82AD564D9F36B5B62A0E0DCA54043301B9633BF265135F7A1A4FDFEE0E08DB7C212DA764EEB1EBF1F188729947EFh";
const char *rsa_e = "0x10001";
const char *rsa_d = "00856594D74090A46E725A5DD7D2E0616EC124BF1101DEED2F7F80F4F88B394F392E284CEDC154D1216BA42A514B8FD5E82FA0311A07B20957DA92501ED6F7D7B941430ACFF326B1129CCAA2D7AEA9BD97D8CE2E10F575891DDF0407AF9C0840783875FDC57DCD818B7920F7247A5ABBE7358D3726708A85CEFC836F02DEF55A7C5245AAC8392413BABEA5BE8AA3F4D41128B1525315AD96A9C3D6DB5F6DAA0E6B1A28405FA4E3C81BE8F43EC3A1307891D1F1E259E7EFEEBAFDEC2BB803CD55990C08CA9CAD8ED3566F155E5A55E649B5DD9F46625FDA9479B9B45ACBA9EE2B82BA5DBAABAA97F28363F9171F5717A02848114E4AEF923BA0B9EEDFEE8AA24139h";
extern void BERDecodePublicKey(RSA::PublicKey *self, void *bt, bool param, size_t size) asm("__ZN8CryptoPP11RSAFunction18BERDecodePublicKeyERNS_22BufferedTransformationEbm");
void (*OrigBERDecodePublicKey)(RSA::PublicKey *self, void *bt, bool param, size_t size);
void MyBERDecodePublicKey(RSA::PublicKey *self, void *bt, bool param, size_t size){
OrigBERDecodePublicKey(self, bt, param, size);
// 替换为新的模数
self->SetModulus(Integer(rsa_n));
return;
}
NSString *activation_code;
static void SaveActivationCode(const byte *request, size_t requestLength) {
// 拼接出完整注册信息JSON数据
string reg_info = ", \"N\":\"skrets\", \"O\":\"52pojie.cn\", \"T\":1761408000}";
size_t responseLength = requestLength + reg_info.length() - 1;
byte *response = new byte[responseLength];
memcpy(response, request, requestLength - 1);
memcpy(response + requestLength - 1, reg_info.data(), reg_info.length());
// PKCS #1 Padding
byte *padded = new byte[256]; // 2048-bit
padded[0] = byte{0};
padded[1] = byte{1};
memset(padded + 2, 0xff, 256 - 3 - responseLength);
padded[256 - responseLength - 1] = byte{0};
memcpy(padded + 256 - responseLength, response, responseLength);
delete[] response;
// 初始化RSA私钥以及随机数生成器
AutoSeededRandomPool prng;
RSA::PrivateKey priv_key;
priv_key.Initialize(Integer(rsa_n), Integer(rsa_e), Integer(rsa_d));
// 进行底层的RSA模幂运算
Integer m(padded, 256);
Integer c = priv_key.CalculateInverse(prng, m);
byte *result = new byte[256];
c.Encode(result, 256);
delete[] padded;
// Base64编码
NSData *res_data = [NSData dataWithBytes:result length:256];
activation_code = [res_data base64EncodedStringWithOptions:0];
delete[] result;
}
extern void TF_EncryptorEncrypt(void *self, void *rng, const byte *plaintext, size_t plaintextLength, byte *ciphertext, void *param) asm("__ZNK8CryptoPP16TF_EncryptorBase7EncryptERNS_21RandomNumberGeneratorEPKhmPhRKNS_14NameValuePairsE");
void (*OrigTF_EncryptorEncrypt)(void *self, void *rng, const byte *plaintext, size_t plaintextLength, byte *ciphertext, void *param);
void MyTF_EncryptorEncrypt(void *self, void *rng, const byte *plaintext, size_t plaintextLength, byte *ciphertext, void *param) {
if (plaintext[0] == '{') {
// 计算好离线激活码
SaveActivationCode(plaintext, plaintextLength);
}
return OrigTF_EncryptorEncrypt(self, rng, plaintext, plaintextLength, ciphertext, param);
}
void (*makeKeyAndOrderFront)(id cls, SEL sel, id sender);
void my_makeKeyAndOrderFront(id cls, SEL sel, id sender) {
if (strcmp(object_getClassName(sender), "ManualActivationWindowController") == 0) {
// 往文本框里填入之前算好的激活码
Ivar ivar = class_getInstanceVariable(object_getClass(sender), "_activationCodeTextField");
NSTextField *code_field = object_getIvar(sender, ivar);
[code_field setStringValue:activation_code];
}
return makeKeyAndOrderFront(cls, sel, sender);
}
id (*initWithURL)(id cls, SEL sel, NSURL *URL);
id my_initWithURL(id cls, SEL sel, NSURL *URL) {
if ([URL.host isEqualToString:@"activate.navicat.com"]) {
// 改成主机环回地址使请求失败
NSURL *newURL = [NSURL URLWithString:@"http://127.0.0.1"];
return initWithURL(cls, sel, newURL);
}
return initWithURL(cls, sel, URL);
}
__attribute__((constructor(0)))
void load() {
// Inline Hook
tiny_hook((void *)BERDecodePublicKey, (void *)MyBERDecodePublicKey, (void **)&OrigBERDecodePublicKey);
tiny_hook((void *)TF_EncryptorEncrypt, (void *)MyTF_EncryptorEncrypt, (void **)&OrigTF_EncryptorEncrypt);
// ObjC Runtime Hook
makeKeyAndOrderFront = (void (*)(id, SEL, id))
HOOK_INSTANCEM("NSWindow", "makeKeyAndOrderFront:", my_makeKeyAndOrderFront);
initWithURL = (id (*)(id, SEL, NSURL *))
HOOK_INSTANCEM("NSURLRequest", "initWithURL:", my_initWithURL);
return;
}
编译好的注入库我就放附件里了
以防有人不知道怎么用 insert_dylib
$ insert_dylib --inplace --all-yes @rpath/libNavicat.dylib 'Navicat Premium.app/Contents/MacOS/Navicat Premium'
$ codesign -f -s - 'Navicat Premium.app/Contents/MacOS/Navicat Premium'
注意别忘了重新签名,然后把 libNavicat.dylib 放在 Navicat Premium.app/Contents/Frameworks 里
如果遇到了损坏无法打开的弹窗,这是 quarantine 属性导致的,移除即可
$ xattr -cr 'Navicat Premium.app'

Screenshot 2025-10-06 at 17.05.17.png (323.84 KB, 下载次数: 2)
下载附件
2025-10-6 17:26 上传
文中用到的软件
文中用到的代码/工具
特别感谢 @iwolf 大佬的原始分析,以及 Flamingo 大佬提供的替换公钥思路
本帖仅作技术交流,请支持并使用正版软件!