Refactor code
This commit is contained in:
parent
7e0705dd9d
commit
88ccb398a0
1
.gitignore
vendored
1
.gitignore
vendored
@ -1 +1,2 @@
|
||||
*.DS_Store
|
||||
bin/*
|
||||
233
HOW_DOES_IT_WORK.md
Normal file
233
HOW_DOES_IT_WORK.md
Normal file
@ -0,0 +1,233 @@
|
||||
# Navicat Keygen - How does it work?
|
||||
|
||||
[中文版 How does it work?](HOW_DOES_IT_WORK.zh-CN.md)
|
||||
|
||||
## 1. Keyword Explanation.
|
||||
|
||||
* __Navicat Activation Public Key__
|
||||
|
||||
It is an __RSA-2048__ public key that Navicat used to encrypt or decrypt offline activation information.
|
||||
|
||||
It is stored in
|
||||
|
||||
```
|
||||
Navicat Premium.app/Contents/Resources/rpk
|
||||
```
|
||||
|
||||
You can see it by any kind of text editor. The content is:
|
||||
|
||||
```
|
||||
-----BEGIN PUBLIC KEY-----
|
||||
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAw1dqF3SkCaAAmMzs889I
|
||||
qdW9M2dIdh3jG9yPcmLnmJiGpBF4E9VHSMGe8oPAy2kJDmdNt4BcEygvssEfginv
|
||||
a5t5jm352UAoDosUJkTXGQhpAWMF4fBmBpO3EedG62rOsqMBgmSdAyxCSPBRJIOF
|
||||
R0QgZFbRnU0frj34fiVmgYiLuZSAmIbs8ZxiHPdp1oD4tUpvsFci4QJtYNjNnGU2
|
||||
WPH6rvChGl1IRKrxMtqLielsvajUjyrgOC6NmymYMvZNER3htFEtL1eQbCyTfDmt
|
||||
YyQ1Wt4Ot12lxf0wVIR5mcGN7XCXJRHOFHSf1gzXWabRSvmt1nrl7sW6cjxljuuQ
|
||||
awIDAQAB
|
||||
-----END PUBLIC KEY-----
|
||||
```
|
||||
|
||||
If you have the corresponding private key, please tell me. I would be very appreciated for your generous.
|
||||
|
||||
__NOTICE:__
|
||||
|
||||
Start from __Navicat Premuim 12.0.24 for Mac__, the public key is no longer stored in
|
||||
|
||||
```
|
||||
Navicat Premium.app/Contents/Resources/rpk
|
||||
```
|
||||
|
||||
Instead, the public key is stored in Navicat executable file
|
||||
|
||||
```
|
||||
Navicat Premium.app/Contents/MacOS/Navicat Premium
|
||||
```
|
||||
|
||||
in plaintext. You can see it by searching string `"-----BEGIN PUBLIC KEY----- "`.
|
||||
|
||||
__NOTICE:__
|
||||
|
||||
Start from __Navicat Premium 12.1.14 for Mac__, the public key is still stored in the executable file in plaintext.
|
||||
|
||||
However, it does not load the key from the plaintext. Instead, it loads the key from a piece of ciphertext which is 0x188 bytes long. The ciphertext is
|
||||
|
||||
```c
|
||||
const uint8_t ciphertext[0x188] = {
|
||||
0xfe, 0xfd, 0xfc, 0xf4, 0xfe, 0xd2, 0xf8, 0xf4, 0xf1, 0xd3, 0xde, 0xc7, 0xdf, 0xd3, 0xd0, 0xfd,
|
||||
0x8a, 0xc3, 0x85, 0xf4, 0xf6, 0xe9, 0xfc, 0xfc, 0xf2, 0xf5, 0xfa, 0xf5, 0xf6, 0xe9, 0x81, 0xfb,
|
||||
0xfe, 0xfd, 0xfc, 0xf4, 0xf4, 0xdf, 0xf2, 0xf9, 0xf2, 0xe5, 0xf0, 0xf7, 0xc0, 0x89, 0xdd, 0xcb,
|
||||
0xf5, 0x87, 0xe6, 0xdd, 0xf4, 0xd9, 0xf8, 0xfb, 0xde, 0xf9, 0xcf, 0xc5, 0x8f, 0x80, 0x80, 0xf3,
|
||||
0xc2, 0xd0, 0xe2, 0x8f, 0xfa, 0x8a, 0xdd, 0xf3, 0xd7, 0xdc, 0x86, 0xdc, 0xf0, 0x81, 0xc0, 0xea,
|
||||
0xd0, 0xd9, 0xf9, 0xd8, 0xda, 0xf2, 0xd0, 0xfd, 0xc3, 0xf6, 0xf3, 0x82, 0xf2, 0x81, 0xef, 0xf2,
|
||||
0xe0, 0xf9, 0xf2, 0xd3, 0x8f, 0xd7, 0xe9, 0xfb, 0xca, 0x86, 0xde, 0xfc, 0xf3, 0xd5, 0xdd, 0xf4,
|
||||
0xc7, 0x80, 0xf7, 0xd5, 0xf2, 0xc1, 0xde, 0xcc, 0xc0, 0xc7, 0xf0, 0xd0, 0xd0, 0xd1, 0xd7, 0xcc,
|
||||
0xd2, 0x81, 0xc1, 0x83, 0xdd, 0xd5, 0x8a, 0x8f, 0x81, 0xe1, 0xf4, 0xd9, 0xf3, 0xd7, 0xca, 0xef,
|
||||
0xf9, 0xdf, 0xe1, 0xee, 0xf0, 0xe9, 0xd1, 0xca, 0xf2, 0xe3, 0xf8, 0xf0, 0x83, 0xde, 0xfb, 0xd7,
|
||||
0xf1, 0xc4, 0xfa, 0x85, 0xf2, 0xdd, 0xdd, 0xfd, 0x85, 0x86, 0xc7, 0xf9, 0xc4, 0xc9, 0xf4, 0xf8,
|
||||
0xd4, 0xd9, 0xe6, 0xd2, 0xf6, 0xc1, 0xc1, 0xf9, 0xe0, 0xe4, 0xf7, 0xe4, 0xfd, 0xf1, 0xf6, 0xfc,
|
||||
0xe1, 0x84, 0xe4, 0xd1, 0xed, 0xfe, 0xdb, 0xe8, 0xdd, 0xe1, 0x85, 0xd0, 0xc5, 0xd2, 0x8a, 0x8e,
|
||||
0xd5, 0xdd, 0xe3, 0xdb, 0xd0, 0xe1, 0xd0, 0xf6, 0xc6, 0xee, 0xe6, 0xf7, 0xda, 0xf1, 0xdb, 0xc9,
|
||||
0x8b, 0xee, 0xcd, 0xdf, 0xff, 0xe8, 0xdd, 0xca, 0x82, 0xdb, 0xf1, 0x82, 0xc3, 0xed, 0xc9, 0xcc,
|
||||
0xc0, 0xf2, 0xd6, 0xdf, 0x83, 0xe9, 0xf3, 0xce, 0xea, 0xfa, 0xdf, 0xf8, 0xd9, 0xff, 0xec, 0x88,
|
||||
0xe4, 0xe4, 0xfd, 0x80, 0xc5, 0xce, 0xfa, 0xd2, 0xf4, 0xd8, 0x84, 0xff, 0xe5, 0xf3, 0xcb, 0xc2,
|
||||
0xfe, 0xc0, 0xc4, 0xfa, 0xde, 0xdd, 0xd5, 0xc9, 0xc5, 0xd5, 0xdf, 0xe3, 0xdd, 0xc1, 0xcb, 0xdd,
|
||||
0xfc, 0xf7, 0x83, 0xf8, 0xda, 0xc1, 0xd4, 0xe3, 0xfe, 0xc2, 0xef, 0xf8, 0xf2, 0xea, 0x8a, 0xd2,
|
||||
0xc7, 0xf2, 0xf0, 0xc2, 0xfb, 0x89, 0xdc, 0xeb, 0xd1, 0xf7, 0xcc, 0xe2, 0xd1, 0xfc, 0xd4, 0xce,
|
||||
0xea, 0xcd, 0xe4, 0x87, 0xe0, 0xcc, 0x8d, 0xf5, 0xc7, 0x85, 0x87, 0xda, 0xcf, 0xde, 0x89, 0xcd,
|
||||
0xe5, 0xfd, 0xe7, 0x83, 0xda, 0xdb, 0xfe, 0xf4, 0x84, 0xec, 0xf6, 0xee, 0xfd, 0xea, 0xf1, 0xf5,
|
||||
0xf5, 0xfc, 0xe6, 0xd0, 0x86, 0xdf, 0xc3, 0xe2, 0xe4, 0xd5, 0xd7, 0xe4, 0xe4, 0xce, 0xd4, 0xce,
|
||||
0x82, 0xda, 0xc7, 0xda, 0x80, 0xcb, 0xee, 0x8c, 0xd0, 0xde, 0xcd, 0xda, 0xdd, 0xcd, 0xcc, 0xeb,
|
||||
0xd2, 0xc3, 0xfc, 0xf2, 0xf6, 0xe9, 0xf8, 0xf8
|
||||
};
|
||||
```
|
||||
|
||||
The ciphertext is encrypted by XOR encryption where XOR key is
|
||||
|
||||
```
|
||||
\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba
|
||||
```
|
||||
|
||||
* __Request Code__
|
||||
|
||||
It is a Base64 string that represents 256-bytes-long data, while the 256-bytes-long data is the ciphertext of __Offline Activation Request Information__ encrypted by __Navicat Activation Public Key__.
|
||||
|
||||
* __Offline Activation Request Information__
|
||||
|
||||
It is just a JSON-style UTF-8 string which contains 3 items. Respectively they are `"K"`, `"DI"` and `"P"`, which represent __snKey__, __DeviceIdentifier__, __Platform__.
|
||||
|
||||
Example:
|
||||
|
||||
```
|
||||
{ "K": "xxxxxxxxxxxxxxxx", "P": "Mac 10.13", "DI": "xxxxxxxxxxxxxxxxxxxx" }
|
||||
```
|
||||
|
||||
* __Activation Code__
|
||||
|
||||
It is a Base64 string that represents 256-bytes-long data, while the 256-bytes-long data is the ciphertext of __Offline Activation Response Information__ encrypted by __Navicat Activation Private Key__ which, so far, we don't know.
|
||||
|
||||
* __Offline Activation Response Information__
|
||||
|
||||
Just like __Offline Activation Request Information__, it is also a JSON-style UTF-8 string. But it contains 5 items. Respectively they are `"K"`, `"N"`, `"O"`, `"T"`, '`DI`'.
|
||||
|
||||
`"K"` and `"DI"` has the same meaning mentioned in __Offline Activation Request Information__ and must be same with the corresponding items in __Offline Activation Request Information__.
|
||||
|
||||
`"N"`, `"O"`, `"T"` represent __Name__, __Organization__, __Time__ respectively. __Name__ and __Organization__ are string and the type of __Time__ can be string or integer (Thanks for discoveries from @Wizr, issue #10).
|
||||
|
||||
Differ from Navicat Windows version, `"T"` is mandatory and must have -1 ~ +4 days difference from current time.
|
||||
|
||||
Example:
|
||||
|
||||
```
|
||||
{
|
||||
"DI" : "xxxxxxxxxxxxxxxxxxxx",
|
||||
"T" : "1515770827.925012",
|
||||
"K" : "xxxxxxxxxxxxxxxx",
|
||||
"N" : "DoubleLabyrinth",
|
||||
"O" : "Shadow"
|
||||
}
|
||||
```
|
||||
|
||||
* __snKey__
|
||||
|
||||
It is a 4-block-long string, while every block is 4-chars-long.
|
||||
|
||||
__snKey__ is generated by 10-bytes-long data. In order to explain it easily, I use __uint8_t data[10]__ to represent the 10-bytes-long data.
|
||||
|
||||
1. __data[0]__ and __data[1]__ must be `0x68` and `0x2A` respectively.
|
||||
|
||||
These two bytes are Naivcat signature number.
|
||||
|
||||
2. __data[2]__, __data[3]__ and __data[4]__ can be any byte. Just set them whatever you want.
|
||||
|
||||
3. __data[5]__ and __data[6]__ are related with your Navicat product language.
|
||||
|
||||
| Language | data[5] | data[6] | Discoverer |
|
||||
|------------|-----------|-----------|-----------------|
|
||||
| English | 0xAC | 0x88 | |
|
||||
| 简体中文 | 0xCE | 0x32 | |
|
||||
| 繁體中文 | 0xAA | 0x99 | |
|
||||
| 日本語 | 0xAD | 0x82 | @dragonflylee |
|
||||
| Polski | 0xBB | 0x55 | @dragonflylee |
|
||||
| Español | 0xAE | 0x10 | @dragonflylee |
|
||||
| Français | 0xFA | 0x20 | @Deltafox79 |
|
||||
| Deutsch | 0xB1 | 0x60 | @dragonflylee |
|
||||
| 한국어 | 0xB5 | 0x60 | @dragonflylee |
|
||||
| Русский | 0xEE | 0x16 | @dragonflylee |
|
||||
| Português | 0xCD | 0x49 | @dragonflylee |
|
||||
|
||||
4. __data[7]__ is Navicat product ID. (Thanks @dragonflylee and @Deltafox79)
|
||||
|
||||
|Product Name |Enterprise|Standard|Educational|Essentials|
|
||||
|---------------------|:--------:|:------:|:---------:|:--------:|
|
||||
|Navicat Report Viewer|0x0B | | | |
|
||||
|Navicat Data Modeler | |0x47 |0x4A | |
|
||||
|Navicat Premium |0x65 | |0x66 |0x67 |
|
||||
|Navicat MySQL |0x68 |0x69 |0x6A |0x6B |
|
||||
|Navicat PostgreSQL |0x6C |0x6D |0x6E |0x6F |
|
||||
|Navicat Oracle |0x70 |0x71 |0x72 |0x73 |
|
||||
|Navicat SQL Server |0x74 |0x75 |0x76 |0x77 |
|
||||
|Navicat SQLite |0x78 |0x79 |0x7A |0x7B |
|
||||
|Navicat MariaDB |0x7C |0x7D |0x7E |0x7F |
|
||||
|Navicat MongoDB |0x80 |0x81 |0x82 | |
|
||||
|
||||
5. High 4 bits of __data[8]__ represents __major version number__.
|
||||
|
||||
Low 4 bits is unknown, but we can use it to delay activation deadline. Possible values are `0000` or `0001`.
|
||||
|
||||
__Example:__
|
||||
|
||||
For __Navicat 12 x64__: High 4 bits must be `1100`, which is the binary of number `12`.
|
||||
For __Navicat 11 x64__: High 4 bits must be `1011`, which is the binary of number `11`.
|
||||
|
||||
6. __data[9]__ is unknown, but you can set it by `0xFD`, `0xFC` or `0xFB` if you want to use __not-for-resale license__.
|
||||
|
||||
According to symbol information in __Navicat 12 for Mac x64__ version:
|
||||
|
||||
* `0xFB` is __Not-For-Resale-30-days__ license.
|
||||
* `0xFC` is __Not-For-Resale-90-days__ license.
|
||||
* `0xFD` is __Not-For-Resale-365-days__ license.
|
||||
* `0xFE` is __Not-For-Resale__ license.
|
||||
* `0xFF` is __Site__ license.
|
||||
|
||||
After that. Navicat use __DES__ with __ECB mode__ to encrypt the last 8 bytes which are from __data[2]__ to __data[9]__.
|
||||
|
||||
The DES key is:
|
||||
|
||||
```cpp
|
||||
const uint8_t DESKey = { 0x64, 0xAD, 0xF3, 0x2F, 0xAE, 0xF2, 0x1A, 0x27 };
|
||||
```
|
||||
|
||||
Then use Base32 to encode `uint8_t data[10]` whose encode table is
|
||||
|
||||
```cpp
|
||||
// Thanks for discoveries from @Wizr, issue #10
|
||||
char EncodeTable[] = "ABCDEFGH8JKLMN9PQRSTUVWXYZ234567";
|
||||
```
|
||||
|
||||
After encoding, you will get a 16-char-long string starting with `"NAV"`.
|
||||
|
||||
Finally, divide the 16-char-long string to four 4-chars-long blocks and join them with `"-"` then you will get __snKey__.
|
||||
|
||||
## 2. Activation Process
|
||||
|
||||
1. Check whether __snKey__ that user inputs is valid.
|
||||
|
||||
2. After user clicks `Activate`, Navicat will start online activation first. If fails, user can choose offline activation.
|
||||
|
||||
3. Navicat will use the __snKey__ that user inputs and some information collected from user's machine to generate __Offline Activation Request Information__. Then Navicat will encrypt it by __Navicat Activation Public Key__ and return a Base64-encoded string as __Request Code__.
|
||||
|
||||
4. In legal way, the __Request Code__ should be sent to Navicat official activation server by a Internet-accessible computer. And Navicat official activation server will return a legal __Activation Code__.
|
||||
|
||||
But now, we use keygen to play the official activation server's role.
|
||||
|
||||
1. According to the __Request Code__, get `"DI"` value and `"K"` value.
|
||||
|
||||
2. Fill __Offline Activation Response Information__ with `"K"` value, name, organization name, `"DI"` value and `"T"` value.
|
||||
|
||||
3. Encrypt __Offline Activation Response Information__ by __Navicat Activation Private Key__ and you will get 256-byte-long data.
|
||||
|
||||
4. Encode the 256-byte-long data by Base64. The result is __Activation Code__.
|
||||
|
||||
5. After user input __Activation Code__, offline activation is done successfully.
|
||||
|
||||
230
HOW_DOES_IT_WORK.zh-CN.md
Normal file
230
HOW_DOES_IT_WORK.zh-CN.md
Normal file
@ -0,0 +1,230 @@
|
||||
# Navicat keygen - 注册机是怎么工作的?
|
||||
|
||||
## 1. 关键词解释.
|
||||
|
||||
* __Navicat激活公钥__
|
||||
|
||||
这是一个2048位的RSA公钥,Navicat使用这个公钥来完成相关激活信息的加密和解密。
|
||||
|
||||
这个公钥储存在
|
||||
|
||||
```
|
||||
Navicat Premium.app/Contents/Resources/rpk
|
||||
```
|
||||
|
||||
中,你可以用任何一种文本编辑器打开并查看它。这个公钥的具体内容为:
|
||||
|
||||
```
|
||||
-----BEGIN PUBLIC KEY-----
|
||||
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAw1dqF3SkCaAAmMzs889I
|
||||
qdW9M2dIdh3jG9yPcmLnmJiGpBF4E9VHSMGe8oPAy2kJDmdNt4BcEygvssEfginv
|
||||
a5t5jm352UAoDosUJkTXGQhpAWMF4fBmBpO3EedG62rOsqMBgmSdAyxCSPBRJIOF
|
||||
R0QgZFbRnU0frj34fiVmgYiLuZSAmIbs8ZxiHPdp1oD4tUpvsFci4QJtYNjNnGU2
|
||||
WPH6rvChGl1IRKrxMtqLielsvajUjyrgOC6NmymYMvZNER3htFEtL1eQbCyTfDmt
|
||||
YyQ1Wt4Ot12lxf0wVIR5mcGN7XCXJRHOFHSf1gzXWabRSvmt1nrl7sW6cjxljuuQ
|
||||
awIDAQAB
|
||||
-----END PUBLIC KEY-----
|
||||
```
|
||||
|
||||
如果您有相应的私钥并乐意公开的话欢迎联系我,我将非常感谢您的慷慨。
|
||||
|
||||
__注意:__
|
||||
|
||||
从 __Navicat Premium for Mac 12.0.24__ 开始,公钥不再存储在
|
||||
|
||||
```
|
||||
Navicat Premium.app/Contents/Resources/rpk
|
||||
```
|
||||
|
||||
中。事实上,公钥放在了Navicat的二进制执行文件
|
||||
|
||||
```
|
||||
Navicat Premium.app/Contents/MacOS/Navicat Premium
|
||||
```
|
||||
|
||||
中,你可以通过搜索`"-----BEGIN PUBLIC KEY-----"`来找到它。
|
||||
|
||||
__注意:__
|
||||
|
||||
从 __Navicat Premium for Mac 12.1.14__ 开始,公钥仍然以明文的方式储存在二进制可执行文件中。
|
||||
|
||||
但是Navicat并不从这个明文中加载公钥。事实上,密钥是从一段0x188字节长的密文中加载的。密文为:
|
||||
|
||||
```c
|
||||
const uint8_t ciphertext[0x188] = {
|
||||
0xfe, 0xfd, 0xfc, 0xf4, 0xfe, 0xd2, 0xf8, 0xf4, 0xf1, 0xd3, 0xde, 0xc7, 0xdf, 0xd3, 0xd0, 0xfd,
|
||||
0x8a, 0xc3, 0x85, 0xf4, 0xf6, 0xe9, 0xfc, 0xfc, 0xf2, 0xf5, 0xfa, 0xf5, 0xf6, 0xe9, 0x81, 0xfb,
|
||||
0xfe, 0xfd, 0xfc, 0xf4, 0xf4, 0xdf, 0xf2, 0xf9, 0xf2, 0xe5, 0xf0, 0xf7, 0xc0, 0x89, 0xdd, 0xcb,
|
||||
0xf5, 0x87, 0xe6, 0xdd, 0xf4, 0xd9, 0xf8, 0xfb, 0xde, 0xf9, 0xcf, 0xc5, 0x8f, 0x80, 0x80, 0xf3,
|
||||
0xc2, 0xd0, 0xe2, 0x8f, 0xfa, 0x8a, 0xdd, 0xf3, 0xd7, 0xdc, 0x86, 0xdc, 0xf0, 0x81, 0xc0, 0xea,
|
||||
0xd0, 0xd9, 0xf9, 0xd8, 0xda, 0xf2, 0xd0, 0xfd, 0xc3, 0xf6, 0xf3, 0x82, 0xf2, 0x81, 0xef, 0xf2,
|
||||
0xe0, 0xf9, 0xf2, 0xd3, 0x8f, 0xd7, 0xe9, 0xfb, 0xca, 0x86, 0xde, 0xfc, 0xf3, 0xd5, 0xdd, 0xf4,
|
||||
0xc7, 0x80, 0xf7, 0xd5, 0xf2, 0xc1, 0xde, 0xcc, 0xc0, 0xc7, 0xf0, 0xd0, 0xd0, 0xd1, 0xd7, 0xcc,
|
||||
0xd2, 0x81, 0xc1, 0x83, 0xdd, 0xd5, 0x8a, 0x8f, 0x81, 0xe1, 0xf4, 0xd9, 0xf3, 0xd7, 0xca, 0xef,
|
||||
0xf9, 0xdf, 0xe1, 0xee, 0xf0, 0xe9, 0xd1, 0xca, 0xf2, 0xe3, 0xf8, 0xf0, 0x83, 0xde, 0xfb, 0xd7,
|
||||
0xf1, 0xc4, 0xfa, 0x85, 0xf2, 0xdd, 0xdd, 0xfd, 0x85, 0x86, 0xc7, 0xf9, 0xc4, 0xc9, 0xf4, 0xf8,
|
||||
0xd4, 0xd9, 0xe6, 0xd2, 0xf6, 0xc1, 0xc1, 0xf9, 0xe0, 0xe4, 0xf7, 0xe4, 0xfd, 0xf1, 0xf6, 0xfc,
|
||||
0xe1, 0x84, 0xe4, 0xd1, 0xed, 0xfe, 0xdb, 0xe8, 0xdd, 0xe1, 0x85, 0xd0, 0xc5, 0xd2, 0x8a, 0x8e,
|
||||
0xd5, 0xdd, 0xe3, 0xdb, 0xd0, 0xe1, 0xd0, 0xf6, 0xc6, 0xee, 0xe6, 0xf7, 0xda, 0xf1, 0xdb, 0xc9,
|
||||
0x8b, 0xee, 0xcd, 0xdf, 0xff, 0xe8, 0xdd, 0xca, 0x82, 0xdb, 0xf1, 0x82, 0xc3, 0xed, 0xc9, 0xcc,
|
||||
0xc0, 0xf2, 0xd6, 0xdf, 0x83, 0xe9, 0xf3, 0xce, 0xea, 0xfa, 0xdf, 0xf8, 0xd9, 0xff, 0xec, 0x88,
|
||||
0xe4, 0xe4, 0xfd, 0x80, 0xc5, 0xce, 0xfa, 0xd2, 0xf4, 0xd8, 0x84, 0xff, 0xe5, 0xf3, 0xcb, 0xc2,
|
||||
0xfe, 0xc0, 0xc4, 0xfa, 0xde, 0xdd, 0xd5, 0xc9, 0xc5, 0xd5, 0xdf, 0xe3, 0xdd, 0xc1, 0xcb, 0xdd,
|
||||
0xfc, 0xf7, 0x83, 0xf8, 0xda, 0xc1, 0xd4, 0xe3, 0xfe, 0xc2, 0xef, 0xf8, 0xf2, 0xea, 0x8a, 0xd2,
|
||||
0xc7, 0xf2, 0xf0, 0xc2, 0xfb, 0x89, 0xdc, 0xeb, 0xd1, 0xf7, 0xcc, 0xe2, 0xd1, 0xfc, 0xd4, 0xce,
|
||||
0xea, 0xcd, 0xe4, 0x87, 0xe0, 0xcc, 0x8d, 0xf5, 0xc7, 0x85, 0x87, 0xda, 0xcf, 0xde, 0x89, 0xcd,
|
||||
0xe5, 0xfd, 0xe7, 0x83, 0xda, 0xdb, 0xfe, 0xf4, 0x84, 0xec, 0xf6, 0xee, 0xfd, 0xea, 0xf1, 0xf5,
|
||||
0xf5, 0xfc, 0xe6, 0xd0, 0x86, 0xdf, 0xc3, 0xe2, 0xe4, 0xd5, 0xd7, 0xe4, 0xe4, 0xce, 0xd4, 0xce,
|
||||
0x82, 0xda, 0xc7, 0xda, 0x80, 0xcb, 0xee, 0x8c, 0xd0, 0xde, 0xcd, 0xda, 0xdd, 0xcd, 0xcc, 0xeb,
|
||||
0xd2, 0xc3, 0xfc, 0xf2, 0xf6, 0xe9, 0xf8, 0xf8
|
||||
};
|
||||
```
|
||||
|
||||
这个密文是采用XOR加密得来,XOR密钥为
|
||||
|
||||
```
|
||||
\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba
|
||||
```
|
||||
|
||||
* __请求码__
|
||||
|
||||
这是一个Base64编码的字符串,代表的是长度为256字节的数据。这256字节的数据是 __离线激活信息__ 用 __Navicat激活公钥__ 加密的密文。
|
||||
|
||||
* __离线激活请求信息__
|
||||
|
||||
这是一个JSON风格的UTF-8字符串。它包含了3个Key:`"K"`、`"DI"`和`"P"`,分别代表 __序列号__、__设备识别码__(与你的电脑硬件信息相关)和 __平台__ (其实就是操作系统类型)。
|
||||
|
||||
例如:
|
||||
|
||||
```
|
||||
{ "K": "xxxxxxxxxxxxxxxx", "P": "Mac 10.13", "DI": "xxxxxxxxxxxxxxxxxxxx" }
|
||||
```
|
||||
|
||||
* __激活码__
|
||||
|
||||
这是一个Base64编码的字符串,代表的是长度为256字节的数据。这256字节的数据是 __离线激活回复信息__ 用 __Navicat激活私钥__ 加密的密文。目前我们不知道官方的 __Navicat激活私钥__,所以我们得替换掉软件里的公钥。
|
||||
|
||||
* __离线激活回复信息__
|
||||
|
||||
和 __离线激活请求信息__ 一样,它也是一个JSON风格的UTF-8字符串。但是它包含5个Key,分别为`"K"`、`"N"`、`"O"`、`"T"` 和 `"DI"`.
|
||||
|
||||
`"K"` 和 `"DI"` 的意义与 __离线激活请求信息__ 中的相同,且Value必须与 __离线激活请求信息__ 中的相同。
|
||||
|
||||
`"N"`、`"O"`、`"T"` 分别代表 __注册名__、__组织__、__授权时间__。
|
||||
|
||||
__注册名__ 和 __组织__ 的值类型为UTF-8编码的字符串。__授权时间__ 的值类型可以为字符串或整数(感谢@Wizr在issue #10中的报告)。
|
||||
|
||||
和Windows版本Navicat不同的是,`"T"` 项不可以省略,并且和当前时间的差距必须在-1 ~ +4天之内。
|
||||
|
||||
例如:
|
||||
|
||||
```
|
||||
{
|
||||
"DI" : "xxxxxxxxxxxxxxxxxxxx",
|
||||
"T" : "1515770827.925012",
|
||||
"K" : "xxxxxxxxxxxxxxxx",
|
||||
"N" : "DoubleLabyrinth",
|
||||
"O" : "Shadow"
|
||||
}
|
||||
```
|
||||
|
||||
* __序列号__
|
||||
|
||||
这是一个被分为了4个部分的字符串,其中每个部分都是4个字符长。
|
||||
|
||||
__序列号__ 是通过10个字节的数据来生成的。为了表达方便,我用 __uint8_t data[10]__ 来表示这10个字节。
|
||||
|
||||
1. __data[0]__ 和 __data[1]__ 必须分别为 `0x68` 和 `0x2A`。
|
||||
|
||||
这两个字节为Navicat的标志数。
|
||||
|
||||
2. __data[2]__、__data[3]__ 和 __data[4]__ 可以是任意字节,你想设成什么都行。
|
||||
|
||||
3. __data[5]__ 和 __data[6]__ 是Navicat的语言标志,值如下:
|
||||
|
||||
| 语言类型 | data[5] | data[6] | 发现者 |
|
||||
|------------|:---------:|:---------:|-----------------|
|
||||
| English | 0xAC | 0x88 | |
|
||||
| 简体中文 | 0xCE | 0x32 | |
|
||||
| 繁體中文 | 0xAA | 0x99 | |
|
||||
| 日本語 | 0xAD | 0x82 | @dragonflylee |
|
||||
| Polski | 0xBB | 0x55 | @dragonflylee |
|
||||
| Español | 0xAE | 0x10 | @dragonflylee |
|
||||
| Français | 0xFA | 0x20 | @Deltafox79 |
|
||||
| Deutsch | 0xB1 | 0x60 | @dragonflylee |
|
||||
| 한국어 | 0xB5 | 0x60 | @dragonflylee |
|
||||
| Русский | 0xEE | 0x16 | @dragonflylee |
|
||||
| Português | 0xCD | 0x49 | @dragonflylee |
|
||||
|
||||
4. __data[7]__ 是Navicat产品ID。(感谢 @dragonflylee 和 @Deltafox79提供的数据)
|
||||
|
||||
|产品名 |Enterprise|Standard|Educational|Essentials|
|
||||
|---------------------|:--------:|:------:|:---------:|:--------:|
|
||||
|Navicat Report Viewer|0x0B | | | |
|
||||
|Navicat Data Modeler | |0x47 |0x4A | |
|
||||
|Navicat Premium |0x65 | |0x66 |0x67 |
|
||||
|Navicat MySQL |0x68 |0x69 |0x6A |0x6B |
|
||||
|Navicat PostgreSQL |0x6C |0x6D |0x6E |0x6F |
|
||||
|Navicat Oracle |0x70 |0x71 |0x72 |0x73 |
|
||||
|Navicat SQL Server |0x74 |0x75 |0x76 |0x77 |
|
||||
|Navicat SQLite |0x78 |0x79 |0x7A |0x7B |
|
||||
|Navicat MariaDB |0x7C |0x7D |0x7E |0x7F |
|
||||
|Navicat MongoDB |0x80 |0x81 |0x82 | |
|
||||
|
||||
5. __data[8]__ 的高4位代表 __版本号__。低4位未知,但可以用来延长激活期限,可取的值有`0000`和`0001`。
|
||||
|
||||
例如:
|
||||
|
||||
对于 __Navicat 12__: 高4位必须是`1100`,为`12`的二进制形式。
|
||||
对于 __Navicat 11__: 高4位必须是`1011`,为`11`的二进制形式。
|
||||
|
||||
6. __data[9]__ 目前暂未知,但如果你想要 __not-for-resale license__ 的话可以设成`0xFD`、`0xFC`或`0xFB`。
|
||||
|
||||
根据 __Navicat 12 for Mac x64__ 版本残留的符号信息可知:
|
||||
|
||||
* `0xFB`是 __Not-For-Resale-30-days__ license.
|
||||
* `0xFC`是 __Not-For-Resale-90-days__ license.
|
||||
* `0xFD`是 __Not-For-Resale-365-days__ license.
|
||||
* `0xFE`是 __Not-For-Resale__ license.
|
||||
* `0xFF`是 __Site__ license.
|
||||
|
||||
之后Navicat使用 __ECB__ 模式的 __DES__ 算法来加密 __data[10]__ 的后8字节,也就是 __data[2]__ 到 __data[9]__ 的部分。
|
||||
|
||||
相应的DES密钥为:
|
||||
|
||||
```cpp
|
||||
const uint8_t DESKey = { 0x64, 0xAD, 0xF3, 0x2F, 0xAE, 0xF2, 0x1A, 0x27 };
|
||||
```
|
||||
|
||||
之后使用Base32编码 __data[10]__,其中编码表改为:
|
||||
|
||||
```cpp
|
||||
// Thanks for discoveries from @Wizr, issue #10
|
||||
char EncodeTable[] = "ABCDEFGH8JKLMN9PQRSTUVWXYZ234567";
|
||||
```
|
||||
|
||||
编码之后你应该会得到一个16字节长的字符串,并且以"NAV"打头。
|
||||
|
||||
将16字节的字符串分成4个4字节的小块,然后用`"-"`连接就可以得到 __序列号__。
|
||||
|
||||
## 2. 激活过程
|
||||
|
||||
1. 检查用户输入的 __序列号__ 是否合法。
|
||||
|
||||
2. 在用户点击了`激活`按钮之后,Navicat会先尝试在线激活。如果失败,用户可以选择离线激活。
|
||||
|
||||
3. Navicat会使用用户输入的 __序列号__ 以及从用户电脑收集来的信息生成 __离线激活请求信息__,然后用 __Navicat激活公钥__ 加密,并将密文用Base64编码,最后得到 __请求码__。
|
||||
|
||||
4. 正常流程下,__请求码__ 应该通过可联网的电脑发送给Navicat的官方激活服务器。之后Navicat的官方激活服务器会返回一个合法的 __激活码__。
|
||||
|
||||
但现在我们使用注册机来扮演官方激活服务器的角色,只是Navicat软件里的激活公钥得换成自己的公钥:
|
||||
|
||||
1. 根据 __请求码__, 获得`"DI"`值和`"K"`值。
|
||||
|
||||
2. 用`"K"`值、用户名、组织名和`"DI"`值填写 __离线激活回复信息__。
|
||||
|
||||
3. 用自己的2048位RSA私钥加密 __离线激活回复信息__,你将会得到256字节的密文。
|
||||
|
||||
4. 用Base64编码这256字节的密文,就可以得到 __激活码__。
|
||||
|
||||
5. 在Navicat软件中填入 __激活码__ 即可完成离线激活。
|
||||
57
Makefile
Normal file
57
Makefile
Normal file
@ -0,0 +1,57 @@
|
||||
CC = g++
|
||||
OPENSSL_INCLUDE_PATH = /usr/local/opt/openssl/include
|
||||
OPENSSL_LIB_PATH = /usr/local/opt/openssl/lib
|
||||
|
||||
OUTPUT_DIR = ./bin/
|
||||
PATCHER_DIR = ./navicat-patcher/
|
||||
KEYGEN_DIR = ./navicat-keygen/
|
||||
keygen_output = $(OUTPUB_DIR)navicat-keygen
|
||||
|
||||
PATCHER_HEADER = \
|
||||
$(PATCHER_DIR)FileMapper.hpp \
|
||||
$(PATCHER_DIR)Helper.hpp \
|
||||
$(PATCHER_DIR)RSACipher.hpp \
|
||||
$(PATCHER_DIR)Solutions.hpp
|
||||
|
||||
PATCHER_SOURCE = \
|
||||
$(PATCHER_DIR)Helper.cpp \
|
||||
$(PATCHER_DIR)main.cpp \
|
||||
$(PATCHER_DIR)Solution0.cpp \
|
||||
$(PATCHER_DIR)Solution1.cpp
|
||||
|
||||
PATCHER_BINARY = $(OUTPUT_DIR)navicat-patcher
|
||||
|
||||
KEYGEN_HEADER = \
|
||||
$(KEYGEN_DIR)Helper.hpp \
|
||||
$(KEYGEN_DIR)RSACipher.hpp \
|
||||
$(KEYGEN_DIR)DESCipher.hpp \
|
||||
$(KEYGEN_DIR)NavicatKeygen.hpp
|
||||
|
||||
KEYGEN_SOURCE = \
|
||||
$(KEYGEN_DIR)Helper.cpp \
|
||||
$(KEYGEN_DIR)main.cpp
|
||||
|
||||
KEYGEN_BINARY = $(OUTPUT_DIR)navicat-keygen
|
||||
|
||||
patcher: $(PATCHER_HEADER) $(PATCHER_SOURCE)
|
||||
@if [ ! -d $(OUTPUT_DIR) ]; then mkdir -p $(OUTPUT_DIR); fi
|
||||
$(CC) -std=c++11 -O2 -I$(OPENSSL_INCLUDE_PATH) -L$(OPENSSL_LIB_PATH) -lcrypto $(PATCHER_SOURCE) -o $(PATCHER_BINARY)
|
||||
|
||||
keygen: $(KEYGEM_HEADER) $(KEYGEN_SOURCE)
|
||||
@if [ ! -d $(OUTPUT_DIR) ]; then mkdir -p $(OUTPUT_DIR); fi
|
||||
$(CC) -std=c++11 -O2 -I$(OPENSSL_INCLUDE_PATH) -L$(OPENSSL_LIB_PATH) -lcrypto $(KEYGEN_SOURCE) -o $(KEYGEN_BINARY)
|
||||
|
||||
all: patcher keygen
|
||||
@echo 'Done.'
|
||||
|
||||
.PHONY: all
|
||||
|
||||
clean:
|
||||
ifeq ($(wildcard $(PATCHER_BINARY)), $(PATCHER_BINARY))
|
||||
rm $(PATCHER_BINARY)
|
||||
endif
|
||||
|
||||
ifeq ($(wildcard $(KEYGEN_BINARY)), $(KEYGEN_BINARY))
|
||||
rm $(KEYGEN_BINARY)
|
||||
endif
|
||||
|
||||
370
README.md
370
README.md
@ -1,291 +1,189 @@
|
||||
# Navicat Keygen
|
||||
|
||||
[中文版README](README.zh-CN.md)
|
||||
[中文版README](README.zh-CN.md)
|
||||
|
||||
This repository will tell you how Navicat offline activation works.
|
||||
This repository will tell you how Navicat offline activation works.
|
||||
|
||||
## 1. Keyword Explanation.
|
||||
[How does it work?](HOW_DOES_IT_WORK.md)
|
||||
|
||||
* __Navicat Activation Public Key__
|
||||
## 1. How to build
|
||||
|
||||
It is a __RSA-2048__ public key that Navicat used to encrypt or decrypt offline activation information.
|
||||
* Before you build keygen, you should make sure you have `OpenSSL` lib and `rapidjson` lib.
|
||||
|
||||
If you have `brew`, you can install them by
|
||||
|
||||
```bash
|
||||
$ brew install openssl
|
||||
$ brew install rapidjson
|
||||
```
|
||||
|
||||
It is stored in __Navicat Premium.app/Contents/Resources/rpk__. You can see it by any kind of text editor. The concrete content is:
|
||||
* Clone `mac` branch and build keygen and patcher:
|
||||
|
||||
> -----BEGIN PUBLIC KEY-----
|
||||
> MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAw1dqF3SkCaAAmMzs889I
|
||||
> qdW9M2dIdh3jG9yPcmLnmJiGpBF4E9VHSMGe8oPAy2kJDmdNt4BcEygvssEfginv
|
||||
> a5t5jm352UAoDosUJkTXGQhpAWMF4fBmBpO3EedG62rOsqMBgmSdAyxCSPBRJIOF
|
||||
> R0QgZFbRnU0frj34fiVmgYiLuZSAmIbs8ZxiHPdp1oD4tUpvsFci4QJtYNjNnGU2
|
||||
> WPH6rvChGl1IRKrxMtqLielsvajUjyrgOC6NmymYMvZNER3htFEtL1eQbCyTfDmt
|
||||
> YyQ1Wt4Ot12lxf0wVIR5mcGN7XCXJRHOFHSf1gzXWabRSvmt1nrl7sW6cjxljuuQ
|
||||
> awIDAQAB
|
||||
> -----END PUBLIC KEY-----
|
||||
```bash
|
||||
$ git clone -b mac https://github.com/DoubleLabyrinth/navicat-keygen.git
|
||||
$ cd navicat-keygen
|
||||
$ make all
|
||||
```
|
||||
|
||||
If you have the corresponding private key, please tell me. I would be very appreciated for your generous.
|
||||
You will see two executable files in `bin/` folder:
|
||||
|
||||
__NOTICE:__
|
||||
```bash
|
||||
$ ls bin/
|
||||
navicat-keygen navicat-patcher
|
||||
```
|
||||
|
||||
Start from __Navicat Premuim 12.0.24 for Mac__, the public key is no longer stored in __Navicat Premium.app/Contents/Resources/rpk__. Instead, the public key is stored in Navicat executable file __Navicat Premium.app/Contents/MacOS/Navicat Premium__. You can see it by searching string `"-----BEGIN PUBLIC KEY----- "`.
|
||||
## 2. How to Use
|
||||
|
||||
* __Request Code__
|
||||
1. Build keygen and patcher.
|
||||
|
||||
It is a Base64 string that represents 256-bytes-long data, while the 256-bytes-long data is the cipher text of the __offline activation information__ encrypted by __Navicat Activation Public Key__.
|
||||
2. Backup your `Navicat Premium.app/Contents/MacOS/Navicat Premium` and all of your saved database connection configurations (with password).
|
||||
|
||||
* __Offline Activation Request Information__
|
||||
3. Remove all connections, if have, that Navicat saved in `Keychain.app`.
|
||||
|
||||
It is just a JSON-style ASCII string which contains 3 items. Respectively they are `"K"`, `"DI"` and `"P"`, which represent __snKey__, __DeviceIdentifier__ (related with your machine), __Platform__ (Appropriately speaking, it should be OS Type).
|
||||
You can find them by search with keyword `navicat`.
|
||||
|
||||
Like:
|
||||
> {
|
||||
> "K": "xxxxxxxxxxxxxxxx",
|
||||
> "P": "Mac 10.13",
|
||||
> "DI": "xxxxxxxxxxxxxxxxxxxx"
|
||||
> }
|
||||
4. Use `navicat-patcher` to replace __Navicat Activation Public Key__.
|
||||
|
||||
```
|
||||
Usage:
|
||||
navicat-patcher <navicat executable file> [RSA-2048 PrivateKey(PEM file)]
|
||||
```
|
||||
|
||||
* __Activation Code__
|
||||
* `<navicat executable file>`: The path to Navicat executable file.
|
||||
|
||||
__This parameter must be specified.__
|
||||
|
||||
It is a Base64 string that represents 256-bytes-long data, while the 256-bytes-long data is the cipher text of the __offline activation response information__ encrypted by __Navicat Activation Private Key__ (so far, we don't know official activation private key).
|
||||
* `[RSA-2048 PrivateKey(PEM file)]`: The path to an RSA-2048 private key file.
|
||||
|
||||
__This parameter is optional.__ If not specified, `navicat-patcher` will generate a new RSA-2048 private key file `RegPrivateKey.pem` at current directory.
|
||||
|
||||
* __Offline Activation Response Information__
|
||||
__Example:__
|
||||
|
||||
Just like __Offline Activation Request Information__, it is also a JSON-style ASCII string. But it contains 5 items. Respectively they are `"K"`, `"N"`, `"O"`, `"T"`, '`DI`'.
|
||||
```
|
||||
$ ./navicat-patcher /Applications/Navicat\ Premium.app/Contents/MacOS/Navicat\ Premium
|
||||
```
|
||||
|
||||
`"K"` and `"DI"` has the same meaning mentioned in __Offline Activation Request Information__ and must be same with the corresponding items in __Offline Activation Request Information__.
|
||||
__FOR Navicat Premium version < 12.0.24 ONLY:__
|
||||
|
||||
`"N"`, `"O"`, `"T"` represent __Name__, __Organization__, __Time__ respectively. __Name__ and __Organization__ are string and the type of __Time__ can be string or integer (Thanks for discoveries from @Wizr, issue #10).
|
||||
`navicat-patcher` will not modify target file. But you should use openssl to convert `RegPrivateKey.pem` to `rpk` file and replace
|
||||
|
||||
Differ from Navicat Windows version, `"T"` is mandatory and must be -1 ~ +4 days difference from current time. Here is an example of __Offline Activation Response Information__:
|
||||
```
|
||||
/Applications/Navicat Premium.app/Contents/Resources/rpk
|
||||
```
|
||||
|
||||
> {
|
||||
> "DI" : "xxxxxxxxxxxxxxxxxxxx",
|
||||
> "T" : "1515770827.925012",
|
||||
> "K" : "xxxxxxxxxxxxxxxx",
|
||||
> "N" : "DoubleLabyrinth",
|
||||
> "O" : "Shadow"
|
||||
> }
|
||||
by it.
|
||||
|
||||
* __snKey__
|
||||
If you don't know how to use openssl, here's an example:
|
||||
|
||||
It is a 4-block-long string, while every block is 4-chars-long.
|
||||
```bash
|
||||
$ openssl rsa -in RegPrivateKey.pem -pubout -out rpk
|
||||
```
|
||||
|
||||
__snKey__ is generated by 10-bytes-long data. In order to explain it easily, I use __data[10]__ to represent the 10-bytes-long data.
|
||||
5. __Generate a self-signed code-sign certificate and always trust it.__
|
||||
|
||||
1. __data[0]__ and __data[1]__ must be `0x68` and `0x2A` respectively.
|
||||
__Then use `codesign` to re-sign `Navicat Premium.app`.__
|
||||
|
||||
_`May change when Navicat product changes. Uncertain yet.`_
|
||||
```
|
||||
$ codesign -f -s "Your self-signed code-sign certificate name" <path to Navicat Premium.app>
|
||||
```
|
||||
|
||||
2. __data[2]__, __data[3]__ and __data[4]__ can be any byte. Just set them whatever you want.
|
||||
__NOTICE:__
|
||||
|
||||
"Your self - signed code - sign certificate name" is the name of your certificate, not path.
|
||||
|
||||
3. __data[5]__ and __data[6]__ are related with your Navicat product language. It depends.
|
||||
__Example:__
|
||||
|
||||
~~_`May change when Navicat product changes. Uncertain yet.`_~~
|
||||
_`Must change when Navicat product changes. Confirmed yet.`_
|
||||
```bash
|
||||
$ codesign -f -s "foobar" /Applications/Navicat\ Premium.app/
|
||||
```
|
||||
|
||||
| Language | data[5] | data[6] | Discoverer |
|
||||
|------------|-----------|-----------|-----------------|
|
||||
| English | 0xAC | 0x88 | |
|
||||
| 简体中文 | 0xCE | 0x32 | |
|
||||
| 繁體中文 | 0xAA | 0x99 | |
|
||||
| 日本語 | 0xAD | 0x82 | @dragonflylee |
|
||||
| Polski | 0xBB | 0x55 | @dragonflylee |
|
||||
| Español | 0xAE | 0x10 | @dragonflylee |
|
||||
| Français | 0xFA | 0x20 | @Deltafox79 |
|
||||
| Deutsch | 0xB1 | 0x60 | @dragonflylee |
|
||||
| 한국어 | 0xB5 | 0x60 | @dragonflylee |
|
||||
| Русский | 0xEE | 0x16 | @dragonflylee |
|
||||
| Português | 0xCD | 0x49 | @dragonflylee |
|
||||
6. Then use `navicat-keygen` to generate __snKey__ and __Activation Code__.
|
||||
|
||||
According to __Navicat 12 for Mac x64__ version, what IDA 7.0 indicates is that this two bytes are product signature.
|
||||
```
|
||||
Usage:
|
||||
navicat-keygen <RSA-2048 PrivateKey(PEM file)>
|
||||
```
|
||||
|
||||
4. __data[7]__ represents whether it is __commercial license__ or __non-commercial license__.
|
||||
* `<RSA-2048 PrivateKey(PEM file)>`: The path to a RSA-2048 private key file.
|
||||
|
||||
__This parameter must be specified.__
|
||||
|
||||
For __Navicat 12 x64__: `0x65` is __commercial license__, `0x66` is __non-commercial license__.
|
||||
For __Navicat 11 x64__: `0x15` is __commercial license__, `0x16` is __non-commercial license__.
|
||||
__Example:__
|
||||
|
||||
_`May change when Navicat product changes. Uncertain yet.`_
|
||||
_`Must change when version change.`_
|
||||
```bash
|
||||
./navicat-keygen ./RegPrivateKey.pem
|
||||
```
|
||||
|
||||
According to __Navicat 12 for Mac x64__ version, what IDA 7.0 indicates is that commercial license is __Enterprise License__ and non-commercial license is __Educational License__.
|
||||
You will be asked to select Navicat language and input major version number. After that an randomly generated __snKey__ will be given.
|
||||
|
||||
5. High 4 bits of __data[8]__ represents __version number__. Low 4 bits is unknown, but we can use it to delay activation deadline. Possible value is `0000` or `0001`.
|
||||
```
|
||||
Which is your Navicat Premium language?
|
||||
0. English
|
||||
1. Simplified Chinese
|
||||
2. Traditional Chinese
|
||||
3. Japanese
|
||||
4. Polish
|
||||
5. Spanish
|
||||
6. French
|
||||
7. German
|
||||
8. Korean
|
||||
9. Russian
|
||||
10. Portuguese
|
||||
|
||||
For __Navicat 12 x64__: High 4 bits must be `1100`, which is the binary of number `12`.
|
||||
For __Navicat 11 x64__: High 4 bits must be `1011`, which is the binary of number `11`.
|
||||
(Input index)> 1
|
||||
(Input major version number, range: 0 ~ 15, default: 12)> 12
|
||||
|
||||
6. __data[9]__ is unknown, but you can set it `0xFD` or `0xFC` or `0xFB` if you want to use __not-for-resale license__. This must not be `0x00`. But other value is OK.
|
||||
Serial number:
|
||||
NAVK-MWQR-LNXV-886V
|
||||
|
||||
According to __Navicat 12 for Mac x64__ version, what IDA 7.0 indicates is that:
|
||||
Your name:
|
||||
```
|
||||
|
||||
* `0xFB` is __Not-For-Resale-30-days__ license.
|
||||
* `0xFC` is __Not-For-Resale-90-days__ license.
|
||||
* `0xFD` is __Not-For-Resale-365-days__ license.
|
||||
* `0xFE` is __Not-For-Resale__ license.
|
||||
* `0xFF` is __Site__ license.
|
||||
You can use this __snKey__ to activate your Navicat preliminarily.
|
||||
|
||||
Then you will be asked to input `Your name` and `Your organization`. Just set them whatever you want, but not too long.
|
||||
|
||||
-----------------
|
||||
```bash
|
||||
Your name: DoubleLabyrinth
|
||||
Your organization: DoubleLabyrinth
|
||||
Input request code (in Base64), input empty line to end:
|
||||
```
|
||||
|
||||
After that, you will be asked to input the request code. Now __DO NOT CLOSE KEYGEN__.
|
||||
|
||||
After that. Navicat use __DES__ with __ECB mode__ to encrypt the last 8 bytes which are from __data[2]__ to __data[9]__.
|
||||
7. __Disconnect your network__ and open Navicat Premium.
|
||||
|
||||
The DES key is:
|
||||
Find and click `Registration`. Fill license key by __Serial number__ that the keygen gave and click `Activate`.
|
||||
|
||||
```cpp
|
||||
unsigned char DESKey = { 0x64, 0xAD, 0xF3, 0x2F, 0xAE, 0xF2, 0x1A, 0x27 };
|
||||
```
|
||||
8. Generally online activation will fail and Navicat will ask you do `Manual Activation`, just choose it.
|
||||
|
||||
Then encode the 10-bytes-long data:
|
||||
9. Copy your request code and paste it in the keygen. Input empty line to tell the keygen that your input ends.
|
||||
|
||||
1. Regard __data[10]__ as a 80-bits-long data.
|
||||
```bash
|
||||
Your name: DoubleLabyrinth
|
||||
Your organization: DoubleLabyrinth
|
||||
|
||||
If __data[10]__ starts with `0x68` and `0x2A`, so the 80-bits-long data is `01011000 00101010......`
|
||||
Input request code (in Base64), input empty line to end:
|
||||
q/cv0bkTrG1YDkS+fajFdi85bwNVBD/lc5jBYJPOSS5bfl4DdtnfXo+RRxdMjJtEcYQnvLPi2LF0
|
||||
OB464brX9dqU29/O+A3qstSyhBq5//iezxfu2Maqca4y0rVtZgQSpEnZ0lBNlqKXv7CuTUYCS1pm
|
||||
tEPgwJysQTMUZf7tu5MR0cQ+hY/AlyQ9iKrQAMhHklqZslaisi8VsnoIqH56vfTyyUwUQXrFNc41
|
||||
qG5zZNsXu/NI79JOo7qTvcFHQT/k5cTadbKTxY+9c5eh+nF3JR7zEa2BDDfdQRLNvy4DTSyxdYXd
|
||||
sAk/YPU+JdWI+8ELaa0SuAuNzr5fEkD6NDSG2A==
|
||||
|
||||
2. Divide the 80-bits-long data as 16 5-bits-long blocks.
|
||||
Request Info:
|
||||
{"K":"NAVADHCNP2OIDV46", "DI":"Y2eJk9vrvfGudPG7Mbdn", "P":"MAC"}
|
||||
|
||||
If __data[10]__ starts with `0x68` and `0x2A`, so the 80-bits-long data is `01011`, `00000`, `10101`, `0....`, ...
|
||||
Response Info:
|
||||
{"K":"NAVADHCNP2OIDV46","DI":"Y2eJk9vrvfGudPG7Mbdn","N":"DoubleLabyrinth","O":"DoubleLabyrinth","T":1537630251}
|
||||
|
||||
3. So the value every block is less than 32. Map them by a encode-table:
|
||||
License:
|
||||
oyoMYr9cfVGXeT7F1dqBwHsB/vvWj6SUL6aR+Kzb0lm5IyEj1CgovuSq+qMzFfx+
|
||||
oHMFaGKFg6viOY2hfJcrO2Vdq0hXZS/B/Ie3jBS2Ov37v8e3ufVajaH+wLkmEpLd
|
||||
xppCVLkDQjIHYR2IPz5s/L/RuWqDpEY4TPmGFF6q+xQMnqQA3vXPyG+JYMARXLru
|
||||
Y1gCDLN30v3DpyOeqKmFjUqiHK5h8s0NYiH2OpMyaCpi12JsF23miP89ldQp3+SJ
|
||||
8moo0cNGy7sFp2gX9ol2zVoo7qxfYlLl03f7CALJ6im0sx4yBsmlzFDdvpQUbXk8
|
||||
YZ5rT4LML2Fx6Wgnnklb5g==
|
||||
```
|
||||
|
||||
```cpp
|
||||
// Thanks for discoveries from @Wizr, issue #10
|
||||
char EncodeTable[] = "ABCDEFGH8JKLMN9PQRSTUVWXYZ234567";
|
||||
```
|
||||
10. Finally, you will get __Activation Code__ which looks like a Base64 string. Just copy it and paste it in Navicat `Manual Activation` window, then click `Activate`. If nothing wrong, activation should be done successfully.
|
||||
|
||||
Then you will get a 16-char-long string.
|
||||
|
||||
If __data[10]__ starts with `0x68` and `0x2A`, so after encoded, it should starts with `"N"`, `"A"`, `"V"`.
|
||||
|
||||
4. Divide the 16-char-long string to four 4-chars-long blocks, Then you get __snKey__.
|
||||
|
||||
## 3. Activation Process
|
||||
|
||||
1. Check whether __sn_Key__ that user inputs is legal.
|
||||
|
||||
2. After user clicks `Activate`, Navicat will start online activation first. If fails, user can choose offline activation.
|
||||
|
||||
3. Navicat will use the __snKey__ that user inputs and some information collected from user's machine to generate __Offline Activation Request Information__, then encrypt it by __Navicat Activation Public Key__ and return Base64-encoded string as __Request Code__.
|
||||
|
||||
4. In legal way, the __Request Code__ should be sent to Navicat official activation server by a Internet-accessible computer. And Navicat official activation server will return a legal __Activation Code__.
|
||||
|
||||
But now, we use keygen to play the official activation server's role.
|
||||
|
||||
1. According to the __Request Code__, Get `"DI"` value and `"K"` value.
|
||||
|
||||
2. Fill __Offline Activation Response Information__ with `"K"` value, name, organization name and `"DI"` value.
|
||||
|
||||
3. Encrypt __Offline Activation Response Information__ by __Navicat Activation Private Key__ and you will get 256-byte-long data.
|
||||
|
||||
4. Encode 256-byte-long data by Base64. The result is __Activation Code__.
|
||||
|
||||
5. Input __Activation Code__, then offline activation is done.
|
||||
|
||||
## 4. How to build
|
||||
|
||||
* Before you build keygen, you should make sure you have installed OpenSSL.
|
||||
If you have `brew`, you can install it by `brew install openssl`.
|
||||
|
||||
```bash
|
||||
$ cd navicat-keygen
|
||||
$ make release
|
||||
```
|
||||
|
||||
* Build patcher if your Navicat version is or is after __12.0.24__.
|
||||
|
||||
```bash
|
||||
$ cd navicat-patcher
|
||||
$ make release
|
||||
```
|
||||
|
||||
__NOTICE:__
|
||||
|
||||
For Navicat whose version is or is after __12.0.24__, if you want to use your own RSA key, please replace the content in `/navicat-patcher/main.c`
|
||||
|
||||
```cpp
|
||||
const char pubkey[9][72] = {
|
||||
"-----BEGIN PUBLIC KEY-----",
|
||||
"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxqkTcfbKw8ysVygePlcB",
|
||||
"oUAhCF6oniyP13iDtu85ZsHwqw8PnMyTp6n6FnMN9YinleIAy6NFveBu/vshTN8S",
|
||||
"oXbYyy5AqdZ8CQpfvuriO9UNfgV1l7SFdPPpruFAmOw+uzA3GawMsg3QNK/htqJe",
|
||||
"b4xKHFS04xC2AueE2RTmk6tJcL8TEBfRG7DEYOHPjebKl1NQ3ZIu15U97cCPYKO2",
|
||||
"pWHzsb+Fr4Wj0DChLoxlXxaBcJ2ozogaq0tW2t4Aopvt9kRSuSK9HcgxICJM5ct4",
|
||||
"naU91WFGWlw0+0JpiMIl5OnMbpak/5xQre9DL8zM8LjRy14I88txvXvhPEsWaYCO",
|
||||
"1QIDAQAB",
|
||||
"-----END PUBLIC KEY-----"
|
||||
};
|
||||
```
|
||||
|
||||
with your own RSA public key before you build patcher.
|
||||
|
||||
## 5. How to Use
|
||||
1. Build keygen.
|
||||
|
||||
2. Generate RSA-2048 private key and public key. __(Navicat Premium version < 12.0.24 ONLY)__
|
||||
|
||||
```bash
|
||||
$ openssl genrsa -out 2048key.pem 2048
|
||||
$ openssl rsa -in 2048key.pem -pubout -out rpk
|
||||
```
|
||||
|
||||
You will get two file: `2048key.pem` and `rpk`.
|
||||
|
||||
Now you do not need to generate RSA key. I've already prepared these two file:
|
||||
|
||||
* `rpk` file is in `navicat-patcher` folder.
|
||||
|
||||
* `2048key.pem` is in `navicat-keygen` folder.
|
||||
|
||||
3. For Navicat Premium version < 12.0.24:
|
||||
|
||||
* Replace `Navicat Premium.app/Contents/Resources/rpk` file by `rpk` file.
|
||||
|
||||
For Navicat Premium version >= 12.0.24:
|
||||
|
||||
* Backup your `Navicat Premium.app/Contents/MacOS/Navicat Premium` and __all of your saved database connection configurations (with password)__.
|
||||
|
||||
* Delete all of passwords in `Keychain.app` that is saved by Navicat.
|
||||
|
||||
* Run patcher:
|
||||
|
||||
```bash
|
||||
$ cd navicat-patcher
|
||||
$ ./navicat-patcher <your navicat executable file path>
|
||||
```
|
||||
|
||||
Example:
|
||||
|
||||
```bash
|
||||
$ cd navicat-patcher
|
||||
$ ./navicat-patcher /Applications/Navicat\ Premium.app/Contents/MacOS/Navicat\ Premium
|
||||
```
|
||||
|
||||
* __Generate a self-signed code-sign certificate and always trust it. Then use `codesign` to re-sign `Navicat Premium.app`.__
|
||||
|
||||
```bash
|
||||
$ codesign -f -s "Your self-signed code-sign certificate name" <path to Navicat Premium.app>
|
||||
```
|
||||
|
||||
Note: "Your self - signed code - sign certificate name" is the name of Your certificate, not a path.
|
||||
|
||||
Example:
|
||||
|
||||
```bash
|
||||
$ codesign -f -s "foobar" /Applications/Navicat\ Premium.app/Contents/MacOS/Navicat\ Premium
|
||||
```
|
||||
|
||||
4. Then goto `navicat-keygen` folder and in Terminal:
|
||||
|
||||
```bash
|
||||
$ ./navicat-keygen 2048key.pem
|
||||
```
|
||||
|
||||
You will get a __snKey__ and be asked to input your name and organization.
|
||||
Just input and then you will be asked to input the request code. Now DO NOT CLOSE KEYGEN.
|
||||
|
||||
5. Open Navicat Premium, find and click `Registration`. Then input `Registration Key` by __snKey__ that keygen gave. Then click `Activate`.
|
||||
|
||||
6. Generally online activation will failed and Navicat will ask you do `Manual Activation`, just choose it.
|
||||
|
||||
7. Copy your request code and paste it in keygen. Leave empty line to tell keygen that your input ends (in other words, type `Enter` at least twice).
|
||||
|
||||
8. Then you will get activation code which looks like a Base64 string. Just copy it and paste it in Navicat `Manual Activation` window, then click `Activate`. If nothing is wrong, activation should be done successfully.
|
||||
|
||||
9. Finally, restore your database connection configurations if you have.
|
||||
|
||||
393
README.zh-CN.md
393
README.zh-CN.md
@ -1,285 +1,184 @@
|
||||
# Navicat Keygen
|
||||
|
||||
这份repo将会告诉你Navicat是怎么完成离线激活的。
|
||||
这份repo将会告诉你Navicat是怎么完成离线激活的。
|
||||
|
||||
## 1. 关键词解释
|
||||
[注册机是怎么工作的?](HOW_DOES_IT_WORK.zh-CN.md)
|
||||
|
||||
* __Navicat激活公钥__
|
||||
## 1. 如何编译
|
||||
|
||||
这是一个2048位的RSA公钥,Navicat使用这个公钥来完成相关激活信息的加密和解密。
|
||||
* 在编译之前,你应该确保你安装了`OpenSSL`和`rapidjson`。
|
||||
|
||||
如果你有`brew`的话,你可以通过
|
||||
|
||||
```
|
||||
$ brew install openssl
|
||||
$ brew install rapidjson
|
||||
```
|
||||
|
||||
来完成它们的安装。
|
||||
|
||||
这个公钥储存在 __Navicat Premium.app/Contents/Resources/rpk__ 中,你可以用任何一种文本编辑器打开并查看它。这个公钥的具体内容为:
|
||||
* Clone `mac` 分支,并编译keygen和patcher
|
||||
|
||||
> -----BEGIN PUBLIC KEY-----
|
||||
> MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAw1dqF3SkCaAAmMzs889I
|
||||
> qdW9M2dIdh3jG9yPcmLnmJiGpBF4E9VHSMGe8oPAy2kJDmdNt4BcEygvssEfginv
|
||||
> a5t5jm352UAoDosUJkTXGQhpAWMF4fBmBpO3EedG62rOsqMBgmSdAyxCSPBRJIOF
|
||||
> R0QgZFbRnU0frj34fiVmgYiLuZSAmIbs8ZxiHPdp1oD4tUpvsFci4QJtYNjNnGU2
|
||||
> WPH6rvChGl1IRKrxMtqLielsvajUjyrgOC6NmymYMvZNER3htFEtL1eQbCyTfDmt
|
||||
> YyQ1Wt4Ot12lxf0wVIR5mcGN7XCXJRHOFHSf1gzXWabRSvmt1nrl7sW6cjxljuuQ
|
||||
> awIDAQAB
|
||||
> -----END PUBLIC KEY-----
|
||||
|
||||
如果您有相应的私钥并乐意公开的话欢迎联系我,我将非常感谢您的慷慨。
|
||||
|
||||
__注意:__
|
||||
|
||||
从 __Navicat Premium for Mac 12.0.24__ 开始,公钥不再存储在 __Navicat Premium.app/Contents/Resources/rpk__ 中。事实上,公钥放在了Navicat的二进制执行文件 __Navicat Premium.app/Contents/MacOS/Navicat Premium__ 中,你可以通过搜索`"-----BEGIN PUBLIC KEY-----"`来找到它。
|
||||
|
||||
* __请求码__
|
||||
|
||||
这是一个Base64编码的字符串,代表的是长度为256字节的数据。这256字节的数据是 __离线激活请求信息__ 被 __Navicat激活公钥__ 加密的密文。
|
||||
|
||||
* __离线激活请求信息__
|
||||
|
||||
这是一个JSON风格的字符串。它包含了3个Key:`"K"`、`"DI"`和`"P"`,分别代表 __序列号__、__设备识别码__(与你的电脑硬件信息相关)和 __平台__ (其实就是操作系统类型)。
|
||||
|
||||
例如:
|
||||
> {
|
||||
> "K": "xxxxxxxxxxxxxxxx",
|
||||
> "P": "Mac 10.13",
|
||||
> "DI": "xxxxxxxxxxxxxxxxxxxx"
|
||||
> }
|
||||
|
||||
* __激活码__
|
||||
|
||||
这是一个Base64编码的字符串,代表的是长度为256字节的数据。这256字节的数据是 __离线激活回复信息__ 被 __Navicat激活私钥__ 加密的密文,目前我们不知道官方的 __Navicat激活私钥__。
|
||||
|
||||
* __离线激活回复信息__
|
||||
|
||||
和 __离线激活请求信息__ 一样,它也是一个JSON风格的字符串。但是它包含5个Key,分别为`"K"`、`"N"`、`"O"`、`"T"`和`"DI"`.
|
||||
|
||||
`"K"` 和 `"DI"` 的意义与 __离线激活请求信息__ 中的相同,且Value必须与 __离线激活请求信息__ 中的相同。
|
||||
|
||||
`"N"`、`"O"`、`"T"` 分别代表 __注册名__、__组织__、__授权时间__。__注册名__ 和 __组织__ 的值类型为字符串,__授权时间__ 的值类型可以为字符串或整数(感谢@Wizr在issue #10的报告)。
|
||||
|
||||
与Windows版本不同的是,`"T"`是必须的,且代表的时间必须位于当前时间-1 ~ +4天之内。下面是一个 __离线激活回复信息__ 的示例:
|
||||
|
||||
> {
|
||||
> "DI" : "xxxxxxxxxxxxxxxxxxxx",
|
||||
> "T" : "1515770827.925012",
|
||||
> "K" : "xxxxxxxxxxxxxxxx",
|
||||
> "N" : "DoubleLabyrinth",
|
||||
> "O" : "Shadow"
|
||||
> }
|
||||
|
||||
* __序列号__
|
||||
|
||||
这是一个被分为了4个部分的字符串,其中每个部分都是4个字符长。
|
||||
|
||||
__序列号__ 是通过10个字节的数据来生成的。为了表达方便,我用 __data[10]__ 来表示这10个字节。
|
||||
|
||||
1. __data[0]__ 和 __data[1]__ 必须分别为 `0x68` 和 `0x2A`。
|
||||
|
||||
_`Navicat产品类型变化时,这两个值可能会变。目前暂未确认。`_
|
||||
|
||||
2. __data[2]__、__data[3]__ 和 __data[4]__ 可以是任意字节,你想设成什么都行。
|
||||
|
||||
3. __data[5]__ 和 __data[6]__ 与你Navicat的语言有关,值如下:
|
||||
|
||||
| 语言类型 | data[5] | data[6] | 发现者 |
|
||||
|------------|-----------|-----------|-----------------|
|
||||
| English | 0xAC | 0x88 | |
|
||||
| 简体中文 | 0xCE | 0x32 | |
|
||||
| 繁體中文 | 0xAA | 0x99 | |
|
||||
| 日本語 | 0xAD | 0x82 | @dragonflylee |
|
||||
| Polski | 0xBB | 0x55 | @dragonflylee |
|
||||
| Español | 0xAE | 0x10 | @dragonflylee |
|
||||
| Français | 0xFA | 0x20 | @Deltafox79 |
|
||||
| Deutsch | 0xB1 | 0x60 | @dragonflylee |
|
||||
| 한국어 | 0xB5 | 0x60 | @dragonflylee |
|
||||
| Русский | 0xEE | 0x16 | @dragonflylee |
|
||||
| Português | 0xCD | 0x49 | @dragonflylee |
|
||||
|
||||
根据 __Navicat 12 for Mac x64__ 版本残留的符号信息可知这两个字节为 __Product Signature__。
|
||||
|
||||
4. __data[7]__ 指示这是 __commercial license__ 还是 __non-commercial license__。
|
||||
|
||||
对于 __Navicat 12__: `0x65`是 __commercial license__,`0x66`是 __non-commercial license__。
|
||||
对于 __Navicat 11__: `0x15`是 __commercial license__,`0x16`是 __non-commercial license__。
|
||||
|
||||
_`Navicat产品类型变化时,这两个值可能会变。目前暂未确认。`_
|
||||
|
||||
根据 __Navicat 12 for Mac x64__ 版本残留的符号信息可知:__commercial license__ 是 __Enterprise License__, __non-commercial license__ 是 __Educational License__。
|
||||
|
||||
5. __data[8]__ 的高4位代表 __版本号__。低四位未知,但可以用来延长激活期限,可取的值有`0000`和`0001`。
|
||||
|
||||
对于 __Navicat 12__: 高4位必须是`1100`,为`12`的二进制形式。
|
||||
对于 __Navicat 11__: 高4位必须是`1011`,为`11`的二进制形式。
|
||||
|
||||
6. __data[9]__ 目前暂未知,但如果你想要 __not-for-resale license__ 的话可以设成`0xFD`、`0xFC`或`0xFB`。这个值一定不能是`0x00`,其他值随便。
|
||||
|
||||
根据 __Navicat 12 for Mac x64__ 版本残留的符号信息可知:
|
||||
|
||||
* `0xFB`是 __Not-For-Resale-30-days__ license.
|
||||
* `0xFC`是 __Not-For-Resale-90-days__ license.
|
||||
* `0xFD`是 __Not-For-Resale-365-days__ license.
|
||||
* `0xFE`是 __Not-For-Resale__ license.
|
||||
* `0xFF`是 __Site__ license.
|
||||
|
||||
-----------------
|
||||
|
||||
之后Navicat使用 __ECB__ 模式的 __DES__ 算法来加密 __data[10]__ 的后8字节,也就是 __data[2]__ 到 __data[9]__ 的部分。
|
||||
|
||||
相应的DES密钥为:
|
||||
|
||||
```cpp
|
||||
unsigned char DESKey = { 0x64, 0xAD, 0xF3, 0x2F, 0xAE, 0xF2, 0x1A, 0x27 };
|
||||
```
|
||||
|
||||
之后编码 __data[10]__:
|
||||
|
||||
1. 将 __data[10]__ 视作为一个80位长的数据。
|
||||
|
||||
如果 __data[10]__ 以`0x68`和`0x2A`开始的话,80位长的数据应该为`01011000 00101010......`
|
||||
|
||||
2. 将80位长的数据分为16个5位长的块。
|
||||
|
||||
如果 __data[10]__ 以`0x68`和`0x2A`开始的话,16个5位长的块应为`01011`、 `00000`、`10101`、`0....`
|
||||
|
||||
3. 这样每一块的值就会小于32。将它们通过下表编码:
|
||||
|
||||
```cpp
|
||||
// Thanks for discoveries from @Wizr, issue #10
|
||||
char EncodeTable[] = "ABCDEFGH8JKLMN9PQRSTUVWXYZ234567";
|
||||
```
|
||||
|
||||
你就会得到一个16字节的字符串。
|
||||
|
||||
如果 __data[10]__ 以`0x68`和`0x2A`开始的话,编码之后应该以`"N"`、`"A"`、`"V"`打头。
|
||||
|
||||
4. 将16字节的字符串分成4个4字节的小块,然后用`"-"`连接就可以得到 __序列号__。
|
||||
|
||||
## 3. 激活过程
|
||||
|
||||
1. 检查用户输入的 __序列号__ 是否合法。
|
||||
|
||||
2. 在用户点击了`激活`按钮之后,Navicat会先尝试在线激活。如果失败,用户可以选择离线激活。
|
||||
|
||||
3. Navicat会使用用户输入的 __序列号__ 以及从用户电脑收集来的信息生成 __离线激活请求信息__,然后用 __Navicat激活公钥__ 加密,并将密文用Base64编码,最后得到 __请求码__。
|
||||
|
||||
4. 正常流程下,__请求码__ 应该通过可访问Internet的电脑发送给Navicat的官方激活服务器。之后Navicat的官方激活服务器会返回一个合法的 __激活码__。
|
||||
|
||||
但现在我们使用注册机来扮演官方激活服务器的角色,只是Navicat软件里的激活公钥得换成自己的公钥:
|
||||
|
||||
1. 根据 __请求码__, 获得`"DI"`值和`"K"`值。
|
||||
|
||||
2. 用`"K"`值、用户名、组织名和`"DI"`值填写 __离线激活回复信息__。
|
||||
|
||||
3. 用自己的2048位RSA私钥加密 __离线激活回复信息__,你将会得到256字节的密文。
|
||||
|
||||
4. 用Base64编码这256字节的密文,就可以得到 __激活码__。
|
||||
|
||||
5. 在Navicat软件中填入 __激活码__ 即可完成离线激活。
|
||||
|
||||
## 4. 如何编译
|
||||
|
||||
* 在编译之前,你应该确保你安装了OpenSSL。如果你有`brew`的话,你可以通过`brew install openssl`来完成OpenSSL的安装。
|
||||
|
||||
```bash
|
||||
$ cd navicat-keygen
|
||||
$ make release
|
||||
```
|
||||
|
||||
* 如果你的Navicat版本号等于或大于12.0.24,你需要编译patcher。
|
||||
|
||||
```bash
|
||||
$ cd navicat-patcher
|
||||
$ make release
|
||||
```
|
||||
|
||||
注意:
|
||||
|
||||
对于Navicat版本号等于或大于12.0.24的,如果你想要使用自己的RSA密钥,请在编译patcher之前替换掉`navicat-patcher/main.c`里下面的内容
|
||||
|
||||
```cpp
|
||||
const char pubkey[9][72] = {
|
||||
"-----BEGIN PUBLIC KEY-----",
|
||||
"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxqkTcfbKw8ysVygePlcB",
|
||||
"oUAhCF6oniyP13iDtu85ZsHwqw8PnMyTp6n6FnMN9YinleIAy6NFveBu/vshTN8S",
|
||||
"oXbYyy5AqdZ8CQpfvuriO9UNfgV1l7SFdPPpruFAmOw+uzA3GawMsg3QNK/htqJe",
|
||||
"b4xKHFS04xC2AueE2RTmk6tJcL8TEBfRG7DEYOHPjebKl1NQ3ZIu15U97cCPYKO2",
|
||||
"pWHzsb+Fr4Wj0DChLoxlXxaBcJ2ozogaq0tW2t4Aopvt9kRSuSK9HcgxICJM5ct4",
|
||||
"naU91WFGWlw0+0JpiMIl5OnMbpak/5xQre9DL8zM8LjRy14I88txvXvhPEsWaYCO",
|
||||
"1QIDAQAB",
|
||||
"-----END PUBLIC KEY-----"
|
||||
};
|
||||
```bash
|
||||
$ git clone -b mac https://github.com/DoubleLabyrinth/navicat-keygen.git
|
||||
$ cd navicat-keygen
|
||||
$ make all
|
||||
```
|
||||
|
||||
为你自己的RSA公钥。
|
||||
编译完成后你会在 `bin/` 文件夹下看到两个可执行文件:
|
||||
|
||||
## 4. 如何使用这个Keygen
|
||||
```bash
|
||||
$ ls bin/
|
||||
navicat-keygen navicat-patcher
|
||||
```
|
||||
|
||||
1. 编译好keygen。
|
||||
## 2. 如何使用这个Keygen
|
||||
|
||||
2. 生成2048位的RSA密钥对。__(仅限Navicat Premium版本号小于12.0.24)__
|
||||
1. 编译好keygen和patcher。
|
||||
|
||||
```bash
|
||||
$ openssl genrsa -out 2048key.pem 2048
|
||||
$ openssl rsa -in 2048key.pem -pubout -out rpk
|
||||
```
|
||||
2. 备份好 `Navicat Premium.app/Contents/MacOS/Navicat Premium` 以及Navicat中所有已保存的数据库连接(包括密码)。
|
||||
|
||||
你会得到两个文件:`2048key.pem`和`rpk`。
|
||||
3. 移除所有Navicat在 `Keychain.app` (即钥匙链)中保存的连接,如果有的话。
|
||||
|
||||
__现在你们可以不用生成RSA密钥了,我已经准备好了这两个文件:__
|
||||
你可以通过搜索关键词 `navicat` 来找到它们。
|
||||
|
||||
* `rpk`文件在`navicat-patcher`文件夹中。
|
||||
4. 使用`navicat-patcher`替换掉公钥:
|
||||
|
||||
* `2048key.pem`在`navicat-keygen`文件夹中。
|
||||
```
|
||||
Usage:
|
||||
navicat-patcher <navicat executable file> [RSA-2048 PrivateKey(PEM file)]
|
||||
```
|
||||
|
||||
3. 对于Navicat Premium版本 < 12.0.24的:
|
||||
* `<navicat executable file>`: Navicat可执行文件的路径。
|
||||
|
||||
__这个参数必须指定。__
|
||||
|
||||
* 用生成或提供的`rpk`文件替换掉`Navicat Premium.app/Contents/Resources/rpk`。
|
||||
* `[RSA-2048 PrivateKey(PEM file)]`: RSA-2048私钥文件的路径。
|
||||
|
||||
__这个参数是可选的。__ 如果没有指定,`navicat-patcher`将会在当前目录下生成一个新的RSA-2048私钥文件`RegPrivateKey.pem`。
|
||||
|
||||
对于Navicat Premium版本 >= 12.0.24的:
|
||||
__例如:__
|
||||
|
||||
* 备份好`Navicat Premium.app/Contents/MacOS/Navicat Premium`文件,__以及Navicat中所有的数据库连接配置信息(包括密码)__。
|
||||
```
|
||||
$ ./navicat-patcher /Applications/Navicat\ Premium.app/Contents/MacOS/Navicat\ Premium
|
||||
```
|
||||
|
||||
* 删掉`Keychain.app`中所有由Navicat保存的密码。
|
||||
__仅对 Navicat Premium 版本 < 12.0.24 的说明:__
|
||||
|
||||
* 运行patcher:
|
||||
如果你的Navicat版本小于12.0.24,那么`navicat-patcher`将不会修改目标文件。但你必须使用openssl将`RegPrivateKey.pem`转化为`rpk`文件,并用转化得到的文件替换
|
||||
|
||||
```bash
|
||||
$ cd navicat-patcher
|
||||
$ ./navicat-patcher <your navicat executable file path>
|
||||
```
|
||||
```
|
||||
/Applications/Navicat Premium.app/Contents/Resources/rpk
|
||||
```
|
||||
|
||||
例如:
|
||||
如果你不知道怎么转化,这是一份样例:
|
||||
|
||||
```bash
|
||||
$ cd navicat-patcher
|
||||
$ ./navicat-patcher /Applications/Navicat\ Premium.app/Contents/MacOS/Navicat\ Premium
|
||||
```
|
||||
```bash
|
||||
$ openssl rsa -in RegPrivateKey.pem -pubout -out rpk
|
||||
```
|
||||
|
||||
* __生成一个自签名的代码签名证书,并在`Keychain.app`中总是信任它。然后使用`codesign`命令对`Navicat Premium.app`进行重签名。这一步非常重要。__
|
||||
5. __生成一份自签名的代码证书,并总是信任该证书。这一步非常重要。__
|
||||
|
||||
```bash
|
||||
$ codesign -f -s "Your self-signed code-sign certificate name" <path to Navicat Premium.app>
|
||||
```
|
||||
__然后用`codesign`对`Navicat Premium.app`重签名。__
|
||||
|
||||
注意:"Your self-signed code-sign certificate name"是你证书的名字,不是路径。
|
||||
|
||||
例如:
|
||||
```bash
|
||||
$ codesign -f -s "Your self-signed code-sign certificate name" <path to Navicat Premium.app>
|
||||
```
|
||||
|
||||
```bash
|
||||
$ codesign -f -s "foobar" /Applications/Navicat\ Premium.app/Contents/MacOS/Navicat\ Premium
|
||||
```
|
||||
__注意:__
|
||||
|
||||
"Your self-signed code-sign certificate name"是你证书的名字,不是路径。
|
||||
|
||||
4. 接下来,打开`Terminal.app`,并定位到`navicat-keygen`文件夹:
|
||||
__例如:__
|
||||
|
||||
```bash
|
||||
$ ./navicat-keygen 2048key.pem
|
||||
```
|
||||
```bash
|
||||
$ codesign -f -s "foobar" /Applications/Navicat\ Premium.app/
|
||||
```
|
||||
|
||||
接下来你会被要求输入Navicat的语言版本,然后得到一个 __序列号__,同时keygen会要求你输入用户名和组织名。
|
||||
直接填写,之后你会被要求填写你得到的 __请求码__。注意此时 __不要关闭Terminal__.
|
||||
6. 接下来使用`navicat-keygen`来生成 __序列号__ 和 __激活码__。
|
||||
|
||||
5. 打开Navicat Premium。找到`注册`按钮并点击,在弹出的窗口中填入keygen给你的 __序列号__。然后点击`激活`按钮。
|
||||
```
|
||||
Usage:
|
||||
navicat-keygen <RSA-2048 PrivateKey(PEM file)>
|
||||
```
|
||||
|
||||
6. 一般来说在线激活肯定会失败,这时候Navicat会询问你是否`手动激活`,直接选吧。
|
||||
* `<RSA-2048 PrivateKey(PEM file)>`: RSA-2048私钥文件的路径。
|
||||
|
||||
__这个参数必须指定。__
|
||||
|
||||
7. 在`手动激活`窗口你会得到一个请求码,复制它并把它粘贴到keygen里。最后别忘了连按至少两下回车结束输入。
|
||||
__例如:__
|
||||
|
||||
8. 如果不出意外,你会得到一个看似用Base64编码的 __激活码__。直接复制它,并把它粘贴到Navicat的`手动激活`窗口,最后点`激活`按钮。如果没什么意外的话应该能成功激活。
|
||||
```bash
|
||||
./navicat-keygen ./RegPrivateKey.pem
|
||||
```
|
||||
|
||||
9. 最后,如果你备份了数据库连接配置信息,那么恢复它把。
|
||||
你会被要求选择Navicat的语言以及输入主版本号。之后会随机生成一个 __序列号__。
|
||||
|
||||
```
|
||||
Which is your Navicat Premium language?
|
||||
0. English
|
||||
1. Simplified Chinese
|
||||
2. Traditional Chinese
|
||||
3. Japanese
|
||||
4. Polish
|
||||
5. Spanish
|
||||
6. French
|
||||
7. German
|
||||
8. Korean
|
||||
9. Russian
|
||||
10. Portuguese
|
||||
|
||||
(Input index)> 1
|
||||
(Input major version number, range: 0 ~ 15, default: 12)> 12
|
||||
|
||||
Serial number:
|
||||
NAVK-MWQR-LNXV-886V
|
||||
|
||||
Your name:
|
||||
```
|
||||
|
||||
你可以使用这个 __序列号__ 暂时激活Navicat。
|
||||
|
||||
接下来你会被要求输入`用户名`和`组织名`;请随便填写,但不要太长。
|
||||
|
||||
```bash
|
||||
Your name: DoubleLabyrinth
|
||||
Your organization: DoubleLabyrinth
|
||||
Input request code (in Base64), input empty line to end:
|
||||
```
|
||||
|
||||
之后你会被要求填入请求码。注意 __不要关闭注册机__.
|
||||
|
||||
7. __断开网络__ 并打开Navicat。找到`注册`窗口,填入注册机给你的序列号。然后点击`激活`按钮。
|
||||
|
||||
8. 一般来说在线激活肯定会失败,这时候Navicat会询问你是否`手动激活`,直接选吧。
|
||||
|
||||
9. 在`手动激活`窗口你会得到一个请求码,复制它并把它粘贴到keygen里。最后别忘了连按至少两下回车结束输入。
|
||||
|
||||
```bash
|
||||
Your name: DoubleLabyrinth
|
||||
Your organization: DoubleLabyrinth
|
||||
|
||||
Input request code (in Base64), input empty line to end:
|
||||
q/cv0bkTrG1YDkS+fajFdi85bwNVBD/lc5jBYJPOSS5bfl4DdtnfXo+RRxdMjJtEcYQnvLPi2LF0
|
||||
OB464brX9dqU29/O+A3qstSyhBq5//iezxfu2Maqca4y0rVtZgQSpEnZ0lBNlqKXv7CuTUYCS1pm
|
||||
tEPgwJysQTMUZf7tu5MR0cQ+hY/AlyQ9iKrQAMhHklqZslaisi8VsnoIqH56vfTyyUwUQXrFNc41
|
||||
qG5zZNsXu/NI79JOo7qTvcFHQT/k5cTadbKTxY+9c5eh+nF3JR7zEa2BDDfdQRLNvy4DTSyxdYXd
|
||||
sAk/YPU+JdWI+8ELaa0SuAuNzr5fEkD6NDSG2A==
|
||||
|
||||
Request Info:
|
||||
{"K":"NAVADHCNP2OIDV46", "DI":"Y2eJk9vrvfGudPG7Mbdn", "P":"MAC"}
|
||||
|
||||
Response Info:
|
||||
{"K":"NAVADHCNP2OIDV46","DI":"Y2eJk9vrvfGudPG7Mbdn","N":"DoubleLabyrinth","O":"DoubleLabyrinth","T":1537630251}
|
||||
|
||||
License:
|
||||
oyoMYr9cfVGXeT7F1dqBwHsB/vvWj6SUL6aR+Kzb0lm5IyEj1CgovuSq+qMzFfx+
|
||||
oHMFaGKFg6viOY2hfJcrO2Vdq0hXZS/B/Ie3jBS2Ov37v8e3ufVajaH+wLkmEpLd
|
||||
xppCVLkDQjIHYR2IPz5s/L/RuWqDpEY4TPmGFF6q+xQMnqQA3vXPyG+JYMARXLru
|
||||
Y1gCDLN30v3DpyOeqKmFjUqiHK5h8s0NYiH2OpMyaCpi12JsF23miP89ldQp3+SJ
|
||||
8moo0cNGy7sFp2gX9ol2zVoo7qxfYlLl03f7CALJ6im0sx4yBsmlzFDdvpQUbXk8
|
||||
YZ5rT4LML2Fx6Wgnnklb5g==
|
||||
```
|
||||
|
||||
10. 如果不出意外,你会得到一个看似用Base64编码的激活码。直接复制它,并把它粘贴到Navicat的`手动激活`窗口,最后点`激活`按钮。如果没什么意外的话应该能成功激活。
|
||||
|
||||
28
navicat-activate-server/cert-crt.pem
Normal file
28
navicat-activate-server/cert-crt.pem
Normal file
@ -0,0 +1,28 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIEujCCAqICCQD77KLUs6JamDANBgkqhkiG9w0BAQsFADAfMR0wGwYDVQQDDBRh
|
||||
Y3RpdmF0ZS5uYXZpY2F0LmNvbTAeFw0xODEyMTcwODEzMDNaFw0yODEyMTQwODEz
|
||||
MDNaMB8xHTAbBgNVBAMMFGFjdGl2YXRlLm5hdmljYXQuY29tMIICIjANBgkqhkiG
|
||||
9w0BAQEFAAOCAg8AMIICCgKCAgEAvGZxEqPRuMtzCS3G+PzLsmxmBTE4VNiSEeDp
|
||||
MZcZOQo7egwCkZOrh3u32tJwFp0p++Gv3QiTQ709vT3h0d54uLm4oZ3Ov/X7xFQ7
|
||||
Jy2qz59sil4hZTnjD9UTb6zGP2ww7LYLAkngX8+heECiJtPGckBMwfM91azR478w
|
||||
BZS0Kwv2b+jlN9htzXfn00aRXhGc0x5LMTibW7ufwYjPpugmdq5JsK2pS8l75cbJ
|
||||
qxZgC38l+WKrvyfTcoqzkw0fcaZIabmVsRWy2qu6O4UQ7LeII59Cl397HmjyUvis
|
||||
GWdhpdQvMYpOczWnARm2+PBxMkK3nOEAvAa5LxHEf1mhMdD5t50i0Rw8rGwsfIAq
|
||||
btAUDPKYhLlC7Hr+3Bijrzrq8Q9L+znlKgeCC/9eJqv9KGTLBjuB0y5MFD48hCob
|
||||
Q18sZdaVXIDZD7xil/GtFayho7CKdNL/m2O/1Ek03OJgqCSXPP243nKnSKWhOfxT
|
||||
vIsOaPBThdYGLp+jnbHPIRSb5TcXiTYh7dtqg9QEEzclPMXPTZEwA+TVKbQekzX3
|
||||
Mv+CsJ9xzHxKX0/tY7IH3++MeEeOY3s2sNQjayS+3YdsCEYBtYhFcd+SoUyk2BXu
|
||||
TpnLIkIQA9j6W+mpAVGvEF4ZGB55Mvic20gByVf4RzrT8Ws9gguWJmffNyD7qPCI
|
||||
d00r6hECAwEAATANBgkqhkiG9w0BAQsFAAOCAgEApX8giNz2VERIxpcnVphtOfqA
|
||||
37HJcDvmDxlnpSSUvVrCFTM+YD/uAuecE1GmQIxuwRbSb63s/vRetboHZ7A+PSkr
|
||||
BwR6xkIlJwOVwaiZXs10HmP5Nz8aele+V83fCAWY/mtxn6EF0Pi7sxTCLg5PSdR+
|
||||
P7QRr6/vZPoyFd8TcU5c3VFMqa/Z3Sdhmx6XWYs2COOFTfOo4k8sXYIwGVyWOt4/
|
||||
XHqpouhpeF+/oxlaOo/fnJY2IcGsao3eyWevZMCrPsf/MQqeHJhsTG6yLfU7pldS
|
||||
s25LbXNzF8mjfQQLWRCKCGgUqRUGjJBsIyg8X9C9e0MKL+/W7KDlcJA8VyJIjFmK
|
||||
k1aEyKfLzNTkAZtzGutYL4x0lAJBxfYWbm9WVtrU2QMkqkkjru1buPRyw0vu3QgO
|
||||
hizSWkU1JqERTADsRN/u8VTddmdLy0Bs44q/btnBO7EU86+lg9O1mPGpD4KmVB2e
|
||||
2XWzfQ++059sA5vT2LtaQuvFG4nulnQtNeboVyzNam+zff7vrHXrszFk/0YFTw0n
|
||||
bS4YHVlpYDIH7KY47L42sj/48LjmDgQ0AExFkkzPCFjaPNxIm5vsg9nm6H3iZOjx
|
||||
9zoMl9EHIwfJqNxw0fsdATBJbW/MsWJ+DHiiyQVPisWC11BmUqKWh/05bLHBhKEB
|
||||
7wKbi9kxjRflM5+An+g=
|
||||
-----END CERTIFICATE-----
|
||||
52
navicat-activate-server/cert-key.pem
Normal file
52
navicat-activate-server/cert-key.pem
Normal file
@ -0,0 +1,52 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIIJQwIBADANBgkqhkiG9w0BAQEFAASCCS0wggkpAgEAAoICAQC8ZnESo9G4y3MJ
|
||||
Lcb4/MuybGYFMThU2JIR4Okxlxk5Cjt6DAKRk6uHe7fa0nAWnSn74a/dCJNDvT29
|
||||
PeHR3ni4ubihnc6/9fvEVDsnLarPn2yKXiFlOeMP1RNvrMY/bDDstgsCSeBfz6F4
|
||||
QKIm08ZyQEzB8z3VrNHjvzAFlLQrC/Zv6OU32G3Nd+fTRpFeEZzTHksxOJtbu5/B
|
||||
iM+m6CZ2rkmwralLyXvlxsmrFmALfyX5Yqu/J9NyirOTDR9xpkhpuZWxFbLaq7o7
|
||||
hRDst4gjn0KXf3seaPJS+KwZZ2Gl1C8xik5zNacBGbb48HEyQrec4QC8BrkvEcR/
|
||||
WaEx0Pm3nSLRHDysbCx8gCpu0BQM8piEuULsev7cGKOvOurxD0v7OeUqB4IL/14m
|
||||
q/0oZMsGO4HTLkwUPjyEKhtDXyxl1pVcgNkPvGKX8a0VrKGjsIp00v+bY7/USTTc
|
||||
4mCoJJc8/bjecqdIpaE5/FO8iw5o8FOF1gYun6Odsc8hFJvlNxeJNiHt22qD1AQT
|
||||
NyU8xc9NkTAD5NUptB6TNfcy/4Kwn3HMfEpfT+1jsgff74x4R45jezaw1CNrJL7d
|
||||
h2wIRgG1iEVx35KhTKTYFe5OmcsiQhAD2Ppb6akBUa8QXhkYHnky+JzbSAHJV/hH
|
||||
OtPxaz2CC5YmZ983IPuo8Ih3TSvqEQIDAQABAoICAQCZ67v/tZV3O8itPBgufiJR
|
||||
kFw9a7wRHashLjZ2zHLP9jsneD50/0eJdht8jKcE4rxVTEqo9AOUuTyCqtce5nqM
|
||||
uTdN2yb6EBb0jLiTRq0h2acM2ODB2exXmAa8G1UQpezGx+fwo7jLrk/Gdk/EFwsv
|
||||
drb/UiI3u2zze9TZCme0L57USVtUJ991mbvuEd5cE1mj5kEaYpwS8xQPQx4bx9j8
|
||||
52HPFeKVx3QRAzrqK2qmmefFesbEct4+23Dg+DyzVl4c+oGA3zBzlQ+e5xUvwLZa
|
||||
HBiEu6Mz8hvgi8fXLW5K1DMGb2+ukqvxqzeg5JgmrvliEzXNaFTNTt/SB8E5ePk6
|
||||
kQ6K4+2rvBFN6Bho/3j649ZbYQiEQ7LL2nm6kLT2QkFcNGuj+PPv97XbR/sUKnG/
|
||||
0FAKisgvNEaVGz6GV1kxqZ0SZkohkSknGODbh03KxKEykUhx05DkDJ9z4vhguOaF
|
||||
tOu+77FmxRpWCyUEzMvXXHuNyryW1k6WGZU7N142gB1HxnMB1OsP9nqTELmHSuh8
|
||||
0z8zgMH/CNVPD6JGLQWTuDjI99oFwuI4eKFYcKqrvquO5KBPOfu65l0siE8jnGgI
|
||||
f9JwkDAMqB2Ho51m241H9XPH1704Y3a2Ew7NnWbZvCKgtmbGw8doIbTur+gKCI0+
|
||||
LqaFyLu35g6Z1jVfijeW7QKCAQEA7bEjK6MjrgYsucx1nnzQ+44SNSGnsTnFLK/P
|
||||
WXYA5yxIUT9eabJf/VwtN0NGoTMmSN8UHFtvsyvH01TQpToUy6jU82O0bEtpDYvE
|
||||
qKvp1w224+c5TCOvzq4kyBRtAito5BFwwLWBCMZ9k4+GADw4MDNgr1Hyt94a97JD
|
||||
uPLbhNOJcapxNV+9nAEN61d3jhvR6YMVJ0npnnqwub/rNv7VJhyBz35p8i1nTkve
|
||||
6YHe8uMvN/TXgryaoP1igrvO9T5xrVlv7nQnzNBjcRJqr99sYMdZ2AOTEQnqiXnU
|
||||
kUME15CtDgdEarlkpwNAuQfq/VxOpMcEkZw4zhvAzXAAwR89swKCAQEAyulbpy+7
|
||||
hTl14RBV9Y2kM25XCWRlN9GNLvfN8vfC9rix98pa+SWg9wawUZPnckZjM3vgOUpm
|
||||
fZVZocBCK0cT5Fm3wUPX3dmNXhlPj6t+HukXto61N9sQoliz0RYzOGFOlT2+1bYF
|
||||
pEKQW5MnRGe7PIwYBUbdsm9nbO7XflEz+F5h3pZmnMWXQVZdMTyQrMKnsifh5y+T
|
||||
ls2Bboy2PLpquEQH2FWbPHaaLzMLy6wD5IAR+c/rFWIk0jl8BlvMOs3sPYPxXgIO
|
||||
TdAWOCOSlymHbB3tot1lUAWfxjNO7vx0EB5mAG1kl5dU2IVzCU1o2PQUCFph9hQF
|
||||
FxfFWF3h8Iy/KwKCAQBN2olkj6juJUOdD+Uupj9lReGc2Ystt3hn7KYD7WzaS1zc
|
||||
j98Cy9Q5DmoZAcYuoqxVjgvuRdzOWPa4t70ngWCyXHhzyCKengyozTD69mQ/dlSD
|
||||
TYcs8ztwfQW7K0WRQlq26Sd/V3QORcV4AbiodxEIaKuwgSz5IJzAqPkZqSJR3V/V
|
||||
hThTfSqD4KyIezvDpkUZMUBKgCEvMYyVKtGGT+3M7+Vs60EUe96sfJlk7o7yC4u5
|
||||
zk6Qmeoj9RrBK3bcSTm+x96rwgwPP+pybM/ZD1tWT7WT1YfkOtVBdlAzzDnXyaGF
|
||||
SMgWFOds9NnMQVWs6wEvY6iRcRQCHoEGSTtWBj+VAoIBAH/9wMVtk3BOtW94+W/B
|
||||
n5CdiiMvg7U45kB5KRyYcg9ko0W1C7tao/UN8AwVKonnok1oVyBsMMgIfBhOiK55
|
||||
0C9a9FDhNXoH72cqugfa2dwvlbievep0sUJmh05dHYPzxTjYUNbHUTmyjxJkeQFq
|
||||
GS3xBHnXoKIcKXYKFj3khM6m4j8gSaub63PYQUJ8fJms01DKXXGRjhwylvEfqxgV
|
||||
UpnWt8ga/6ec/FbHcSpQ38ezjSMxpoy/cpB0mCn+n418NQb8gjSVQWaN2mYg8ieN
|
||||
l5F4M1xnIK/gTE8eMC0Ja0B7nEWquyfv2iIV02FLxdYL59L5CA/LwwY1BVNfLNSq
|
||||
Qy8CggEBAMn9Dco7uBUgE4M9EQzic9oWlrsxno+XklhRtMkWhQfdbFJbZIRs7rXO
|
||||
SSx6en7IBp7L/Xqb1TS+u32sdnjUqnqCXJUlkgn5IFwEoLCFlHFvgE41hnmhlRr/
|
||||
xgXsPYozKDq65ZdZE4wuWXTMqbCZTxI1bF2MZ0Jvi5GePLQaVhpbQFsNFskjYYcg
|
||||
2VGaFWlDQtc2luTuvHiHsMwXx9kC4sWE6k01Nr60c8oRbVEM+UeJ1bspuk2TtLU3
|
||||
cmGBdOGiqYxvbKU8+ROeimbCMwtf5lA0zYrXTW8TnZBibcv+FhFrqtJz6w54h1NR
|
||||
6VJ+97E5OdzZqH2XTdf9Fi7c/WjxYCg=
|
||||
-----END PRIVATE KEY-----
|
||||
97
navicat-activate-server/server.py
Executable file
97
navicat-activate-server/server.py
Executable file
@ -0,0 +1,97 @@
|
||||
#!/usr/bin/env python3
|
||||
from http.server import HTTPServer, BaseHTTPRequestHandler
|
||||
import sys, ssl, base64, json, time, gzip
|
||||
|
||||
from OpenSSL import crypto
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
from cryptography.hazmat.primitives import serialization
|
||||
|
||||
openssl_lib = crypto._lib
|
||||
|
||||
def RSAPublicEncrypt(key, msg: bytes):
|
||||
enc = bytes((key.key_size + 7) // 8)
|
||||
length = openssl_lib.RSA_public_encrypt(len(msg), msg, enc, key._rsa_cdata, openssl_lib.RSA_PKCS1_PADDING)
|
||||
if (length == -1):
|
||||
raise ValueError('Public encrypt failure.')
|
||||
return enc[:length]
|
||||
|
||||
def RSAPublicDecrypt(key, enc: bytes):
|
||||
msg = bytes((key.key_size + 7) // 8)
|
||||
length = openssl_lib.RSA_public_decrypt(len(enc), enc, msg, key._rsa_cdata, openssl_lib.RSA_PKCS1_PADDING)
|
||||
if (length == -1):
|
||||
raise ValueError('Public decrypt failure.')
|
||||
return msg[:length]
|
||||
|
||||
def RSAPrivateEncrypt(key, msg: bytes):
|
||||
enc = bytes((key.key_size + 7) // 8)
|
||||
length = openssl_lib.RSA_private_encrypt(len(msg), msg, enc, key._rsa_cdata, openssl_lib.RSA_PKCS1_PADDING)
|
||||
if (length == -1):
|
||||
raise ValueError('Private encrypt failure.')
|
||||
return enc[:length]
|
||||
|
||||
def RSAPrivateDecrypt(key, enc: bytes):
|
||||
msg = bytes((key.key_size + 7) // 8)
|
||||
length = openssl_lib.RSA_private_decrypt(len(enc), enc, msg, key._rsa_cdata, openssl_lib.RSA_PKCS1_PADDING)
|
||||
if (length == -1):
|
||||
raise ValueError('Private decrypt failure.')
|
||||
return msg[:length]
|
||||
|
||||
def LoadKey(path: str):
|
||||
with open(path, 'rb') as f:
|
||||
key = serialization.load_pem_private_key(f.read(), None, default_backend())
|
||||
return key
|
||||
|
||||
class NavicatActivateServerHandler(BaseHTTPRequestHandler):
|
||||
|
||||
def do_GET(self):
|
||||
self.send_response(200)
|
||||
self.end_headers()
|
||||
self.wfile.write('This is an activation server for Navicat.'.encode())
|
||||
|
||||
def do_POST(self):
|
||||
print('-' * 32)
|
||||
print(self.headers)
|
||||
body = self.rfile.read(int(self.headers['Content-Length'], 0))
|
||||
print('Receive a POST request from %s:%d -->' % self.client_address)
|
||||
print(body)
|
||||
if (body.startswith(b'input:')):
|
||||
enc = body[6:]
|
||||
try:
|
||||
snKey = RSAPrivateDecrypt(prikey, enc).decode()
|
||||
print('snKey = %s' % snKey)
|
||||
license = {}
|
||||
license['K'] = snKey
|
||||
license['DI'] = 'fjavsmSJmOssbuo4Ns9H'
|
||||
license['N'] = 'DoubleLabyrinth'
|
||||
license['O'] = 'DoubleLabyrinth'
|
||||
license['T'] = 1545208026
|
||||
license['M'] = 'MMMMMMMM'
|
||||
license['FA'] = 'FAFAFAFA'
|
||||
license = json.dumps(license).encode()
|
||||
print(license)
|
||||
data = gzip.compress(RSAPrivateEncrypt(prikey, license))
|
||||
self.send_response(200)
|
||||
self.send_header('Vary', 'Accept-Encoding')
|
||||
self.send_header('Content-Encoding', 'gzip')
|
||||
self.send_header('Content-Type', 'text/html; charset=UTF-8')
|
||||
self.send_header('Content-Length', len(data))
|
||||
self.end_headers()
|
||||
self.wfile.write(data)
|
||||
print('sent...')
|
||||
print()
|
||||
except:
|
||||
self.send_response(404)
|
||||
self.end_headers()
|
||||
pass
|
||||
|
||||
IP = '0.0.0.0'
|
||||
Port = 443
|
||||
prikey = LoadKey(sys.argv[1])
|
||||
|
||||
httpd = HTTPServer((IP, Port), NavicatActivateServerHandler)
|
||||
httpd.socket = ssl.wrap_socket(httpd.socket,
|
||||
keyfile = 'cert-key.pem',
|
||||
certfile = 'cert-crt.pem',
|
||||
server_side = True)
|
||||
httpd.serve_forever()
|
||||
|
||||
@ -1,27 +0,0 @@
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEpQIBAAKCAQEAxqkTcfbKw8ysVygePlcBoUAhCF6oniyP13iDtu85ZsHwqw8P
|
||||
nMyTp6n6FnMN9YinleIAy6NFveBu/vshTN8SoXbYyy5AqdZ8CQpfvuriO9UNfgV1
|
||||
l7SFdPPpruFAmOw+uzA3GawMsg3QNK/htqJeb4xKHFS04xC2AueE2RTmk6tJcL8T
|
||||
EBfRG7DEYOHPjebKl1NQ3ZIu15U97cCPYKO2pWHzsb+Fr4Wj0DChLoxlXxaBcJ2o
|
||||
zogaq0tW2t4Aopvt9kRSuSK9HcgxICJM5ct4naU91WFGWlw0+0JpiMIl5OnMbpak
|
||||
/5xQre9DL8zM8LjRy14I88txvXvhPEsWaYCO1QIDAQABAoIBAHsTYKKoPPKVKOhO
|
||||
SH5itPXLnytqSZjFDtB1L1T0XGGXIZ04lXmVHJ0xJ2klGq7VXM302H1Qx/JcyydZ
|
||||
OkY/pXE3ChTGsPUHloybSrojFsnuso8ynqnAAzZzroDTuIuFQVADDP/woWTmPemG
|
||||
VZYqfcsp5PgsWmyae8jM2ncLBdbRnSCtQ0FqMZPxHnazxoxh43fIisdZiHVretDR
|
||||
cHR0MKig8ggetpRCPv8prHvYjllmnp+vFPNbI8DD6LUho0gtsKnGG+4k5NCUArrH
|
||||
gfNK7ov/wsBe991XgC+tG/Xa8GgQJjSXr6qxwI6jPJ9d8EDHi31pqOWggJz0iPUL
|
||||
U/5uNoECgYEA9U+ykujylIn7WPDcoNuXXk+xb5J+7p0En1dnFPDWEqM21rHfYHpo
|
||||
X8ikfW63PSYp/472BLhum3TnjbgBQy8Uu80uGXWKCTCmnguMZVAw0yJn4bwCKmUM
|
||||
hI8eaB231O3pz0AeoKfjj3Y25c6EF+P0wixO95qNj3gZs8fRE1o2BoUCgYEAz1EE
|
||||
AHHqNus/2iGVS/OD0cGcZBzBgz790o1YHvaAZWTB8J4Q55Z8NnbxHKPk+1FRik2c
|
||||
3iqNhDtfee/9j9QkNookjP6JqliKLj6AHAzqmKAviMN/akQHO9vYrKVrgGXts9X4
|
||||
k5Rn3Wyb1R9xSg9timalZ6HXsG0fqMKlQimroBECgYEAvfCPnCCyc0DDVQJqUkK0
|
||||
2VlrUbBkh+0b/yR1tEkl+BhYBbSD+PfOiRuaAWUQjFBYhH/1DYKgYc4K3Dh1Acga
|
||||
ja842o2f66231PesJWYJ5/Dj7mzcYGcNYjZOnN8lz8DHIKnNDSfxkss9hoJ41Oa0
|
||||
RQR5wE3y/ftIK2O0Bv/6hOECgYEAhzh2fbvPYobKnQIumN16a0P3N60x4uMhN0YN
|
||||
5hQt5vRzMvhGWmdjGTs3RND5t5pwrt60pIvoxgjb23LXVgqunAw6juQu8rbRWhxV
|
||||
/vXFxksk9fVdk/hPJ5Hbn7A9j//rrNKxUcEIhfojIj1h7UZm0lDlLdT4FBD5B1/9
|
||||
3kvDr6ECgYEAu/OS+6ApNTH/LhSeeqw9jMtxJwu560WOzSsDk8LSlSDCZHwREwVU
|
||||
M+uBWShYTDlPn6Bpe15dNHghvxFnZi12pS57l5AgD4J8AW4FEveDuIjj7BOexNFa
|
||||
nB099pHr6MUM5s8zmw+9Z4Rc7gk8bwTyFAwMFf8ZYSyl4WIHQfTbhx0=
|
||||
-----END RSA PRIVATE KEY-----
|
||||
41
navicat-keygen/DESCipher.hpp
Normal file
41
navicat-keygen/DESCipher.hpp
Normal file
@ -0,0 +1,41 @@
|
||||
#pragma once
|
||||
#include <openssl/crypto.h>
|
||||
#include <openssl/des.h>
|
||||
#include <memory.h>
|
||||
|
||||
class DESCipher {
|
||||
private:
|
||||
DES_cblock _Key;
|
||||
DES_key_schedule _Schedule;
|
||||
public:
|
||||
|
||||
DESCipher() noexcept : _Key{}, _Schedule{} {}
|
||||
|
||||
void SetKey(const void* pKey) noexcept {
|
||||
memcpy(&_Key, pKey, sizeof(_Key));
|
||||
DES_set_odd_parity(&_Key);
|
||||
DES_set_key(&_Key, &_Schedule);
|
||||
}
|
||||
|
||||
void Clear() noexcept {
|
||||
OPENSSL_cleanse(&_Key, sizeof(_Key));
|
||||
OPENSSL_cleanse(&_Schedule, sizeof(_Schedule));
|
||||
}
|
||||
|
||||
void EncryptBlock(void* pBuffer) noexcept {
|
||||
DES_cblock block;
|
||||
DES_ecb_encrypt(reinterpret_cast<const_DES_cblock*>(pBuffer), &block, &_Schedule, DES_ENCRYPT);
|
||||
memcpy(pBuffer, &block, sizeof(block));
|
||||
}
|
||||
|
||||
void DecryptBlock(void* pBuffer) noexcept {
|
||||
DES_cblock block;
|
||||
DES_ecb_encrypt(reinterpret_cast<const_DES_cblock*>(pBuffer), &block, &_Schedule, DES_DECRYPT);
|
||||
memcpy(pBuffer, &block, sizeof(block));
|
||||
}
|
||||
|
||||
~DESCipher() noexcept {
|
||||
Clear();
|
||||
}
|
||||
};
|
||||
|
||||
82
navicat-keygen/Helper.cpp
Normal file
82
navicat-keygen/Helper.cpp
Normal file
@ -0,0 +1,82 @@
|
||||
#include "Helper.hpp"
|
||||
|
||||
namespace Helper {
|
||||
|
||||
//
|
||||
// https://stackoverflow.com/questions/5288076/base64-encoding-and-decoding-with-openssl
|
||||
// Thanks Omnifarious
|
||||
// OpenSSL's base64 API is too dreadful to use
|
||||
//
|
||||
static const char b64_table[65] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||
|
||||
static const char b64_reverse_table[128] = {
|
||||
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
|
||||
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
|
||||
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 62, 64, 64, 64, 63,
|
||||
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 64, 64, 64, 64, 64, 64,
|
||||
64, 0, 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, 64, 64, 64, 64, 64,
|
||||
64, 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, 64, 64, 64, 64, 64
|
||||
};
|
||||
|
||||
std::string base64_encode(const std::vector<uint8_t>& bindata) {
|
||||
using bytes = std::vector<uint8_t>;
|
||||
|
||||
if (bindata.size() > (std::numeric_limits<bytes::size_type>::max() / 4u) * 3u) {
|
||||
throw ::std::length_error("Converting too large a string to base64.");
|
||||
}
|
||||
|
||||
const std::size_t binlen = bindata.size();
|
||||
// Use = signs so the end is properly padded.
|
||||
std::string retval((((binlen + 2) / 3) * 4), '=');
|
||||
std::size_t outpos = 0;
|
||||
int bits_collected = 0;
|
||||
unsigned int accumulator = 0;
|
||||
const bytes::const_iterator binend = bindata.end();
|
||||
|
||||
for (auto i = bindata.begin(); i != binend; ++i) {
|
||||
accumulator = (accumulator << 8) | (*i & 0xffu);
|
||||
bits_collected += 8;
|
||||
while (bits_collected >= 6) {
|
||||
bits_collected -= 6;
|
||||
retval[outpos++] = b64_table[(accumulator >> bits_collected) & 0x3fu];
|
||||
}
|
||||
}
|
||||
if (bits_collected > 0) { // Any trailing bits that are missing.
|
||||
assert(bits_collected < 6);
|
||||
accumulator <<= 6 - bits_collected;
|
||||
retval[outpos++] = b64_table[accumulator & 0x3fu];
|
||||
}
|
||||
assert(outpos >= (retval.size() - 2));
|
||||
assert(outpos <= retval.size());
|
||||
return retval;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> base64_decode(const std::string& ascdata) {
|
||||
std::vector<uint8_t> retval;
|
||||
const std::string::const_iterator last = ascdata.end();
|
||||
int bits_collected = 0;
|
||||
unsigned int accumulator = 0;
|
||||
|
||||
for (std::string::const_iterator i = ascdata.begin(); i != last; ++i) {
|
||||
const int c = *i;
|
||||
if (::std::isspace(c) || c == '=') {
|
||||
// Skip whitespace and padding. Be liberal in what you accept.
|
||||
continue;
|
||||
}
|
||||
if ((c > 127) || (c < 0) || (b64_reverse_table[c] > 63)) {
|
||||
throw ::std::invalid_argument("This contains characters not legal in a base64 encoded string.");
|
||||
}
|
||||
accumulator = (accumulator << 6) | b64_reverse_table[c];
|
||||
bits_collected += 6;
|
||||
if (bits_collected >= 8) {
|
||||
bits_collected -= 8;
|
||||
retval.emplace_back(static_cast<uint8_t>((accumulator >> bits_collected) & 0xffu));
|
||||
}
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
45
navicat-keygen/Helper.hpp
Normal file
45
navicat-keygen/Helper.hpp
Normal file
@ -0,0 +1,45 @@
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace Helper {
|
||||
template<int min_num, int max_num>
|
||||
bool ReadNumber(int& num, const char* msg, const char* err_msg) {
|
||||
int temp;
|
||||
std::string input;
|
||||
while (true) {
|
||||
std::cout << msg;
|
||||
if (!std::getline(std::cin, input))
|
||||
return false;
|
||||
|
||||
try {
|
||||
temp = std::stoi(input, nullptr, 0);
|
||||
if (min_num <= temp && temp <= max_num) {
|
||||
num = temp;
|
||||
return true;
|
||||
} else {
|
||||
throw std::invalid_argument("Invalid number");
|
||||
}
|
||||
} catch (...) {
|
||||
std::cout << err_msg << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<typename _Type>
|
||||
struct ResourceGuard {
|
||||
_Type* ptr;
|
||||
|
||||
explicit ResourceGuard(_Type* p) noexcept : ptr(p) {}
|
||||
|
||||
~ResourceGuard() {
|
||||
if (ptr) {
|
||||
delete ptr;
|
||||
ptr = nullptr;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
std::string base64_encode(const std::vector<uint8_t>& bindata);
|
||||
std::vector<uint8_t> base64_decode(const std::string& ascdata);
|
||||
}
|
||||
@ -1,9 +0,0 @@
|
||||
|
||||
release : main.cpp
|
||||
g++ -std=c++11 main.cpp -lcrypto -I/usr/local/opt/openssl/include -L/usr/local/opt/openssl/lib -o navicat-keygen
|
||||
|
||||
debug : main.cpp
|
||||
g++ -std=c++11 -D_DEBUG main.cpp -lcrypto -I/usr/local/opt/openssl/include -L/usr/local/opt/openssl/lib -o navicat-keygen
|
||||
|
||||
clean:
|
||||
rm navicat-keygen
|
||||
195
navicat-keygen/NavicatKeygen.hpp
Normal file
195
navicat-keygen/NavicatKeygen.hpp
Normal file
@ -0,0 +1,195 @@
|
||||
#pragma once
|
||||
#include <string>
|
||||
#include <random>
|
||||
#include "DESCipher.hpp"
|
||||
|
||||
class NavicatKeygen {
|
||||
public:
|
||||
enum class Language {
|
||||
English,
|
||||
SimplifiedChinese,
|
||||
TraditionalChinese,
|
||||
Japanese,
|
||||
Polish,
|
||||
Spanish,
|
||||
French,
|
||||
German,
|
||||
Korean,
|
||||
Russian,
|
||||
Portuguese
|
||||
};
|
||||
|
||||
enum class Product {
|
||||
DataModeler,
|
||||
Premium,
|
||||
MySQL,
|
||||
PostgreSQL,
|
||||
Oracle,
|
||||
SQLServer,
|
||||
SQLite,
|
||||
MariaDB,
|
||||
MongoDB,
|
||||
ReportViewer
|
||||
};
|
||||
private:
|
||||
std::random_device rand_dev;
|
||||
std::default_random_engine rand_eng;
|
||||
std::uniform_int_distribution<int> rand;
|
||||
uint8_t data[10];
|
||||
public:
|
||||
|
||||
NavicatKeygen() : rand_eng(rand_dev()), rand(0, UINT8_MAX), data{} {
|
||||
data[0] = 0x68;
|
||||
data[1] = 0x2A;
|
||||
}
|
||||
|
||||
void SetLanguageSignature(Language _language) {
|
||||
switch (_language) {
|
||||
case Language::English:
|
||||
data[5] = 0xAC; // Must be 0xAC for English version.
|
||||
data[6] = 0x88; // Must be 0x88 for English version.
|
||||
break;
|
||||
case Language::SimplifiedChinese:
|
||||
data[5] = 0xCE; // Must be 0xCE for Simplified Chinese version.
|
||||
data[6] = 0x32; // Must be 0x32 for Simplified Chinese version.
|
||||
break;
|
||||
case Language::TraditionalChinese:
|
||||
data[5] = 0xAA; // Must be 0xAA for Traditional Chinese version.
|
||||
data[6] = 0x99; // Must be 0x99 for Traditional Chinese version.
|
||||
break;
|
||||
case Language::Japanese:
|
||||
data[5] = 0xAD; // Must be 0xAD for Japanese version. Discoverer: @dragonflylee
|
||||
data[6] = 0x82; // Must be 0x82 for Japanese version. Discoverer: @dragonflylee
|
||||
break;
|
||||
case Language::Polish:
|
||||
data[5] = 0xBB; // Must be 0xBB for Polish version. Discoverer: @dragonflylee
|
||||
data[6] = 0x55; // Must be 0x55 for Polish version. Discoverer: @dragonflylee
|
||||
break;
|
||||
case Language::Spanish:
|
||||
data[5] = 0xAE; // Must be 0xAE for Spanish version. Discoverer: @dragonflylee
|
||||
data[6] = 0x10; // Must be 0x10 for Spanish version. Discoverer: @dragonflylee
|
||||
break;
|
||||
case Language::French:
|
||||
data[5] = 0xFA; // Must be 0xFA for French version. Discoverer: @Deltafox79
|
||||
data[6] = 0x20; // Must be 0x20 for French version. Discoverer: @Deltafox79
|
||||
break;
|
||||
case Language::German:
|
||||
data[5] = 0xB1; // Must be 0xB1 for German version. Discoverer: @dragonflylee
|
||||
data[6] = 0x60; // Must be 0x60 for German version. Discoverer: @dragonflylee
|
||||
break;
|
||||
case Language::Korean:
|
||||
data[5] = 0xB5; // Must be 0xB5 for Korean version. Discoverer: @dragonflylee
|
||||
data[6] = 0x60; // Must be 0x60 for Korean version. Discoverer: @dragonflylee
|
||||
break;
|
||||
case Language::Russian:
|
||||
data[5] = 0xEE; // Must be 0xB5 for Russian version. Discoverer: @dragonflylee
|
||||
data[6] = 0x16; // Must be 0x60 for Russian version. Discoverer: @dragonflylee
|
||||
break;
|
||||
case Language::Portuguese:
|
||||
data[5] = 0xCD; // Must be 0xCD for Portuguese version. Discoverer: @dragonflylee
|
||||
data[6] = 0x49; // Must be 0x49 for Portuguese version. Discoverer: @dragonflylee
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void SetLanguageSignature(uint8_t value0, uint8_t value1) {
|
||||
data[5] = value0;
|
||||
data[6] = value1;
|
||||
}
|
||||
|
||||
void SetProductSignature(Product _product) {
|
||||
switch (_product) {
|
||||
case Product::DataModeler:
|
||||
data[7] = 0x47;
|
||||
break;
|
||||
case Product::Premium:
|
||||
data[7] = 0x65;
|
||||
break;
|
||||
case Product::MySQL:
|
||||
data[7] = 0x68;
|
||||
break;
|
||||
case Product::PostgreSQL:
|
||||
data[7] = 0x6C;
|
||||
break;
|
||||
case Product::Oracle:
|
||||
data[7] = 0x70;
|
||||
break;
|
||||
case Product::SQLServer:
|
||||
data[7] = 0x74;
|
||||
break;
|
||||
case Product::SQLite:
|
||||
data[7] = 0x78;
|
||||
break;
|
||||
case Product::MariaDB:
|
||||
data[7] = 0x7C;
|
||||
break;
|
||||
case Product::MongoDB:
|
||||
data[7] = 0x80;
|
||||
break;
|
||||
case Product::ReportViewer:
|
||||
data[7] = 0xb;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void SetProductSignature(uint8_t value) {
|
||||
data[7] = value;
|
||||
}
|
||||
|
||||
void SetVersion(uint8_t version) {
|
||||
data[8] = version << 4;
|
||||
}
|
||||
|
||||
void Generate() {
|
||||
static const_DES_cblock DESKey = { 0x64, 0xAD, 0xF3, 0x2F, 0xAE, 0xF2, 0x1A, 0x27 };
|
||||
DESCipher cipher;
|
||||
|
||||
data[2] = static_cast<uint8_t>(rand(rand_eng));
|
||||
data[3] = static_cast<uint8_t>(rand(rand_eng));
|
||||
data[4] = static_cast<uint8_t>(rand(rand_eng));
|
||||
data[9] = 0x32;
|
||||
|
||||
cipher.SetKey(&DESKey);
|
||||
cipher.EncryptBlock(data + 2);
|
||||
}
|
||||
|
||||
std::string GetKey() const {
|
||||
static const char EncodeTable[] = "ABCDEFGH8JKLMN9PQRSTUVWXYZ234567";
|
||||
std::string Key;
|
||||
|
||||
Key.resize(16);
|
||||
|
||||
Key[0] = EncodeTable[data[0] >> 3];
|
||||
Key[1] = EncodeTable[(data[0] & 0x07) << 2 | data[1] >> 6];
|
||||
Key[2] = EncodeTable[data[1] >> 1 & 0x1F];
|
||||
Key[3] = EncodeTable[(data[1] & 0x1) << 4 | data[2] >> 4];
|
||||
Key[4] = EncodeTable[(data[2] & 0xF) << 1 | data[3] >> 7];
|
||||
Key[5] = EncodeTable[data[3] >> 2 & 0x1F];
|
||||
Key[6] = EncodeTable[(data[3] << 3 & 0x1F) | data[4] >> 5];
|
||||
Key[7] = EncodeTable[data[4] & 0x1F];
|
||||
|
||||
Key[8] = EncodeTable[data[5] >> 3];
|
||||
Key[9] = EncodeTable[(data[5] & 0x07) << 2 | data[6] >> 6];
|
||||
Key[10] = EncodeTable[data[6] >> 1 & 0x1F];
|
||||
Key[11] = EncodeTable[(data[6] & 0x1) << 4 | data[7] >> 4];
|
||||
Key[12] = EncodeTable[(data[7] & 0xF) << 1 | data[8] >> 7];
|
||||
Key[13] = EncodeTable[data[8] >> 2 & 0x1F];
|
||||
Key[14] = EncodeTable[(data[8] << 3 & 0x1F) | data[9] >> 5];
|
||||
Key[15] = EncodeTable[data[9] & 0x1F];
|
||||
|
||||
return Key;
|
||||
}
|
||||
|
||||
std::string GetFormatedKey() const {
|
||||
std::string Key = GetKey();
|
||||
Key.insert(Key.begin() + 4, '-');
|
||||
Key.insert(Key.begin() + 5 + 4, '-');
|
||||
Key.insert(Key.begin() + 10 + 4, '-');
|
||||
return Key;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
280
navicat-keygen/RSACipher.hpp
Normal file
280
navicat-keygen/RSACipher.hpp
Normal file
@ -0,0 +1,280 @@
|
||||
#pragma once
|
||||
#include <openssl/err.h>
|
||||
#include <openssl/bio.h>
|
||||
#include <openssl/rsa.h>
|
||||
#include <openssl/pem.h>
|
||||
#include <string>
|
||||
|
||||
class OpenSSLException {
|
||||
private:
|
||||
const unsigned long ErrorCode;
|
||||
public:
|
||||
explicit OpenSSLException(unsigned long code) : ErrorCode(code) {}
|
||||
unsigned long GetErrorCode() const noexcept { return ErrorCode; }
|
||||
const char* GetErrorString() const noexcept { return ERR_error_string(ErrorCode, nullptr); }
|
||||
};
|
||||
|
||||
template<typename _Type, void(_Type_free)(_Type*)>
|
||||
class OpenSSLObject {
|
||||
protected:
|
||||
_Type* _pObj;
|
||||
public:
|
||||
// take over the object passed in
|
||||
explicit OpenSSLObject(_Type* pObj) noexcept : _pObj(pObj) {}
|
||||
|
||||
OpenSSLObject(const OpenSSLObject<_Type, _Type_free>& other) = delete;
|
||||
|
||||
OpenSSLObject(OpenSSLObject<_Type, _Type_free>&& other) noexcept : _pObj(other._pObj) {
|
||||
other._pObj = nullptr;
|
||||
}
|
||||
|
||||
OpenSSLObject<_Type, _Type_free>&
|
||||
operator=(const OpenSSLObject<_Type, _Type_free>& other) = delete;
|
||||
|
||||
OpenSSLObject<_Type, _Type_free>&
|
||||
operator=(_Type* other) noexcept {
|
||||
if (_pObj != other) {
|
||||
_Type_free(_pObj);
|
||||
}
|
||||
_pObj = other;
|
||||
return *this;
|
||||
}
|
||||
|
||||
OpenSSLObject<_Type, _Type_free>&
|
||||
operator=(OpenSSLObject<_Type, _Type_free>&& other) noexcept {
|
||||
if (&other != this) {
|
||||
if (_pObj)
|
||||
_Type_free(_pObj);
|
||||
_pObj = other._pObj;
|
||||
other._pObj = nullptr;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
_Type* GetPointer() const noexcept {
|
||||
return _pObj;
|
||||
}
|
||||
|
||||
~OpenSSLObject() {
|
||||
if (_pObj) {
|
||||
_Type_free(_pObj);
|
||||
_pObj = nullptr;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class RSACipher {
|
||||
private:
|
||||
OpenSSLObject<RSA, RSA_free> _RsaObj;
|
||||
|
||||
explicit RSACipher(RSA* lpRsa) : _RsaObj(lpRsa) {}
|
||||
public:
|
||||
|
||||
RSACipher(const RSACipher&) = delete;
|
||||
RSACipher(RSACipher&&) = delete;
|
||||
RSACipher& operator=(const RSACipher&) = delete;
|
||||
RSACipher& operator=(RSACipher&&) = delete;
|
||||
|
||||
enum class KeyType {
|
||||
PrivateKey,
|
||||
PublicKey
|
||||
};
|
||||
|
||||
enum class KeyFormat {
|
||||
NotSpecified,
|
||||
PEM,
|
||||
PKCS1
|
||||
};
|
||||
|
||||
static RSACipher* Create() {
|
||||
RSA* pObj = RSA_new();
|
||||
return pObj ? new RSACipher(pObj) : nullptr;
|
||||
}
|
||||
|
||||
bool GenerateKey(int bits, unsigned int e = RSA_F4) {
|
||||
bool bSuccess = false;
|
||||
OpenSSLObject<BIGNUM, BN_free> bn_e(BN_new());
|
||||
|
||||
if (!bn_e.GetPointer())
|
||||
return false;
|
||||
|
||||
if (!BN_set_word(bn_e.GetPointer(), e))
|
||||
return false;
|
||||
|
||||
if (!RSA_generate_key_ex(_RsaObj.GetPointer(), bits, bn_e.GetPointer(), nullptr))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
template<KeyType _Type, KeyFormat _Format = KeyFormat::NotSpecified>
|
||||
bool ExportKeyToFile(const std::string& filename) {
|
||||
static_assert(
|
||||
_Type == KeyType::PrivateKey || (_Format == KeyFormat::PEM || _Format == KeyFormat::PKCS1),
|
||||
"Not supported format."
|
||||
);
|
||||
|
||||
bool bSuccess = false;
|
||||
OpenSSLObject<BIO, BIO_free_all> bio_file(BIO_new_file(filename.c_str(), "w"));
|
||||
|
||||
if (bio_file.GetPointer() == nullptr)
|
||||
return false;
|
||||
|
||||
if (_Type == KeyType::PrivateKey) {
|
||||
bSuccess = PEM_write_bio_RSAPrivateKey(bio_file.GetPointer(), _RsaObj.GetPointer(), nullptr, nullptr, 0, nullptr, nullptr) != 0;
|
||||
} else {
|
||||
if (_Format == KeyFormat::PEM)
|
||||
bSuccess = PEM_write_bio_RSA_PUBKEY(bio_file.GetPointer(), _RsaObj.GetPointer()) != 0;
|
||||
if (_Format == KeyFormat::PKCS1)
|
||||
bSuccess = PEM_write_bio_RSAPublicKey(bio_file.GetPointer(), _RsaObj.GetPointer()) != 0;
|
||||
}
|
||||
|
||||
return bSuccess;
|
||||
}
|
||||
|
||||
template<KeyType _Type, KeyFormat _Format = KeyFormat::NotSpecified>
|
||||
std::string ExportKeyString() {
|
||||
static_assert(
|
||||
_Type == KeyType::PrivateKey || (_Format == KeyFormat::PEM || _Format == KeyFormat::PKCS1),
|
||||
"Not supported format."
|
||||
);
|
||||
|
||||
std::string KeyString;
|
||||
OpenSSLObject<BIO, BIO_free_all> bio_mem(BIO_new(BIO_s_mem()));
|
||||
long len = 0;
|
||||
const char* lpdata = nullptr;
|
||||
|
||||
if (bio_mem.GetPointer() == nullptr)
|
||||
return KeyString;
|
||||
|
||||
if (_Type == KeyType::PrivateKey) {
|
||||
if (!PEM_write_bio_RSAPrivateKey(bio_mem.GetPointer(), _RsaObj.GetPointer(), nullptr, nullptr, 0, nullptr, nullptr))
|
||||
return KeyString;
|
||||
} else {
|
||||
if (_Format == KeyFormat::PEM) {
|
||||
if (!PEM_write_bio_RSA_PUBKEY(bio_mem.GetPointer(), _RsaObj.GetPointer()))
|
||||
return KeyString;
|
||||
}
|
||||
if (_Format == KeyFormat::PKCS1) {
|
||||
if (!PEM_write_bio_RSAPublicKey(bio_mem.GetPointer(), _RsaObj.GetPointer()))
|
||||
return KeyString;
|
||||
}
|
||||
}
|
||||
|
||||
len = BIO_get_mem_data(bio_mem.GetPointer(), &lpdata);
|
||||
KeyString.assign(lpdata, static_cast<size_t>(len));
|
||||
return KeyString;
|
||||
}
|
||||
|
||||
template<KeyType _Type, KeyFormat _Format = KeyFormat::NotSpecified>
|
||||
bool ImportKeyFromFile(const std::string& filename) {
|
||||
static_assert(
|
||||
_Type == KeyType::PrivateKey || (_Format == KeyFormat::PEM || _Format == KeyFormat::PKCS1),
|
||||
"Not supported format."
|
||||
);
|
||||
|
||||
bool bSuccess = false;
|
||||
OpenSSLObject<BIO, BIO_free_all> bio_file(BIO_new_file(filename.c_str(), "r"));
|
||||
OpenSSLObject<RSA, RSA_free> newRsaObj(nullptr);
|
||||
|
||||
if (bio_file.GetPointer() == nullptr)
|
||||
return false;
|
||||
|
||||
if (_Type == KeyType::PrivateKey) {
|
||||
newRsaObj = PEM_read_bio_RSAPrivateKey(bio_file.GetPointer(), nullptr, nullptr, nullptr);
|
||||
} else {
|
||||
if (_Format == KeyFormat::PEM)
|
||||
newRsaObj = PEM_read_bio_RSA_PUBKEY(bio_file.GetPointer(), nullptr, nullptr, nullptr);
|
||||
if (_Format == KeyFormat::PKCS1)
|
||||
newRsaObj = PEM_read_bio_RSAPublicKey(bio_file.GetPointer(), nullptr, nullptr, nullptr);
|
||||
}
|
||||
|
||||
if (newRsaObj.GetPointer()) {
|
||||
_RsaObj = static_cast<OpenSSLObject<RSA, RSA_free>&&>(newRsaObj);
|
||||
bSuccess = true;
|
||||
}
|
||||
|
||||
return bSuccess;
|
||||
}
|
||||
|
||||
template<KeyType _Type, KeyFormat _Format = KeyFormat::NotSpecified>
|
||||
bool ImportKeyString(const std::string& KeyString) {
|
||||
static_assert(
|
||||
_Type == KeyType::PrivateKey || (_Format == KeyFormat::PEM || _Format == KeyFormat::PKCS1),
|
||||
"Not supported format."
|
||||
);
|
||||
|
||||
bool bSuccess = false;
|
||||
OpenSSLObject<BIO, BIO_free_all> bio_mem(BIO_new(BIO_s_mem()));
|
||||
OpenSSLObject<RSA, RSA_free> newRsaObj(nullptr);
|
||||
|
||||
if (bio_mem.GetPointer() == nullptr)
|
||||
return false;
|
||||
|
||||
BIO_puts(bio_mem.GetPointer(), KeyString.c_str());
|
||||
|
||||
if (_Type == KeyType::PrivateKey) {
|
||||
newRsaObj = PEM_read_bio_RSAPrivateKey(bio_mem.GetPointer(), nullptr, nullptr, nullptr);
|
||||
} else {
|
||||
if (_Format == KeyFormat::PEM)
|
||||
newRsaObj = PEM_read_bio_RSA_PUBKEY(bio_mem.GetPointer(), nullptr, nullptr, nullptr);
|
||||
if (_Format == KeyFormat::PKCS1)
|
||||
newRsaObj = PEM_read_bio_RSAPublicKey(bio_mem.GetPointer(), nullptr, nullptr, nullptr);
|
||||
}
|
||||
|
||||
if (newRsaObj.GetPointer()) {
|
||||
_RsaObj = static_cast<OpenSSLObject<RSA, RSA_free>&&>(newRsaObj);
|
||||
bSuccess = true;
|
||||
}
|
||||
|
||||
return bSuccess;
|
||||
}
|
||||
|
||||
template<KeyType _Type = KeyType::PublicKey>
|
||||
int Encrypt(const void* from, int len, void* to, int padding) {
|
||||
int write_bytes = 0;
|
||||
|
||||
if (_Type == KeyType::PrivateKey) {
|
||||
write_bytes = RSA_private_encrypt(len,
|
||||
reinterpret_cast<const unsigned char*>(from),
|
||||
reinterpret_cast<unsigned char*>(to),
|
||||
_RsaObj.GetPointer(),
|
||||
padding);
|
||||
} else {
|
||||
write_bytes = RSA_public_encrypt(len,
|
||||
reinterpret_cast<const unsigned char*>(from),
|
||||
reinterpret_cast<unsigned char*>(to),
|
||||
_RsaObj.GetPointer(),
|
||||
padding);
|
||||
}
|
||||
|
||||
if (write_bytes == -1)
|
||||
write_bytes = 0;
|
||||
return write_bytes;
|
||||
}
|
||||
|
||||
template<KeyType _Type = KeyType::PrivateKey>
|
||||
int Decrypt(const void* from, int len, void* to, int padding) {
|
||||
int write_bytes = 0;
|
||||
|
||||
if (_Type == KeyType::PrivateKey) {
|
||||
write_bytes = RSA_private_decrypt(len,
|
||||
reinterpret_cast<const unsigned char*>(from),
|
||||
reinterpret_cast<unsigned char*>(to),
|
||||
_RsaObj.GetPointer(),
|
||||
padding);
|
||||
} else {
|
||||
write_bytes = RSA_public_decrypt(len,
|
||||
reinterpret_cast<const unsigned char*>(from),
|
||||
reinterpret_cast<unsigned char*>(to),
|
||||
_RsaObj.GetPointer(),
|
||||
padding);
|
||||
}
|
||||
|
||||
if (write_bytes == -1)
|
||||
write_bytes = 0;
|
||||
return write_bytes;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
@ -1,282 +1,217 @@
|
||||
#include <stdio.h>
|
||||
#include <memory.h>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <chrono>
|
||||
#include "Helper.hpp"
|
||||
#include "RSACipher.hpp"
|
||||
#include "DESCipher.hpp"
|
||||
#include "NavicatKeygen.hpp"
|
||||
|
||||
#include <openssl/err.h>
|
||||
#include <openssl/bio.h>
|
||||
#include <openssl/rsa.h>
|
||||
#include <openssl/pem.h>
|
||||
#include <openssl/des.h>
|
||||
#include <openssl/evp.h>
|
||||
#include <rapidjson/document.h>
|
||||
#include <rapidjson/writer.h>
|
||||
#include <rapidjson/stringbuffer.h>
|
||||
|
||||
enum NavicatLanguage {
|
||||
English,
|
||||
SimplifiedChinese,
|
||||
TraditionalChinese,
|
||||
Japanese,
|
||||
Polish,
|
||||
Spanish,
|
||||
French,
|
||||
German,
|
||||
Korean,
|
||||
Russian,
|
||||
Portuguese
|
||||
};
|
||||
|
||||
void GenerateSnKey(char (&SnKey)[16], NavicatLanguage _language) {
|
||||
static char EncodeTable[] = "ABCDEFGH8JKLMN9PQRSTUVWXYZ234567"; // Thanks for discoveries from @Wizr.
|
||||
// This is not a standard Base32 alphabet table.
|
||||
// The differences are:
|
||||
// | Standard | Non-standard |
|
||||
// |------------|----------------|
|
||||
// | 'I' | '8' |
|
||||
// | 'O' | '9' |
|
||||
|
||||
static DES_cblock DESKey = { 0x64, 0xAD, 0xF3, 0x2F, 0xAE, 0xF2, 0x1A, 0x27 };
|
||||
|
||||
unsigned char temp_snKey[10] = { 0x68, 0x2a }; // must start with 0x68, 0x2a
|
||||
temp_snKey[2] = rand();
|
||||
temp_snKey[3] = rand();
|
||||
temp_snKey[4] = rand();
|
||||
|
||||
switch (_language) {
|
||||
case English:
|
||||
temp_snKey[5] = 0xAC; // Must be 0xAC for English version.
|
||||
temp_snKey[6] = 0x88; // Must be 0x88 for English version.
|
||||
break;
|
||||
case SimplifiedChinese:
|
||||
temp_snKey[5] = 0xCE; // Must be 0xCE for Simplified Chinese version.
|
||||
temp_snKey[6] = 0x32; // Must be 0x32 for Simplified Chinese version.
|
||||
break;
|
||||
case TraditionalChinese:
|
||||
temp_snKey[5] = 0xAA; // Must be 0xAA for Traditional Chinese version.
|
||||
temp_snKey[6] = 0x99; // Must be 0x99 for Traditional Chinese version.
|
||||
break;
|
||||
case Japanese:
|
||||
temp_snKey[5] = 0xAD; // Must be 0xAD for Japanese version. Discoverer: @dragonflylee
|
||||
temp_snKey[6] = 0x82; // Must be 0x82 for Japanese version. Discoverer: @dragonflylee
|
||||
break;
|
||||
case Polish:
|
||||
temp_snKey[5] = 0xBB; // Must be 0xBB for Polish version. Discoverer: @dragonflylee
|
||||
temp_snKey[6] = 0x55; // Must be 0x55 for Polish version. Discoverer: @dragonflylee
|
||||
break;
|
||||
case Spanish:
|
||||
temp_snKey[5] = 0xAE; // Must be 0xAE for Spanish version. Discoverer: @dragonflylee
|
||||
temp_snKey[6] = 0x10; // Must be 0x10 for Spanish version. Discoverer: @dragonflylee
|
||||
break;
|
||||
case French:
|
||||
temp_snKey[5] = 0xFA; // Must be 0xFA for French version. Discoverer: @Deltafox79
|
||||
temp_snKey[6] = 0x20; // Must be 0x20 for French version. Discoverer: @Deltafox79
|
||||
break;
|
||||
case German:
|
||||
temp_snKey[5] = 0xB1; // Must be 0xB1 for German version. Discoverer: @dragonflylee
|
||||
temp_snKey[6] = 0x60; // Must be 0x60 for German version. Discoverer: @dragonflylee
|
||||
break;
|
||||
case Korean:
|
||||
temp_snKey[5] = 0xB5; // Must be 0xB5 for Korean version. Discoverer: @dragonflylee
|
||||
temp_snKey[6] = 0x60; // Must be 0x60 for Korean version. Discoverer: @dragonflylee
|
||||
break;
|
||||
case Russian:
|
||||
temp_snKey[5] = 0xEE; // Must be 0xB5 for Russian version. Discoverer: @dragonflylee
|
||||
temp_snKey[6] = 0x16; // Must be 0x60 for Russian version. Discoverer: @dragonflylee
|
||||
break;
|
||||
case Portuguese:
|
||||
temp_snKey[5] = 0xCD; // Must be 0xCD for Portuguese version. Discoverer: @dragonflylee
|
||||
temp_snKey[6] = 0x49; // Must be 0x49 for Portuguese version. Discoverer: @dragonflylee
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
temp_snKey[7] = 0x65; // 0x65 - commercial, 0x66 - non-commercial
|
||||
temp_snKey[8] = 0xC0; // High 4-bits = version number. Low 4-bits doesn't know, but can be used to delay activation time.
|
||||
temp_snKey[9] = 0x32; // 0xFB is Not-For-Resale-30-days license.
|
||||
// 0xFC is Not-For-Resale-90-days license.
|
||||
// 0xFD is Not-For-Resale-365-days license.
|
||||
// 0xFE is Not-For-Resale license.
|
||||
// 0xFF is Site license.
|
||||
// Must not be 0x00. 0x01-0xFA is ok.
|
||||
|
||||
DES_key_schedule schedule;
|
||||
DES_set_odd_parity(&DESKey);
|
||||
DES_set_key(&DESKey, &schedule);
|
||||
DES_cblock enc_temp_snKey;
|
||||
|
||||
DES_ecb_encrypt(reinterpret_cast<const_DES_cblock*>(temp_snKey + 2), &enc_temp_snKey, &schedule, DES_ENCRYPT);
|
||||
memmove(temp_snKey + 2, enc_temp_snKey, sizeof(enc_temp_snKey));
|
||||
|
||||
SnKey[0] = EncodeTable[temp_snKey[0] >> 3];
|
||||
SnKey[1] = EncodeTable[(temp_snKey[0] & 0x07) << 2 | temp_snKey[1] >> 6];
|
||||
SnKey[2] = EncodeTable[temp_snKey[1] >> 1 & 0x1F];
|
||||
SnKey[3] = EncodeTable[(temp_snKey[1] & 0x1) << 4 | temp_snKey[2] >> 4];
|
||||
SnKey[4] = EncodeTable[(temp_snKey[2] & 0xF) << 1 | temp_snKey[3] >> 7];
|
||||
SnKey[5] = EncodeTable[temp_snKey[3] >> 2 & 0x1F];
|
||||
SnKey[6] = EncodeTable[temp_snKey[3] << 3 & 0x1F | temp_snKey[4] >> 5];
|
||||
SnKey[7] = EncodeTable[temp_snKey[4] & 0x1F];
|
||||
|
||||
SnKey[8] = EncodeTable[temp_snKey[5] >> 3];
|
||||
SnKey[9] = EncodeTable[(temp_snKey[5] & 0x07) << 2 | temp_snKey[6] >> 6];
|
||||
SnKey[10] = EncodeTable[temp_snKey[6] >> 1 & 0x1F];
|
||||
SnKey[11] = EncodeTable[(temp_snKey[6] & 0x1) << 4 | temp_snKey[7] >> 4];
|
||||
SnKey[12] = EncodeTable[(temp_snKey[7] & 0xF) << 1 | temp_snKey[8] >> 7];
|
||||
SnKey[13] = EncodeTable[temp_snKey[8] >> 2 & 0x1F];
|
||||
SnKey[14] = EncodeTable[temp_snKey[8] << 3 & 0x1F | temp_snKey[9] >> 5];
|
||||
SnKey[15] = EncodeTable[temp_snKey[9] & 0x1F];
|
||||
|
||||
char formated_SnKeyString[20] = { };
|
||||
snprintf(formated_SnKeyString, sizeof(formated_SnKeyString), "%.4s-%.4s-%.4s-%.4s", SnKey, SnKey + 4, SnKey + 8, SnKey + 12);
|
||||
|
||||
std::cout << std::endl;
|
||||
std::cout
|
||||
<< "SnKey:" << std::endl
|
||||
<< formated_SnKeyString << std::endl
|
||||
<< std::endl;
|
||||
void help() {
|
||||
std::cout << "Usage:" << std::endl
|
||||
<< " ./navicat-keygen <RSA-2048 PrivateKey(PEM file)>" << std::endl
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
int main(int argc, char* argv[], char* envp[]) {
|
||||
if (argc != 2) {
|
||||
std::cout
|
||||
<< "Usage:" << std::endl
|
||||
<< " ./navicat-keygen <RSA-2048 PrivateKey(PEM file)>" << std::endl
|
||||
<< std::endl;
|
||||
help();
|
||||
return 0;
|
||||
}
|
||||
|
||||
srand(time(0));
|
||||
|
||||
Helper::ResourceGuard<RSACipher> cipher(RSACipher::Create());
|
||||
NavicatKeygen keygen;
|
||||
std::string username;
|
||||
std::string organization;
|
||||
|
||||
std::string RequestCode_b64;
|
||||
std::vector<uint8_t> RequestCode;
|
||||
char RequestInfo[256] = {};
|
||||
char ResponseInfo[256] = {};
|
||||
std::vector<uint8_t> ResponseCode;
|
||||
std::string ResponseCode_b64;
|
||||
|
||||
rapidjson::Document json;
|
||||
rapidjson::Value N_Key;
|
||||
rapidjson::Value N_Value;
|
||||
rapidjson::Value O_Key;
|
||||
rapidjson::Value O_Value;
|
||||
rapidjson::Value T_Key;
|
||||
rapidjson::Value T_Value;
|
||||
rapidjson::StringBuffer buffer;
|
||||
rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
|
||||
|
||||
if (cipher.ptr == nullptr) {
|
||||
std::cout << "@Function: " << __FUNCTION__ << " LINE: " << __LINE__ << std::endl;
|
||||
std::cout << "ERROR: Failed to create RSACipher." << std::endl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!cipher.ptr->ImportKeyFromFile<RSACipher::KeyType::PrivateKey, RSACipher::KeyFormat::PEM>(argv[1])) {
|
||||
std::cout << "@Function: " << __FUNCTION__ << " LINE: " << __LINE__ << std::endl;
|
||||
std::cout << "ERROR: Failed to load RSA-2048 key." << std::endl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
keygen.SetProductSignature(NavicatKeygen::Product::Premium);
|
||||
|
||||
std::cout
|
||||
<< "Which is your Navicat language?" << std::endl
|
||||
<< "0. English" << std::endl
|
||||
<< "1. Simplified Chinese" << std::endl
|
||||
<< "2. Traditional Chinese" << std::endl
|
||||
<< "3. Japanese" << std::endl
|
||||
<< "4. Polish" << std::endl
|
||||
<< "5. Spanish" << std::endl
|
||||
<< "6. French" << std::endl
|
||||
<< "7. German" << std::endl
|
||||
<< "8. Korean" << std::endl
|
||||
<< "9. Russian" << std::endl
|
||||
<< "10. Portuguese" << std::endl
|
||||
<< std::endl;
|
||||
|
||||
int LanguageIndex = -1;
|
||||
while(true) {
|
||||
std::cout << "(input index)>";
|
||||
|
||||
std::string temp;
|
||||
std::getline(std::cin, temp);
|
||||
try {
|
||||
LanguageIndex = std::stoi(temp);
|
||||
if (LanguageIndex < 0 || LanguageIndex > 10)
|
||||
throw std::invalid_argument("Invalid index");
|
||||
break;
|
||||
} catch(...) {
|
||||
std::cout << "Invalid index." << std::endl;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
char SnKey[16] = { };
|
||||
GenerateSnKey(SnKey, static_cast<NavicatLanguage>(LanguageIndex));
|
||||
<< "Which is your Navicat Premium language?" << std::endl
|
||||
<< "0. English" << std::endl
|
||||
<< "1. Simplified Chinese" << std::endl
|
||||
<< "2. Traditional Chinese" << std::endl
|
||||
<< "3. Japanese" << std::endl
|
||||
<< "4. Polish" << std::endl
|
||||
<< "5. Spanish" << std::endl
|
||||
<< "6. French" << std::endl
|
||||
<< "7. German" << std::endl
|
||||
<< "8. Korean" << std::endl
|
||||
<< "9. Russian" << std::endl
|
||||
<< "10. Portuguese" << std::endl
|
||||
<< std::endl;
|
||||
|
||||
double current_time
|
||||
= std::chrono::duration_cast<std::chrono::duration<double>>(
|
||||
std::chrono::system_clock::now().time_since_epoch()).count();
|
||||
{
|
||||
int num;
|
||||
if (!Helper::ReadNumber<0, 10>(num, "(Input index)> ", "Invalid index."))
|
||||
return 0;
|
||||
keygen.SetLanguageSignature(static_cast<NavicatKeygen::Language>(num));
|
||||
|
||||
BIO* BIO_file = BIO_new_file(argv[1], "r");
|
||||
if (BIO_file == nullptr) {
|
||||
std::cout << "Failed to read file." << std::endl;
|
||||
return -1;
|
||||
if (!Helper::ReadNumber<0, 15>(num, "(Input major version number, range: 0 ~ 15, default: 12)> ", "Invalid number."))
|
||||
return 0;
|
||||
keygen.SetVersion(static_cast<uint8_t>(num));
|
||||
}
|
||||
|
||||
RSA* PrivateKey = PEM_read_bio_RSAPrivateKey(BIO_file, nullptr, nullptr, nullptr);
|
||||
if (PrivateKey == nullptr) {
|
||||
std::cout << "Failed to load private key." << std::endl;
|
||||
return -2;
|
||||
}
|
||||
//
|
||||
// Generate snKey
|
||||
//
|
||||
keygen.Generate();
|
||||
std::cout << std::endl;
|
||||
std::cout << "Serial number:" << std::endl;
|
||||
std::cout << keygen.GetFormatedKey() << std::endl;
|
||||
std::cout << std::endl;
|
||||
|
||||
BIO_free_all(BIO_file);
|
||||
|
||||
std::string Name;
|
||||
std::string Organization;
|
||||
|
||||
std::cin.clear();
|
||||
//
|
||||
// Get user name
|
||||
//
|
||||
std::cout << "Your name: ";
|
||||
std::getline(std::cin, Name);
|
||||
std::cout << "Yout organization: ";
|
||||
std::getline(std::cin, Organization);
|
||||
if (!std::getline(std::cin, username))
|
||||
return 0;
|
||||
|
||||
std::string buffer;
|
||||
std::cout << "Input Request Code (in Base64), empty line to return:" << std::endl;
|
||||
while(true) {
|
||||
//
|
||||
// Get organization name
|
||||
//
|
||||
std::cout << "Your organization: ";
|
||||
if (!std::getline(std::cin, organization))
|
||||
return 0;
|
||||
|
||||
std::cout << std::endl;
|
||||
|
||||
//
|
||||
// Get request code in base64
|
||||
//
|
||||
std::cout << "Input request code (in Base64), input empty line to end:" << std::endl;
|
||||
while (true) {
|
||||
std::string temp;
|
||||
std::getline(std::cin, temp);
|
||||
buffer += temp;
|
||||
if (!std::getline(std::cin, temp))
|
||||
return 0;
|
||||
|
||||
if (temp.empty())
|
||||
break;
|
||||
|
||||
RequestCode_b64 += temp;
|
||||
}
|
||||
|
||||
unsigned char enc_data[1024] = { };
|
||||
char data[1024] = { };
|
||||
|
||||
std::string DeviceIdentifier("");
|
||||
EVP_DecodeBlock(enc_data, reinterpret_cast<const unsigned char*>(buffer.c_str()), buffer.length());
|
||||
if (RSA_private_decrypt(256, enc_data, reinterpret_cast<unsigned char*>(data), PrivateKey, RSA_PKCS1_PADDING) == -1) {
|
||||
std::cout << "Failed to decrypt data." << std::endl;
|
||||
return -3;
|
||||
}
|
||||
#ifdef _DEBUG
|
||||
std::cout << "-----------Begin Request Code Data---------------" << std::endl;
|
||||
std::cout << data << std::endl;
|
||||
std::cout <<"-----------End Request Code Data---------------" << std::endl;
|
||||
#endif
|
||||
// Get DeviceIdentifier from data.
|
||||
for (int i = 0, length = strlen(data) - 4; i < length; ++i) {
|
||||
if(data[i] == '"' &&
|
||||
data[i + 1] == 'D' &&
|
||||
data[i + 2] == 'I' &&
|
||||
data[i + 3] == '"') {
|
||||
|
||||
char temp[256] = { };
|
||||
int x = i + 4, j = 0;
|
||||
while (data[x] != '"' && x < length)
|
||||
x++;
|
||||
x++;
|
||||
while (data[x] != '"' && j < 256) {
|
||||
temp[j++] = data[x];
|
||||
x++;
|
||||
}
|
||||
DeviceIdentifier += temp;
|
||||
break;
|
||||
}
|
||||
//
|
||||
// Get request code in raw bytes
|
||||
//
|
||||
try {
|
||||
RequestCode = Helper::base64_decode(RequestCode_b64);
|
||||
} catch(...) {
|
||||
std::cout << "@Function: " << __FUNCTION__ << " LINE: " << __LINE__ << std::endl;
|
||||
std::cout << "ERROR: Helper::base64_decode fails." << std::endl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
memset(data, 0, sizeof(data));
|
||||
memset(enc_data, 0, sizeof(enc_data));
|
||||
//
|
||||
// Decrypt to get request info
|
||||
//
|
||||
if (!cipher.ptr->Decrypt(RequestCode.data(), static_cast<int>(RequestCode.size()), RequestInfo, RSA_PKCS1_PADDING)) {
|
||||
std::cout << "@Function: " << __FUNCTION__ << " LINE: " << __LINE__ << std::endl;
|
||||
std::cout << "ERROR: Decrypt<RSACipher::KeyType::PrivateKey> fails." << std::endl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
snprintf(data, sizeof(data), "{\n \"DI\" : \"%s\", \n \"T\" : \"%lf\", \n \"K\" : \"%.16s\", \n \"N\" : \"%s\", \n \"O\" : \"%s\"\n}",
|
||||
DeviceIdentifier.c_str(),
|
||||
current_time,
|
||||
SnKey,
|
||||
Name.c_str(),
|
||||
Organization.c_str()
|
||||
);
|
||||
//
|
||||
// print out request info
|
||||
//
|
||||
std::cout << "Request Info:" << std::endl;
|
||||
std::cout << RequestInfo << std::endl;
|
||||
std::cout << std::endl;
|
||||
|
||||
#ifdef _DEBUG
|
||||
std::cout << "-----------Begin Activation Code Data---------------" << std::endl;
|
||||
std::cout << data << std::endl;
|
||||
std::cout << "-----------End Activation Code Data---------------" << std::endl;
|
||||
#endif
|
||||
//
|
||||
// Generate response info
|
||||
//
|
||||
json.Parse(RequestInfo);
|
||||
json.RemoveMember("P"); // remove Platform info
|
||||
|
||||
RSA_private_encrypt(strlen(data), reinterpret_cast<unsigned char*>(data), enc_data, PrivateKey, RSA_PKCS1_PADDING);
|
||||
N_Key.SetString("N", 1);
|
||||
N_Value.SetString(username.c_str(), static_cast<rapidjson::SizeType>(username.length()));
|
||||
O_Key.SetString("O", 1);
|
||||
O_Value.SetString(organization.c_str(), static_cast<rapidjson::SizeType>(organization.length()));
|
||||
T_Key.SetString("T", 1);
|
||||
T_Value.SetUint(static_cast<unsigned>(std::time(nullptr)));
|
||||
|
||||
char result[1024] = { };
|
||||
EVP_EncodeBlock(reinterpret_cast<unsigned char*>(result), enc_data, 256);
|
||||
std::cout << "Activation Code:" << std::endl;
|
||||
std::cout << result << std::endl;
|
||||
json.AddMember(N_Key, N_Value, json.GetAllocator());
|
||||
json.AddMember(O_Key, O_Value, json.GetAllocator());
|
||||
json.AddMember(T_Key, T_Value, json.GetAllocator());
|
||||
|
||||
json.Accept(writer);
|
||||
|
||||
if (buffer.GetSize() > 240) {
|
||||
std::cout << "@Function: " << __FUNCTION__ << " LINE: " << __LINE__ << std::endl;
|
||||
std::cout << "ERROR: Response info is too long." << std::endl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
//
|
||||
// print out response info
|
||||
//
|
||||
memcpy(ResponseInfo, buffer.GetString(), buffer.GetSize());
|
||||
std::cout << "Response Info:" << std::endl;
|
||||
std::cout << ResponseInfo << std::endl;
|
||||
std::cout << std::endl;
|
||||
|
||||
//
|
||||
// encrypt response info
|
||||
//
|
||||
ResponseCode.resize(256);
|
||||
if (!cipher.ptr->Encrypt<RSACipher::KeyType::PrivateKey>(ResponseInfo,
|
||||
static_cast<int>(strlen(ResponseInfo)),
|
||||
ResponseCode.data(),
|
||||
RSA_PKCS1_PADDING)) {
|
||||
std::cout << "@Function: " << __FUNCTION__ << " LINE: " << __LINE__ << std::endl;
|
||||
std::cout << "ERROR: Encrypt<RSACipher::KeyType::PrivateKey> fails." << std::endl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
//
|
||||
// Encode encrypted response info in base64 format
|
||||
//
|
||||
try {
|
||||
ResponseCode_b64 = Helper::base64_encode(ResponseCode);
|
||||
} catch(...) {
|
||||
std::cout << "@Function: " << __FUNCTION__ << " LINE: " << __LINE__ << std::endl;
|
||||
std::cout << "ERROR: Helper::base64_encode fails." << std::endl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
//
|
||||
// print out activation code
|
||||
//
|
||||
std::cout << "License:" << std::endl;
|
||||
std::cout << ResponseCode_b64 << std::endl;
|
||||
|
||||
RSA_free(PrivateKey);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
78
navicat-patcher/FileMapper.hpp
Normal file
78
navicat-patcher/FileMapper.hpp
Normal file
@ -0,0 +1,78 @@
|
||||
#pragma once
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/mman.h>
|
||||
|
||||
class FileMapper {
|
||||
private:
|
||||
int _fd;
|
||||
size_t _MapSize;
|
||||
void* _pView;
|
||||
public:
|
||||
|
||||
class AlreadyInUseException {};
|
||||
|
||||
FileMapper() noexcept :
|
||||
_fd(-1),
|
||||
_MapSize(static_cast<size_t>(-1)),
|
||||
_pView(reinterpret_cast<void*>(-1)) { }
|
||||
|
||||
void Unmap() noexcept {
|
||||
if (_pView != reinterpret_cast<void*>(-1)) {
|
||||
munmap(_pView, _MapSize);
|
||||
_MapSize = static_cast<size_t>(-1);
|
||||
_pView = reinterpret_cast<void*>(-1);
|
||||
}
|
||||
}
|
||||
|
||||
void Close() noexcept {
|
||||
if (_fd != -1) {
|
||||
close(_fd);
|
||||
_fd = -1;
|
||||
}
|
||||
}
|
||||
|
||||
bool OpenFile(const char* FilePath) {
|
||||
if (_fd != -1)
|
||||
throw AlreadyInUseException();
|
||||
_fd = open(FilePath, O_RDWR, S_IRUSR | S_IWUSR);
|
||||
return _fd != -1;
|
||||
}
|
||||
|
||||
bool GetFileSize(off_t& refSize) noexcept {
|
||||
struct stat fd_stat = {};
|
||||
if (fstat(_fd, &fd_stat) != 0) {
|
||||
return false;
|
||||
} else {
|
||||
refSize = fd_stat.st_size;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
bool Map(size_t Size) {
|
||||
if (_pView != reinterpret_cast<void*>(-1))
|
||||
throw AlreadyInUseException();
|
||||
|
||||
_pView = mmap(nullptr, Size, PROT_READ | PROT_WRITE, MAP_SHARED, _fd, 0);
|
||||
|
||||
if (_pView != reinterpret_cast<void*>(-1)) {
|
||||
_MapSize = Size;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
template<typename _Type>
|
||||
_Type* GetView() const noexcept {
|
||||
return reinterpret_cast<_Type*>(_pView);
|
||||
}
|
||||
|
||||
~FileMapper() {
|
||||
Unmap();
|
||||
Close();
|
||||
}
|
||||
};
|
||||
|
||||
97
navicat-patcher/Helper.cpp
Normal file
97
navicat-patcher/Helper.cpp
Normal file
@ -0,0 +1,97 @@
|
||||
#include "Helper.hpp"
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
#include <signal.h>
|
||||
#include <setjmp.h>
|
||||
|
||||
namespace Helper {
|
||||
|
||||
static jmp_buf env;
|
||||
|
||||
static void SIGSEGV_Handler(int sig) {
|
||||
siglongjmp(env, 1);
|
||||
}
|
||||
|
||||
//
|
||||
// read byte(s) at address `p` as _Type to `out`
|
||||
// succeed if return true, otherwise return false
|
||||
//
|
||||
template<typename _Type>
|
||||
static inline bool ProbeForRead(const void* p, void* out) {
|
||||
int r = sigsetjmp(env, 1);
|
||||
if (r == 0) {
|
||||
*reinterpret_cast<_Type*>(out) = *reinterpret_cast<const _Type*>(p);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Print memory data in [from, to) at least
|
||||
// If `base` is not nullptr, print address as offset. Otherwise, as absolute address.
|
||||
// NOTICE:
|
||||
// `base` must >= `from`
|
||||
//
|
||||
void PrintMemory(const void* from, const void* to, const void* base) {
|
||||
const uint8_t* start = reinterpret_cast<const uint8_t*>(from);
|
||||
const uint8_t* end = reinterpret_cast<const uint8_t*>(to);
|
||||
const uint8_t* base_ptr = reinterpret_cast<const uint8_t*>(base);
|
||||
|
||||
if (start >= end)
|
||||
return;
|
||||
|
||||
while (reinterpret_cast<uintptr_t>(start) % 16)
|
||||
start--;
|
||||
|
||||
while (reinterpret_cast<uintptr_t>(start) % 16)
|
||||
end++;
|
||||
|
||||
void (*prev_handler)(int) = signal(SIGSEGV, SIGSEGV_Handler);
|
||||
while (start < end) {
|
||||
uint16_t value[16] = {};
|
||||
|
||||
if (base_ptr)
|
||||
printf("+0x%p ", reinterpret_cast<const void*>(start - base_ptr));
|
||||
else
|
||||
printf("0x%p ", start);
|
||||
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
if (ProbeForRead<uint8_t>(start + i, value + i)) {
|
||||
printf("%02x ", value[i]);
|
||||
} else {
|
||||
value[i] = 0xffff;
|
||||
printf("?? ");
|
||||
}
|
||||
}
|
||||
|
||||
printf(" ");
|
||||
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
if (value[i] < 0x20) {
|
||||
printf(".");
|
||||
} else if (value[i] > 0x7e) {
|
||||
printf(".");
|
||||
} else {
|
||||
printf("%c", value[i]);
|
||||
}
|
||||
}
|
||||
printf("\n");
|
||||
start += 0x10;
|
||||
}
|
||||
signal(SIGSEGV, prev_handler);
|
||||
}
|
||||
|
||||
void ErrorReport(const char* at, size_t line, const char* msg) {
|
||||
printf("ERROR: @%s LINE: %zu\n", at, line);
|
||||
printf("%s\n", msg);
|
||||
}
|
||||
|
||||
void ErrorReport(const char* at, size_t line, const char* msg, int err_code) {
|
||||
printf("ERROR: @%s LINE: %zu\n", at, line);
|
||||
printf("%s CODE: 0x%08x\n", msg, err_code);
|
||||
printf("REASON: %s\n", strerror(err_code));
|
||||
}
|
||||
}
|
||||
|
||||
35
navicat-patcher/Helper.hpp
Normal file
35
navicat-patcher/Helper.hpp
Normal file
@ -0,0 +1,35 @@
|
||||
#pragma once
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
|
||||
namespace Helper {
|
||||
|
||||
//
|
||||
// Print memory data in [from, to) at least
|
||||
// If `base` is not nullptr, print address as offset. Otherwise, as absolute address.
|
||||
// NOTICE:
|
||||
// `base` must >= `from`
|
||||
//
|
||||
void PrintMemory(const void* from, const void* to, const void* base);
|
||||
|
||||
void ErrorReport(const char* at, size_t line, const char* msg);
|
||||
void ErrorReport(const char* at, size_t line, const char* msg, int err_code);
|
||||
|
||||
template<typename _Type>
|
||||
struct ResourceGuard {
|
||||
_Type* ptr;
|
||||
|
||||
explicit ResourceGuard(_Type* p) noexcept : ptr(p) {}
|
||||
|
||||
~ResourceGuard() {
|
||||
if (ptr) {
|
||||
delete ptr;
|
||||
ptr = nullptr;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#define REPORT_ERROR(msg) Helper::ErrorReport(__FUNCTION__, __LINE__, msg)
|
||||
#define REPORT_ERROR_WITH_CODE(msg) Helper::ErrorReport(__FUNCTION__, __LINE__, msg, errno)
|
||||
#define PRINT_MESSAGE(msg) puts(msg)
|
||||
@ -1,6 +0,0 @@
|
||||
|
||||
release : main.c
|
||||
gcc main.c -o navicat-patcher
|
||||
|
||||
clean:
|
||||
rm navicat-patcher
|
||||
280
navicat-patcher/RSACipher.hpp
Normal file
280
navicat-patcher/RSACipher.hpp
Normal file
@ -0,0 +1,280 @@
|
||||
#pragma once
|
||||
#include <openssl/err.h>
|
||||
#include <openssl/bio.h>
|
||||
#include <openssl/rsa.h>
|
||||
#include <openssl/pem.h>
|
||||
#include <string>
|
||||
|
||||
class OpenSSLException {
|
||||
private:
|
||||
const unsigned long ErrorCode;
|
||||
public:
|
||||
explicit OpenSSLException(unsigned long code) : ErrorCode(code) {}
|
||||
unsigned long GetErrorCode() const noexcept { return ErrorCode; }
|
||||
const char* GetErrorString() const noexcept { return ERR_error_string(ErrorCode, nullptr); }
|
||||
};
|
||||
|
||||
template<typename _Type, void(_Type_free)(_Type*)>
|
||||
class OpenSSLObject {
|
||||
protected:
|
||||
_Type* _pObj;
|
||||
public:
|
||||
// take over the object passed in
|
||||
explicit OpenSSLObject(_Type* pObj) noexcept : _pObj(pObj) {}
|
||||
|
||||
OpenSSLObject(const OpenSSLObject<_Type, _Type_free>& other) = delete;
|
||||
|
||||
OpenSSLObject(OpenSSLObject<_Type, _Type_free>&& other) noexcept : _pObj(other._pObj) {
|
||||
other._pObj = nullptr;
|
||||
}
|
||||
|
||||
OpenSSLObject<_Type, _Type_free>&
|
||||
operator=(const OpenSSLObject<_Type, _Type_free>& other) = delete;
|
||||
|
||||
OpenSSLObject<_Type, _Type_free>&
|
||||
operator=(_Type* other) noexcept {
|
||||
if (_pObj != other) {
|
||||
_Type_free(_pObj);
|
||||
}
|
||||
_pObj = other;
|
||||
return *this;
|
||||
}
|
||||
|
||||
OpenSSLObject<_Type, _Type_free>&
|
||||
operator=(OpenSSLObject<_Type, _Type_free>&& other) noexcept {
|
||||
if (&other != this) {
|
||||
if (_pObj)
|
||||
_Type_free(_pObj);
|
||||
_pObj = other._pObj;
|
||||
other._pObj = nullptr;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
_Type* GetPointer() const noexcept {
|
||||
return _pObj;
|
||||
}
|
||||
|
||||
~OpenSSLObject() {
|
||||
if (_pObj) {
|
||||
_Type_free(_pObj);
|
||||
_pObj = nullptr;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class RSACipher {
|
||||
private:
|
||||
OpenSSLObject<RSA, RSA_free> _RsaObj;
|
||||
|
||||
explicit RSACipher(RSA* lpRsa) : _RsaObj(lpRsa) {}
|
||||
public:
|
||||
|
||||
RSACipher(const RSACipher&) = delete;
|
||||
RSACipher(RSACipher&&) = delete;
|
||||
RSACipher& operator=(const RSACipher&) = delete;
|
||||
RSACipher& operator=(RSACipher&&) = delete;
|
||||
|
||||
enum class KeyType {
|
||||
PrivateKey,
|
||||
PublicKey
|
||||
};
|
||||
|
||||
enum class KeyFormat {
|
||||
NotSpecified,
|
||||
PEM,
|
||||
PKCS1
|
||||
};
|
||||
|
||||
static RSACipher* Create() {
|
||||
RSA* pObj = RSA_new();
|
||||
return pObj ? new RSACipher(pObj) : nullptr;
|
||||
}
|
||||
|
||||
bool GenerateKey(int bits, unsigned int e = RSA_F4) {
|
||||
bool bSuccess = false;
|
||||
OpenSSLObject<BIGNUM, BN_free> bn_e(BN_new());
|
||||
|
||||
if (!bn_e.GetPointer())
|
||||
return false;
|
||||
|
||||
if (!BN_set_word(bn_e.GetPointer(), e))
|
||||
return false;
|
||||
|
||||
if (!RSA_generate_key_ex(_RsaObj.GetPointer(), bits, bn_e.GetPointer(), nullptr))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
template<KeyType _Type, KeyFormat _Format = KeyFormat::NotSpecified>
|
||||
bool ExportKeyToFile(const std::string& filename) {
|
||||
static_assert(
|
||||
_Type == KeyType::PrivateKey || (_Format == KeyFormat::PEM || _Format == KeyFormat::PKCS1),
|
||||
"Not supported format."
|
||||
);
|
||||
|
||||
bool bSuccess = false;
|
||||
OpenSSLObject<BIO, BIO_free_all> bio_file(BIO_new_file(filename.c_str(), "w"));
|
||||
|
||||
if (bio_file.GetPointer() == nullptr)
|
||||
return false;
|
||||
|
||||
if (_Type == KeyType::PrivateKey) {
|
||||
bSuccess = PEM_write_bio_RSAPrivateKey(bio_file.GetPointer(), _RsaObj.GetPointer(), nullptr, nullptr, 0, nullptr, nullptr) != 0;
|
||||
} else {
|
||||
if (_Format == KeyFormat::PEM)
|
||||
bSuccess = PEM_write_bio_RSA_PUBKEY(bio_file.GetPointer(), _RsaObj.GetPointer()) != 0;
|
||||
if (_Format == KeyFormat::PKCS1)
|
||||
bSuccess = PEM_write_bio_RSAPublicKey(bio_file.GetPointer(), _RsaObj.GetPointer()) != 0;
|
||||
}
|
||||
|
||||
return bSuccess;
|
||||
}
|
||||
|
||||
template<KeyType _Type, KeyFormat _Format = KeyFormat::NotSpecified>
|
||||
std::string ExportKeyString() {
|
||||
static_assert(
|
||||
_Type == KeyType::PrivateKey || (_Format == KeyFormat::PEM || _Format == KeyFormat::PKCS1),
|
||||
"Not supported format."
|
||||
);
|
||||
|
||||
std::string KeyString;
|
||||
OpenSSLObject<BIO, BIO_free_all> bio_mem(BIO_new(BIO_s_mem()));
|
||||
long len = 0;
|
||||
const char* lpdata = nullptr;
|
||||
|
||||
if (bio_mem.GetPointer() == nullptr)
|
||||
return KeyString;
|
||||
|
||||
if (_Type == KeyType::PrivateKey) {
|
||||
if (!PEM_write_bio_RSAPrivateKey(bio_mem.GetPointer(), _RsaObj.GetPointer(), nullptr, nullptr, 0, nullptr, nullptr))
|
||||
return KeyString;
|
||||
} else {
|
||||
if (_Format == KeyFormat::PEM) {
|
||||
if (!PEM_write_bio_RSA_PUBKEY(bio_mem.GetPointer(), _RsaObj.GetPointer()))
|
||||
return KeyString;
|
||||
}
|
||||
if (_Format == KeyFormat::PKCS1) {
|
||||
if (!PEM_write_bio_RSAPublicKey(bio_mem.GetPointer(), _RsaObj.GetPointer()))
|
||||
return KeyString;
|
||||
}
|
||||
}
|
||||
|
||||
len = BIO_get_mem_data(bio_mem.GetPointer(), &lpdata);
|
||||
KeyString.assign(lpdata, static_cast<size_t>(len));
|
||||
return KeyString;
|
||||
}
|
||||
|
||||
template<KeyType _Type, KeyFormat _Format = KeyFormat::NotSpecified>
|
||||
bool ImportKeyFromFile(const std::string& filename) {
|
||||
static_assert(
|
||||
_Type == KeyType::PrivateKey || (_Format == KeyFormat::PEM || _Format == KeyFormat::PKCS1),
|
||||
"Not supported format."
|
||||
);
|
||||
|
||||
bool bSuccess = false;
|
||||
OpenSSLObject<BIO, BIO_free_all> bio_file(BIO_new_file(filename.c_str(), "r"));
|
||||
OpenSSLObject<RSA, RSA_free> newRsaObj(nullptr);
|
||||
|
||||
if (bio_file.GetPointer() == nullptr)
|
||||
return false;
|
||||
|
||||
if (_Type == KeyType::PrivateKey) {
|
||||
newRsaObj = PEM_read_bio_RSAPrivateKey(bio_file.GetPointer(), nullptr, nullptr, nullptr);
|
||||
} else {
|
||||
if (_Format == KeyFormat::PEM)
|
||||
newRsaObj = PEM_read_bio_RSA_PUBKEY(bio_file.GetPointer(), nullptr, nullptr, nullptr);
|
||||
if (_Format == KeyFormat::PKCS1)
|
||||
newRsaObj = PEM_read_bio_RSAPublicKey(bio_file.GetPointer(), nullptr, nullptr, nullptr);
|
||||
}
|
||||
|
||||
if (newRsaObj.GetPointer()) {
|
||||
_RsaObj = static_cast<OpenSSLObject<RSA, RSA_free>&&>(newRsaObj);
|
||||
bSuccess = true;
|
||||
}
|
||||
|
||||
return bSuccess;
|
||||
}
|
||||
|
||||
template<KeyType _Type, KeyFormat _Format = KeyFormat::NotSpecified>
|
||||
bool ImportKeyString(const std::string& KeyString) {
|
||||
static_assert(
|
||||
_Type == KeyType::PrivateKey || (_Format == KeyFormat::PEM || _Format == KeyFormat::PKCS1),
|
||||
"Not supported format."
|
||||
);
|
||||
|
||||
bool bSuccess = false;
|
||||
OpenSSLObject<BIO, BIO_free_all> bio_mem(BIO_new(BIO_s_mem()));
|
||||
OpenSSLObject<RSA, RSA_free> newRsaObj(nullptr);
|
||||
|
||||
if (bio_mem.GetPointer() == nullptr)
|
||||
return false;
|
||||
|
||||
BIO_puts(bio_mem.GetPointer(), KeyString.c_str());
|
||||
|
||||
if (_Type == KeyType::PrivateKey) {
|
||||
newRsaObj = PEM_read_bio_RSAPrivateKey(bio_mem.GetPointer(), nullptr, nullptr, nullptr);
|
||||
} else {
|
||||
if (_Format == KeyFormat::PEM)
|
||||
newRsaObj = PEM_read_bio_RSA_PUBKEY(bio_mem.GetPointer(), nullptr, nullptr, nullptr);
|
||||
if (_Format == KeyFormat::PKCS1)
|
||||
newRsaObj = PEM_read_bio_RSAPublicKey(bio_mem.GetPointer(), nullptr, nullptr, nullptr);
|
||||
}
|
||||
|
||||
if (newRsaObj.GetPointer()) {
|
||||
_RsaObj = static_cast<OpenSSLObject<RSA, RSA_free>&&>(newRsaObj);
|
||||
bSuccess = true;
|
||||
}
|
||||
|
||||
return bSuccess;
|
||||
}
|
||||
|
||||
template<KeyType _Type = KeyType::PublicKey>
|
||||
int Encrypt(const void* from, int len, void* to, int padding) {
|
||||
int write_bytes = 0;
|
||||
|
||||
if (_Type == KeyType::PrivateKey) {
|
||||
write_bytes = RSA_private_encrypt(len,
|
||||
reinterpret_cast<const unsigned char*>(from),
|
||||
reinterpret_cast<unsigned char*>(to),
|
||||
_RsaObj.GetPointer(),
|
||||
padding);
|
||||
} else {
|
||||
write_bytes = RSA_public_encrypt(len,
|
||||
reinterpret_cast<const unsigned char*>(from),
|
||||
reinterpret_cast<unsigned char*>(to),
|
||||
_RsaObj.GetPointer(),
|
||||
padding);
|
||||
}
|
||||
|
||||
if (write_bytes == -1)
|
||||
write_bytes = 0;
|
||||
return write_bytes;
|
||||
}
|
||||
|
||||
template<KeyType _Type = KeyType::PrivateKey>
|
||||
int Decrypt(const void* from, int len, void* to, int padding) {
|
||||
int write_bytes = 0;
|
||||
|
||||
if (_Type == KeyType::PrivateKey) {
|
||||
write_bytes = RSA_private_decrypt(len,
|
||||
reinterpret_cast<const unsigned char*>(from),
|
||||
reinterpret_cast<unsigned char*>(to),
|
||||
_RsaObj.GetPointer(),
|
||||
padding);
|
||||
} else {
|
||||
write_bytes = RSA_public_decrypt(len,
|
||||
reinterpret_cast<const unsigned char*>(from),
|
||||
reinterpret_cast<unsigned char*>(to),
|
||||
_RsaObj.GetPointer(),
|
||||
padding);
|
||||
}
|
||||
|
||||
if (write_bytes == -1)
|
||||
write_bytes = 0;
|
||||
return write_bytes;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
84
navicat-patcher/Solution0.cpp
Normal file
84
navicat-patcher/Solution0.cpp
Normal file
@ -0,0 +1,84 @@
|
||||
#include "Solutions.hpp"
|
||||
#include "Helper.hpp"
|
||||
|
||||
namespace Patcher {
|
||||
|
||||
const char Solution0::Keyword[452] =
|
||||
"-----BEGIN PUBLIC KEY-----\x00"
|
||||
"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAw1dqF3SkCaAAmMzs889I\x00"
|
||||
"qdW9M2dIdh3jG9yPcmLnmJiGpBF4E9VHSMGe8oPAy2kJDmdNt4BcEygvssEfginv\x00"
|
||||
"a5t5jm352UAoDosUJkTXGQhpAWMF4fBmBpO3EedG62rOsqMBgmSdAyxCSPBRJIOF\x00"
|
||||
"R0QgZFbRnU0frj34fiVmgYiLuZSAmIbs8ZxiHPdp1oD4tUpvsFci4QJtYNjNnGU2\x00"
|
||||
"WPH6rvChGl1IRKrxMtqLielsvajUjyrgOC6NmymYMvZNER3htFEtL1eQbCyTfDmt\x00"
|
||||
"YyQ1Wt4Ot12lxf0wVIR5mcGN7XCXJRHOFHSf1gzXWabRSvmt1nrl7sW6cjxljuuQ\x00"
|
||||
"awIDAQAB\x00"
|
||||
"-----END PUBLIC KEY-----\x00";
|
||||
|
||||
bool Solution0::FindPatchOffset() noexcept {
|
||||
bool bFound = false;
|
||||
|
||||
uint8_t* pFileView = pTargetFile->GetView<uint8_t>();
|
||||
off_t FileSize;
|
||||
|
||||
if (pFileView == nullptr)
|
||||
return false;
|
||||
|
||||
if (!pTargetFile->GetFileSize(FileSize))
|
||||
return false;
|
||||
|
||||
if (FileSize < KeywordLength)
|
||||
return false;
|
||||
|
||||
FileSize -= KeywordLength;
|
||||
for (off_t i = 0; i < FileSize; ++i) {
|
||||
if (pFileView[i] == Keyword[0] && memcmp(pFileView + i, Keyword, KeywordLength) == 0) {
|
||||
PatchOffset = i;
|
||||
bFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (bFound)
|
||||
printf("MESSAGE: [Solution0] Keyword has been found: offset = +0x%08lx.\n",
|
||||
static_cast<unsigned long>(PatchOffset));
|
||||
return bFound;
|
||||
}
|
||||
|
||||
bool Solution0::MakePatch(RSACipher* cipher) const {
|
||||
uint8_t* lpTargetFileView = pTargetFile->GetView<uint8_t>();
|
||||
std::string RSAPublicKeyPEM =
|
||||
cipher->ExportKeyString<RSACipher::KeyType::PublicKey, RSACipher::KeyFormat::PEM>();
|
||||
|
||||
if (RSAPublicKeyPEM.empty()) {
|
||||
REPORT_ERROR("ERROR: cipher->ExportKeyString failed.");
|
||||
return false;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < RSAPublicKeyPEM.length(); ++i) {
|
||||
if (RSAPublicKeyPEM[i] == '\n')
|
||||
RSAPublicKeyPEM[i] = '\x00';
|
||||
}
|
||||
|
||||
if (RSAPublicKeyPEM.length() != KeywordLength) {
|
||||
REPORT_ERROR("ERROR: Public key length does not match.");
|
||||
return false;
|
||||
}
|
||||
|
||||
PRINT_MESSAGE("//");
|
||||
PRINT_MESSAGE("// Begin Solution0");
|
||||
PRINT_MESSAGE("//");
|
||||
printf("@+0x%08llX\nPrevious:\n", PatchOffset);
|
||||
Helper::PrintMemory(lpTargetFileView + PatchOffset,
|
||||
lpTargetFileView + PatchOffset + KeywordLength,
|
||||
lpTargetFileView);
|
||||
|
||||
memcpy(lpTargetFileView + PatchOffset, RSAPublicKeyPEM.c_str(), KeywordLength);
|
||||
|
||||
PRINT_MESSAGE("After:");
|
||||
Helper::PrintMemory(lpTargetFileView + PatchOffset,
|
||||
lpTargetFileView + PatchOffset + KeywordLength,
|
||||
lpTargetFileView);
|
||||
PRINT_MESSAGE("");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
116
navicat-patcher/Solution1.cpp
Normal file
116
navicat-patcher/Solution1.cpp
Normal file
@ -0,0 +1,116 @@
|
||||
#include "Solutions.hpp"
|
||||
#include "Helper.hpp"
|
||||
|
||||
namespace Patcher {
|
||||
|
||||
const uint8_t Solution1::Keyword[KeywordLength] = {
|
||||
0xfe, 0xfd, 0xfc, 0xf4, 0xfe, 0xd2, 0xf8, 0xf4, 0xf1, 0xd3, 0xde, 0xc7, 0xdf, 0xd3, 0xd0, 0xfd,
|
||||
0x8a, 0xc3, 0x85, 0xf4, 0xf6, 0xe9, 0xfc, 0xfc, 0xf2, 0xf5, 0xfa, 0xf5, 0xf6, 0xe9, 0x81, 0xfb,
|
||||
0xfe, 0xfd, 0xfc, 0xf4, 0xf4, 0xdf, 0xf2, 0xf9, 0xf2, 0xe5, 0xf0, 0xf7, 0xc0, 0x89, 0xdd, 0xcb,
|
||||
0xf5, 0x87, 0xe6, 0xdd, 0xf4, 0xd9, 0xf8, 0xfb, 0xde, 0xf9, 0xcf, 0xc5, 0x8f, 0x80, 0x80, 0xf3,
|
||||
0xc2, 0xd0, 0xe2, 0x8f, 0xfa, 0x8a, 0xdd, 0xf3, 0xd7, 0xdc, 0x86, 0xdc, 0xf0, 0x81, 0xc0, 0xea,
|
||||
0xd0, 0xd9, 0xf9, 0xd8, 0xda, 0xf2, 0xd0, 0xfd, 0xc3, 0xf6, 0xf3, 0x82, 0xf2, 0x81, 0xef, 0xf2,
|
||||
0xe0, 0xf9, 0xf2, 0xd3, 0x8f, 0xd7, 0xe9, 0xfb, 0xca, 0x86, 0xde, 0xfc, 0xf3, 0xd5, 0xdd, 0xf4,
|
||||
0xc7, 0x80, 0xf7, 0xd5, 0xf2, 0xc1, 0xde, 0xcc, 0xc0, 0xc7, 0xf0, 0xd0, 0xd0, 0xd1, 0xd7, 0xcc,
|
||||
0xd2, 0x81, 0xc1, 0x83, 0xdd, 0xd5, 0x8a, 0x8f, 0x81, 0xe1, 0xf4, 0xd9, 0xf3, 0xd7, 0xca, 0xef,
|
||||
0xf9, 0xdf, 0xe1, 0xee, 0xf0, 0xe9, 0xd1, 0xca, 0xf2, 0xe3, 0xf8, 0xf0, 0x83, 0xde, 0xfb, 0xd7,
|
||||
0xf1, 0xc4, 0xfa, 0x85, 0xf2, 0xdd, 0xdd, 0xfd, 0x85, 0x86, 0xc7, 0xf9, 0xc4, 0xc9, 0xf4, 0xf8,
|
||||
0xd4, 0xd9, 0xe6, 0xd2, 0xf6, 0xc1, 0xc1, 0xf9, 0xe0, 0xe4, 0xf7, 0xe4, 0xfd, 0xf1, 0xf6, 0xfc,
|
||||
0xe1, 0x84, 0xe4, 0xd1, 0xed, 0xfe, 0xdb, 0xe8, 0xdd, 0xe1, 0x85, 0xd0, 0xc5, 0xd2, 0x8a, 0x8e,
|
||||
0xd5, 0xdd, 0xe3, 0xdb, 0xd0, 0xe1, 0xd0, 0xf6, 0xc6, 0xee, 0xe6, 0xf7, 0xda, 0xf1, 0xdb, 0xc9,
|
||||
0x8b, 0xee, 0xcd, 0xdf, 0xff, 0xe8, 0xdd, 0xca, 0x82, 0xdb, 0xf1, 0x82, 0xc3, 0xed, 0xc9, 0xcc,
|
||||
0xc0, 0xf2, 0xd6, 0xdf, 0x83, 0xe9, 0xf3, 0xce, 0xea, 0xfa, 0xdf, 0xf8, 0xd9, 0xff, 0xec, 0x88,
|
||||
0xe4, 0xe4, 0xfd, 0x80, 0xc5, 0xce, 0xfa, 0xd2, 0xf4, 0xd8, 0x84, 0xff, 0xe5, 0xf3, 0xcb, 0xc2,
|
||||
0xfe, 0xc0, 0xc4, 0xfa, 0xde, 0xdd, 0xd5, 0xc9, 0xc5, 0xd5, 0xdf, 0xe3, 0xdd, 0xc1, 0xcb, 0xdd,
|
||||
0xfc, 0xf7, 0x83, 0xf8, 0xda, 0xc1, 0xd4, 0xe3, 0xfe, 0xc2, 0xef, 0xf8, 0xf2, 0xea, 0x8a, 0xd2,
|
||||
0xc7, 0xf2, 0xf0, 0xc2, 0xfb, 0x89, 0xdc, 0xeb, 0xd1, 0xf7, 0xcc, 0xe2, 0xd1, 0xfc, 0xd4, 0xce,
|
||||
0xea, 0xcd, 0xe4, 0x87, 0xe0, 0xcc, 0x8d, 0xf5, 0xc7, 0x85, 0x87, 0xda, 0xcf, 0xde, 0x89, 0xcd,
|
||||
0xe5, 0xfd, 0xe7, 0x83, 0xda, 0xdb, 0xfe, 0xf4, 0x84, 0xec, 0xf6, 0xee, 0xfd, 0xea, 0xf1, 0xf5,
|
||||
0xf5, 0xfc, 0xe6, 0xd0, 0x86, 0xdf, 0xc3, 0xe2, 0xe4, 0xd5, 0xd7, 0xe4, 0xe4, 0xce, 0xd4, 0xce,
|
||||
0x82, 0xda, 0xc7, 0xda, 0x80, 0xcb, 0xee, 0x8c, 0xd0, 0xde, 0xcd, 0xda, 0xdd, 0xcd, 0xcc, 0xeb,
|
||||
0xd2, 0xc3, 0xfc, 0xf2, 0xf6, 0xe9, 0xf8, 0xf8
|
||||
};
|
||||
|
||||
bool Solution1::FindPatchOffset() noexcept {
|
||||
bool bFound = false;
|
||||
|
||||
uint8_t* pFileView = pTargetFile->GetView<uint8_t>();
|
||||
off_t FileSize;
|
||||
|
||||
if (pFileView == nullptr)
|
||||
return false;
|
||||
|
||||
if (!pTargetFile->GetFileSize(FileSize))
|
||||
return false;
|
||||
|
||||
if (FileSize < KeywordLength)
|
||||
return false;
|
||||
|
||||
FileSize -= KeywordLength;
|
||||
for (off_t i = 0; i < FileSize; ++i) {
|
||||
if (pFileView[i] == Keyword[0] && memcmp(pFileView + i, Keyword, KeywordLength) == 0) {
|
||||
PatchOffset = i;
|
||||
bFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (bFound)
|
||||
printf("MESSAGE: [Solution1] Keyword has been found: offset = +0x%08lx.\n",
|
||||
static_cast<unsigned long>(PatchOffset));
|
||||
return bFound;
|
||||
}
|
||||
|
||||
bool Solution1::MakePatch(RSACipher* cipher) const {
|
||||
uint8_t* lpTargetFileView = pTargetFile->GetView<uint8_t>();
|
||||
std::string RSAPublicKeyPEM =
|
||||
cipher->ExportKeyString<RSACipher::KeyType::PublicKey, RSACipher::KeyFormat::PEM>();
|
||||
|
||||
if (RSAPublicKeyPEM.empty()) {
|
||||
REPORT_ERROR("ERROR: cipher->ExportKeyString failed.");
|
||||
return false;
|
||||
}
|
||||
|
||||
RSAPublicKeyPEM.erase(RSAPublicKeyPEM.find("-----BEGIN PUBLIC KEY-----"), 26);
|
||||
RSAPublicKeyPEM.erase(RSAPublicKeyPEM.find("-----END PUBLIC KEY-----"), 24);
|
||||
{
|
||||
std::string::size_type pos = 0;
|
||||
while ((pos = RSAPublicKeyPEM.find("\n", pos)) != std::string::npos) {
|
||||
RSAPublicKeyPEM.erase(pos, 1);
|
||||
}
|
||||
}
|
||||
|
||||
if (RSAPublicKeyPEM.length() != KeywordLength) {
|
||||
REPORT_ERROR("ERROR: Public key length does not match.");
|
||||
return false;
|
||||
}
|
||||
|
||||
{
|
||||
uint8_t key = 8;
|
||||
for (size_t i = 0; i < RSAPublicKeyPEM.length(); ++i) {
|
||||
if (key == 0)
|
||||
key = 8;
|
||||
RSAPublicKeyPEM[i] ^= 0xbb - key;
|
||||
--key;
|
||||
}
|
||||
}
|
||||
|
||||
PRINT_MESSAGE("//");
|
||||
PRINT_MESSAGE("// Begin Solution1");
|
||||
PRINT_MESSAGE("//");
|
||||
printf("@+0x%08llX\nPrevious:\n", PatchOffset);
|
||||
Helper::PrintMemory(lpTargetFileView + PatchOffset,
|
||||
lpTargetFileView + PatchOffset + KeywordLength,
|
||||
lpTargetFileView);
|
||||
|
||||
memcpy(lpTargetFileView + PatchOffset, RSAPublicKeyPEM.c_str(), KeywordLength);
|
||||
|
||||
PRINT_MESSAGE("After:");
|
||||
Helper::PrintMemory(lpTargetFileView + PatchOffset,
|
||||
lpTargetFileView + PatchOffset + KeywordLength,
|
||||
lpTargetFileView);
|
||||
PRINT_MESSAGE("");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
78
navicat-patcher/Solutions.hpp
Normal file
78
navicat-patcher/Solutions.hpp
Normal file
@ -0,0 +1,78 @@
|
||||
#pragma once
|
||||
#include "FileMapper.hpp"
|
||||
#include "RSACipher.hpp"
|
||||
|
||||
namespace Patcher {
|
||||
|
||||
class Solution {
|
||||
public:
|
||||
virtual void SetFile(FileMapper* pFile) = 0;
|
||||
virtual bool CheckKey(RSACipher* cipher) const = 0;
|
||||
virtual bool FindPatchOffset() = 0;
|
||||
virtual bool MakePatch(RSACipher* cipher) const = 0;
|
||||
virtual ~Solution() = default;
|
||||
};
|
||||
|
||||
// Solution0 will replace the RSA public key stored in main application.
|
||||
class Solution0 : public Solution {
|
||||
private:
|
||||
static constexpr size_t KeywordLength = 451;
|
||||
static const char Keyword[KeywordLength + 1];
|
||||
|
||||
FileMapper* pTargetFile;
|
||||
off_t PatchOffset;
|
||||
public:
|
||||
|
||||
Solution0() noexcept :
|
||||
pTargetFile(nullptr),
|
||||
PatchOffset(-1) {}
|
||||
|
||||
virtual void SetFile(FileMapper* pMainApp) noexcept override {
|
||||
pTargetFile = pMainApp;
|
||||
}
|
||||
|
||||
// Solution0 does not have any requirements for an RSA-2048 key
|
||||
virtual bool CheckKey(RSACipher* cipher) const noexcept override {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Return true if found, other return false
|
||||
virtual bool FindPatchOffset() noexcept override;
|
||||
|
||||
// Make a patch based on an RSA private key given
|
||||
// Return true if success, otherwise return false
|
||||
virtual bool MakePatch(RSACipher* cipher) const override;
|
||||
};
|
||||
|
||||
// Solution1 will replace the RSA public key stored in main application.
|
||||
class Solution1 : public Solution {
|
||||
private:
|
||||
static constexpr size_t KeywordLength = 0x188;
|
||||
static const uint8_t Keyword[KeywordLength];
|
||||
|
||||
FileMapper* pTargetFile;
|
||||
off_t PatchOffset;
|
||||
public:
|
||||
Solution1() :
|
||||
pTargetFile(nullptr),
|
||||
PatchOffset(-1) {}
|
||||
|
||||
virtual void SetFile(FileMapper* pLibccFile) noexcept override {
|
||||
pTargetFile = pLibccFile;
|
||||
}
|
||||
|
||||
// Solution1 has no requirements for an RSA-2048 key
|
||||
virtual bool CheckKey(RSACipher* cipher) const noexcept override {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Return true if found, otherwise return false
|
||||
virtual bool FindPatchOffset() noexcept override;
|
||||
|
||||
// Make a patch based on an RSA private key given
|
||||
// Return true if success, otherwise return false
|
||||
virtual bool MakePatch(RSACipher* cipher) const override;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@ -1,127 +0,0 @@
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
#include <memory.h>
|
||||
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/errno.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/mman.h>
|
||||
|
||||
const char pubkey[9][72] = {
|
||||
"-----BEGIN PUBLIC KEY-----",
|
||||
"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxqkTcfbKw8ysVygePlcB",
|
||||
"oUAhCF6oniyP13iDtu85ZsHwqw8PnMyTp6n6FnMN9YinleIAy6NFveBu/vshTN8S",
|
||||
"oXbYyy5AqdZ8CQpfvuriO9UNfgV1l7SFdPPpruFAmOw+uzA3GawMsg3QNK/htqJe",
|
||||
"b4xKHFS04xC2AueE2RTmk6tJcL8TEBfRG7DEYOHPjebKl1NQ3ZIu15U97cCPYKO2",
|
||||
"pWHzsb+Fr4Wj0DChLoxlXxaBcJ2ozogaq0tW2t4Aopvt9kRSuSK9HcgxICJM5ct4",
|
||||
"naU91WFGWlw0+0JpiMIl5OnMbpak/5xQre9DL8zM8LjRy14I88txvXvhPEsWaYCO",
|
||||
"1QIDAQAB",
|
||||
"-----END PUBLIC KEY-----"
|
||||
};
|
||||
|
||||
void help() {
|
||||
printf("Usage:\n");
|
||||
printf(" ./navicat-patcher <navicat executable file>\n");
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
size_t search_pubkey_location(uint8_t* pFileContent, size_t FileSize) {
|
||||
static const char search_str[] = "-----BEGIN PUBLIC KEY-----";
|
||||
|
||||
size_t i = 0;
|
||||
if (FileSize < sizeof(search_str) - 1) return (size_t)-1;
|
||||
FileSize -= sizeof(search_str);
|
||||
for (; i < FileSize; ++i) {
|
||||
if (pFileContent[i] == '-' && memcmp(pFileContent + i, search_str, sizeof(search_str) - 1) == 0)
|
||||
return i;
|
||||
}
|
||||
return (size_t)-1;
|
||||
}
|
||||
|
||||
void do_patch(uint8_t* pFileContent, size_t offset) {
|
||||
strcpy(pFileContent + offset, pubkey[0]);
|
||||
offset += strlen(pubkey[0]) + 1;
|
||||
|
||||
strcpy(pFileContent + offset, pubkey[1]);
|
||||
offset += strlen(pubkey[1]) + 1;
|
||||
|
||||
strcpy(pFileContent + offset, pubkey[2]);
|
||||
offset += strlen(pubkey[2]) + 1;
|
||||
|
||||
strcpy(pFileContent + offset, pubkey[3]);
|
||||
offset += strlen(pubkey[3]) + 1;
|
||||
|
||||
strcpy(pFileContent + offset, pubkey[4]);
|
||||
offset += strlen(pubkey[4]) + 1;
|
||||
|
||||
strcpy(pFileContent + offset, pubkey[5]);
|
||||
offset += strlen(pubkey[5]) + 1;
|
||||
|
||||
strcpy(pFileContent + offset, pubkey[6]);
|
||||
offset += strlen(pubkey[6]) + 1;
|
||||
|
||||
strcpy(pFileContent + offset, pubkey[7]);
|
||||
offset += strlen(pubkey[7]) + 1;
|
||||
|
||||
strcpy(pFileContent + offset, pubkey[8]);
|
||||
offset += strlen(pubkey[8]) + 1;
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[], char* envp[]) {
|
||||
int status = 0;
|
||||
int fd = -1;
|
||||
struct stat fd_stat = {};
|
||||
uint8_t* file_content = 0;
|
||||
|
||||
if (argc != 2) {
|
||||
help();
|
||||
return 0;
|
||||
}
|
||||
|
||||
fd = open(argv[1], O_RDWR, S_IRUSR | S_IWUSR);
|
||||
if (fd == -1) {
|
||||
printf("Failed to open file. CODE: 0x%08x\n", errno);
|
||||
status = errno;
|
||||
goto main_fin;
|
||||
} else {
|
||||
printf("Open file successfully.\n");
|
||||
}
|
||||
|
||||
if (fstat(fd, &fd_stat) != 0) {
|
||||
printf("Failed to get file size. CODE: 0x%08x\n", errno);
|
||||
status = errno;
|
||||
goto main_fin;
|
||||
} else {
|
||||
printf("Get file size successfully: %zu\n", fd_stat.st_size);
|
||||
}
|
||||
|
||||
file_content = mmap(NULL, fd_stat.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
|
||||
|
||||
if (file_content == (void*)-1) {
|
||||
printf("Failed to map file. CODE: 0x%08x\n", errno);
|
||||
status = errno;
|
||||
goto main_fin;
|
||||
} else {
|
||||
printf("Map file successfully.\n");
|
||||
}
|
||||
|
||||
size_t offset = search_pubkey_location(file_content, fd_stat.st_size);
|
||||
if (offset == (size_t)-1) {
|
||||
printf("Failed to find pubkey location.\n");
|
||||
goto main_fin;
|
||||
}
|
||||
|
||||
printf("offset = 0x%016llx\n", offset);
|
||||
|
||||
do_patch(file_content, offset);
|
||||
printf("Success!\n");
|
||||
|
||||
main_fin:
|
||||
if (file_content != NULL) munmap(file_content, fd_stat.st_size);
|
||||
if (fd != -1) close(fd);
|
||||
return status;
|
||||
}
|
||||
|
||||
156
navicat-patcher/main.cpp
Normal file
156
navicat-patcher/main.cpp
Normal file
@ -0,0 +1,156 @@
|
||||
#include <stdio.h>
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "Helper.hpp"
|
||||
#include "FileMapper.hpp"
|
||||
#include "RSACipher.hpp"
|
||||
#include "Solutions.hpp"
|
||||
|
||||
#define SAFE_DELETE(x) { delete x; x = nullptr; }
|
||||
|
||||
void help() {
|
||||
puts("Usage:");
|
||||
puts(" ./navicat-patcher <navicat executable file> [RSA-2048 PrivateKey(PEM file)]");
|
||||
puts("");
|
||||
}
|
||||
|
||||
bool LoadKey(RSACipher* cipher, const char* filename,
|
||||
Patcher::Solution* pSolution0,
|
||||
Patcher::Solution* pSolution1) {
|
||||
if (filename) {
|
||||
if (!cipher->ImportKeyFromFile<RSACipher::KeyType::PrivateKey, RSACipher::KeyFormat::PEM>(filename)) {
|
||||
REPORT_ERROR("ERROR: cipher->ImportKeyFromFile failed.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((pSolution0 && !pSolution0->CheckKey(cipher)) ||
|
||||
(pSolution1 && !pSolution1->CheckKey(cipher))) {
|
||||
REPORT_ERROR("ERROR: The RSA private key you provided cannot be used.");
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
PRINT_MESSAGE("");
|
||||
PRINT_MESSAGE("MESSAGE: Generating new RSA private key, it may take a long time.");
|
||||
|
||||
do {
|
||||
cipher->GenerateKey(2048);
|
||||
} while ((pSolution0 && !pSolution0->CheckKey(cipher)) ||
|
||||
(pSolution1 && !pSolution1->CheckKey(cipher))); // re-generate RSA key if one of CheckKey return false
|
||||
|
||||
if (!cipher->ExportKeyToFile<RSACipher::KeyType::PrivateKey, RSACipher::KeyFormat::NotSpecified>("RegPrivateKey.pem")) {
|
||||
REPORT_ERROR("ERROR: Failed to save RSA private key.");
|
||||
return false;
|
||||
}
|
||||
|
||||
PRINT_MESSAGE("MESSAGE: New RSA private key has been saved to RegPrivateKey.pem.");
|
||||
}
|
||||
|
||||
std::string PublicKeyString = cipher->ExportKeyString<RSACipher::KeyType::PublicKey, RSACipher::KeyFormat::PEM>();
|
||||
if (PublicKeyString.empty()) {
|
||||
REPORT_ERROR("ERROR: cipher->ExportKeyString failed.");
|
||||
return false;
|
||||
}
|
||||
|
||||
PRINT_MESSAGE("");
|
||||
PRINT_MESSAGE("Your RSA public key:");
|
||||
PRINT_MESSAGE(PublicKeyString.c_str());
|
||||
return true;
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[], char* envp[]) {
|
||||
int status = 0;
|
||||
FileMapper MainApp;
|
||||
off_t MainAppSize;
|
||||
Helper::ResourceGuard<RSACipher> cipher(nullptr);
|
||||
Helper::ResourceGuard<Patcher::Solution> pSolution0(nullptr);
|
||||
Helper::ResourceGuard<Patcher::Solution> pSolution1(nullptr);
|
||||
|
||||
if (argc != 2 && argc != 3) {
|
||||
help();
|
||||
return status;
|
||||
}
|
||||
|
||||
PRINT_MESSAGE("NOTICE:");
|
||||
printf("This patcher will modify the file: %s\n", argv[1]);
|
||||
PRINT_MESSAGE("Please make a backup by your own if you care. Otherwise just ignore this notice.");
|
||||
PRINT_MESSAGE("Press Enter to continue OR Ctrl+C to abort...");
|
||||
getchar();
|
||||
|
||||
cipher.ptr = RSACipher::Create();
|
||||
if (cipher.ptr == nullptr) {
|
||||
REPORT_ERROR("ERROR: RSACipher::Create failed.");
|
||||
return status;
|
||||
}
|
||||
pSolution0.ptr = new Patcher::Solution0();
|
||||
pSolution1.ptr = new Patcher::Solution1();
|
||||
|
||||
//
|
||||
// Map file
|
||||
//
|
||||
if (!MainApp.OpenFile(argv[1])) {
|
||||
status = errno;
|
||||
REPORT_ERROR_WITH_CODE("Failed to open file.");
|
||||
return status;
|
||||
} else {
|
||||
PRINT_MESSAGE("MESSAGE: Open file successfully.");
|
||||
}
|
||||
|
||||
if (!MainApp.GetFileSize(MainAppSize)) {
|
||||
status = errno;
|
||||
REPORT_ERROR_WITH_CODE("Failed to get file size.");
|
||||
return status;
|
||||
} else {
|
||||
printf("MESSAGE: Get file size successfully: %lld\n", MainAppSize);
|
||||
}
|
||||
|
||||
if (!MainApp.Map(static_cast<size_t>(MainAppSize))) {
|
||||
status = errno;
|
||||
REPORT_ERROR_WITH_CODE("Failed to map file.");
|
||||
return status;
|
||||
} else {
|
||||
PRINT_MESSAGE("MESSAGE: Map file successfully.");
|
||||
}
|
||||
|
||||
pSolution0.ptr->SetFile(&MainApp);
|
||||
pSolution1.ptr->SetFile(&MainApp);
|
||||
|
||||
//
|
||||
// Find patch offsets
|
||||
//
|
||||
if (!pSolution0.ptr->FindPatchOffset()) {
|
||||
PRINT_MESSAGE("MESSAGE: Solution0: Cannot find public key. Solution0 will be omitted.");
|
||||
pSolution0.ptr->SetFile(nullptr);
|
||||
SAFE_DELETE(pSolution0.ptr);
|
||||
}
|
||||
|
||||
if (!pSolution1.ptr->FindPatchOffset()) {
|
||||
PRINT_MESSAGE("MESSAGE: Solution1: Cannot find public key. Solution1 will be omitted.");
|
||||
pSolution1.ptr->SetFile(nullptr);
|
||||
SAFE_DELETE(pSolution1.ptr);
|
||||
}
|
||||
|
||||
//
|
||||
// Load or generate RSA-2048 key
|
||||
//
|
||||
if (!LoadKey(cipher.ptr, argc == 3 ? argv[2] : nullptr, pSolution0.ptr, pSolution1.ptr))
|
||||
return status;
|
||||
|
||||
//
|
||||
// Making patch
|
||||
//
|
||||
if (pSolution0.ptr && !pSolution0.ptr->MakePatch(cipher.ptr))
|
||||
return status;
|
||||
if (pSolution1.ptr && !pSolution1.ptr->MakePatch(cipher.ptr))
|
||||
return status;
|
||||
|
||||
//
|
||||
// Report result
|
||||
//
|
||||
if (pSolution0.ptr)
|
||||
PRINT_MESSAGE("Solution0 has been done successfully.");
|
||||
if (pSolution1.ptr)
|
||||
PRINT_MESSAGE("Solution1 has been done successfully.");
|
||||
|
||||
return status;
|
||||
}
|
||||
@ -1,9 +0,0 @@
|
||||
-----BEGIN PUBLIC KEY-----
|
||||
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxqkTcfbKw8ysVygePlcB
|
||||
oUAhCF6oniyP13iDtu85ZsHwqw8PnMyTp6n6FnMN9YinleIAy6NFveBu/vshTN8S
|
||||
oXbYyy5AqdZ8CQpfvuriO9UNfgV1l7SFdPPpruFAmOw+uzA3GawMsg3QNK/htqJe
|
||||
b4xKHFS04xC2AueE2RTmk6tJcL8TEBfRG7DEYOHPjebKl1NQ3ZIu15U97cCPYKO2
|
||||
pWHzsb+Fr4Wj0DChLoxlXxaBcJ2ozogaq0tW2t4Aopvt9kRSuSK9HcgxICJM5ct4
|
||||
naU91WFGWlw0+0JpiMIl5OnMbpak/5xQre9DL8zM8LjRy14I88txvXvhPEsWaYCO
|
||||
1QIDAQAB
|
||||
-----END PUBLIC KEY-----
|
||||
Loading…
Reference in New Issue
Block a user