Navicat 17 之 macOS 替换公钥通杀思路
[ 本帖最后由 skrets 于 2025-10-6 17:50 编辑 ]看了 (https://www.52pojie.cn/home.php?mod=space&uid=204161) 大佬的 (https://www.52pojie.cn/thread-2052969-1-1.html) 觉得受益匪浅,写的很好。
正好 macOS 上几乎没人弄非商店版的 Navicat,便想试试能不能移植过来,顺便试试新的替换公钥方法。
## 替换公钥
相信很多人都注意到了 Navicat 安装目录下的 `libcrypto`,但非常可惜,经过验证它并没有调用里面的 RSA 相关函数
这里特别感谢大佬 Flamingo 提供的思路,发现其实用的是 `libcf` 中的 RSA 加解密的函数。
我们用 IDA 看一下 `libcf` 的导出表,可以看出来其实用的是 (https://github.com/weidai11/cryptopp) 这个开源库
既然是开源的那就很好办了,根据前面的分析我们知道公钥是用二进制的DER格式存储的,只需要Hook `BERDecodePublicKey` 函数就可以了
用 OpenSSL 生成一对自己的 `n` 和 `d` ,`e` 就和 Navicat 的公钥保持一致就行,然后在动态库中把旧的模数替换掉就行
```cpp
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框架是 (https://github.com/Antibioticss/tinyhook),在 `constructor` 函数里加上这样一行就可以了
```cpp
__attribute__((constructor(0)))
void load() {
tiny_hook((void *)BERDecodePublicKey, (void *)MyBERDecodePublicKey, (void **)&OrigBERDecodePublicKey);
return;
}
```
编译的时候需要引入 `cryptopp` 的头文件,然后直接链接到 `libcf.dylib` 就可以找到我们用到的所有函数
然后用 (https://github.com/tyilo/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 解码
```sh
$ echo "QClZInZ0LYK..." | base64 -d > request.bin
```
然后用用 openssl 解密试试
```sh
$ openssl pkeyutl -decrypt -inkey private.pem -in request.bin
{"K":"NAVPE4AMHQYM4SVI", "DI":"271E3B24BEXXXXXXXXXX", "P":"MAC"}
```
可以解密就说明我们替换公钥成功了,来试试生成对应的激活码吧
给这个JSON加上 N, O, T 字段,保存到 response.txt 里
```sh
$ echo '{"K":"NAVPE4AMHQYM4SVI", "DI":"271E3B24BEXXXXXXXXXX", "P":"MAC", "N":"skrets", "O":"52pojie.cn", "T":1761408000}' > response.txt
```
再使用 openssl 加密,注意这里要用 `-sign` 来指定用私钥加密
```sh
$ openssl pkeyutl -sign -inkey private.pem -in response.txt | base64
es7ywFxtex1cQoHl....
```
输出的就是我们需要的离线激活码了,拿去试试
ok,成功激活
这里没有涉及到写死的地址,通过导出的符号表进行Hook,所以理论上可以通杀后续的版本
那么,这就结束了吗?并非,手动弄这么一大通未免太麻烦了一点。
既然 Navicat 已经给我们提供了 RSA 的函数,那我们直接在动态库里调用计算好不就行了
## 自动计算离线激活码
那我们首先需要拿到请求码里面的JSON数据,好办,只要Hook公钥加密的函数就好了
分析一下就能知道它用的是这个函数 `CryptoPP::TF_EncryptorBase::Encrypt(CryptoPP::RandomNumberGenerator&, unsigned char const*, unsigned long, unsigned char*, CryptoPP::NameValuePairs const&) const`
```cpp
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 == '{') {
// 计算好离线激活码
SaveActivationCode(plaintext, plaintextLength);
}
return OrigTF_EncryptorEncrypt(self, rng, plaintext, plaintextLength, ciphertext, param);
}
```
`SaveActivationCode` 的具体实现如下,基本差不多,先拼接,再进行加密
比较坑的是 `Crypto++` 并没有直接用私钥加密的函数,要把数据转换成 `Integer` 进行RSA底层运算
并且要手动进行 PKSC #1 填充,我开始忘记了导致一直激活失败
```objc
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;
memcpy(response, request, requestLength - 1);
memcpy(response + requestLength - 1, reg_info.data(), reg_info.length());
// PKCS #1 Padding
byte *padded = new byte; // 2048-bit
padded = byte{0};
padded = byte{1};
memset(padded + 2, 0xff, 256 - 3 - responseLength);
padded = 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;
c.Encode(result, 256);
delete[] padded;
// Base64编码
NSData *res_data = ;
activation_code = ;
delete[] result;
}
```
算好的激活码怎么用呢,我心有一计
## 自动填入离线激活码
我们用 Hopper 来分析一下主程序,根据联网失败的弹窗来搜索一下这个字符串
`Your license could not be activated because the activation server is temporarily unavailable. Try manual activation, or try again later.`
可以看到有两个引用,我们看看来自 `-` 的引用
```objc
else {
r0 = ;
r0 = ;
r23 = r0;
r24 = [ retain];
r0 = ;
r0 = ;
r20 = r0;
r21 = [ retain];
r22 = [ retain];
;
;
;
;
r0 = ;
r0 = ;
r23 = r0;
r24 = [ retain];
r9 = decomp_var_118;
r8 = &decomp_var_118;
if (sign_extend_64(decomp_var_101) >= 0x0) {
r2 = r8;
}
else {
r2 = r9;
}
r25 = [ retain];
[ retain];
r28 = [[[ retain] localizedStringForKey:@"OK" value:@"" table:0x0] retain];
r0 = ;
r0 = ;
r20 = r0;
r21 = [ retain];
r26 = [ retain];
;
;
;
;
;
;
}
```
可以看到这里就是创建了一个带有两个按钮的弹窗,其中一个就是手动激活的
那我们往下翻翻,应该就可以找到手动激活的窗口所在位置了
```objc
if (r20 == 0x3e9) {
r23 = ;
r20 = [ retain];
r24 = [ retain];
r21 = ;
;
;
;
r0 = ;
r0 = ;
r20 = r0;
;
;
r23 = **_NSApp;
r20 = [ retain];
r23 = ;
;
if (r23 == 0x1) {
;
}
;
}
```
到这里就很显然了,`ManualActivationWindowController` 就是我们要找的,去看看这个类有什么成员吧
```objc
@Class ManualActivationWindowController : NSWindowController {
ivar _requestCodeTextField
ivar _activationCodeTextField
ivar _dialogInfoValue
ivar _key
-windowDidLoad
-windowShouldClose:
-initWithDialogInfoValue:key:
-activate:
-cancel:
-.cxx_destruct
}
```
一目了然啊,这个 `_activationCodeTextField` 就是我们想要的,这是一个 `NSTextField` 对象,我们可以直接设置它的内容
那现在就是要找一个合适的时机,通过Hook拿到这个 `_activationCodeTextField` 并写入我们的激活码
看回上面的伪代码,通过调试我们可以知道,请求码在 `retainUntilWindowClosed` 的时候会被算好,也就意味着我们的激活码也算好了,再往下看看,目标就很明确了
```objc
;
r0 = ;
r0 = ;
r20 = r0;
; // 就是你了!
```
函数原型是这样的 `- (void) makeKeyAndOrderFront:(id) sender;`
这个 `sender` 就是我们要的 `ManualActivationWindowController`,那么利用 `ObjC` 的运行时机制,Hook就很好写了
```objc
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);
;
}
return makeKeyAndOrderFront(cls, sel, sender);
}
```
这里就没有定义自己的 `ObjC` 方法然后再 `exchange` 了,直接用 `setImplementation` 即可
```objc
#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);
```
那我们来注入试试吧
可以看到弹出的窗口已经是填入了有效激活码的,直接点Activate就可以了
都已经到这一步了,那顺便把网络屏蔽也在注入库里实现好了
## 屏蔽联网验证
用 Proxyman 抓包可以看到请求激活的域名是 `activate.navicat.com`,但非常不幸的是二进制里搜索不到这个字符串
不过注意到上面的 UA 里面有 CFNetwork,说明它用的是系统提供的网络请求框架,那么大概率就是 `NSURLSession` 了
我们可以在 lldb 中下断点验证,确实用的是 `NSURLSession`,并且调用了 `-` 来初始化
那我们就直接拦截这个函数,把请求改掉就好了,非常简单
```objc
id (*initWithURL)(id cls, SEL sel, NSURL *URL);
id my_initWithURL(id cls, SEL sel, NSURL *URL) {
if () {
// 改成主机环回地址使请求失败
NSURL *newURL = ;
return initWithURL(cls, sel, newURL);
}
return initWithURL(cls, sel, URL);
}
```
## 结语
那么到这里本文就差不多结束了
通过Hook导出表函数以及ObjC运行时实现了理论通杀,并且简化了很多步骤
现在你不需要屏蔽联网,也不用拿私钥加密解密,只需要
1. 把动态库注入到主程序中
2. 填一个激活码选择离线激活
3. Enjoy!
这里放几个生成的激活码
```
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
```
以及完整的注入库代码
```cpp
#include "tinyhook.h"
#include "cryptopp/rsa.h"
#include "cryptopp/osrng.h"
#include <string>
#include <objc/runtime.h>
#import <AppKit/AppKit.h>
#import <Foundation/Foundation.h>
#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;
memcpy(response, request, requestLength - 1);
memcpy(response + requestLength - 1, reg_info.data(), reg_info.length());
// PKCS #1 Padding
byte *padded = new byte; // 2048-bit
padded = byte{0};
padded = byte{1};
memset(padded + 2, 0xff, 256 - 3 - responseLength);
padded = 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;
c.Encode(result, 256);
delete[] padded;
// Base64编码
NSData *res_data = ;
activation_code = ;
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 == '{') {
// 计算好离线激活码
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);
;
}
return makeKeyAndOrderFront(cls, sel, sender);
}
id (*initWithURL)(id cls, SEL sel, NSURL *URL);
id my_initWithURL(id cls, SEL sel, NSURL *URL) {
if () {
// 改成主机环回地址使请求失败
NSURL *newURL = ;
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
```sh
$ 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 属性导致的,移除即可
```sh
$ xattr -cr 'Navicat Premium.app'
```
文中用到的软件
- IDA
- Hopper
- Proxyman
文中用到的代码/工具
- https://github.com/weidai11/cryptopp
- https://github.com/Antibioticss/tinyhook
- https://github.com/tyilo/insert_dylib
特别感谢 @iwolf 大佬的原始分析,以及 Flamingo 大佬提供的替换公钥思路
本帖仅作技术交流,请支持并使用正版软件!
哈,最近在做协议状态机,研究协议相关的安全机制,但是对于加密部分处理一直没什么思路。偶然刷到本文,虽然软件不同但是似乎处理方法挺类似的,感谢提供思路🙏 mark
有空操作一下 我问一下 这个方式破解的话是不是 所有功能 都可以用了
这个思路真不错,非侵入PJ 思路清晰,后面版本还能一直这么玩。 楼主 这个必须使用C吗
zhy1992 发表于 2025-10-9 10:25
楼主 这个必须使用C吗
Crypto++是cpp的库,我还用到了一些objc的函数
最后的源码应该保存为.mm(objc++)文件 牛!成功了, M1 需要用用 clang 手动编译下, 然后再 cp,clang main.c -o insert_dylib Sen 发表于 2025-10-9 14:56
牛!成功了, M1 需要用用 clang 手动编译下, 然后再 cp,clang main.c -o insert_dy ...
大佬,可以跟你学习逆向吗? 弱弱的问一下,偷偷使用navicat破解版,被navicat公司检测到,向公司发了律师函怎么破?{:1_907:}