main.cpp 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451
  1. #include <fcntl.h>
  2. #include <sys/stat.h>
  3. #include <sys/mman.h>
  4. #include <plist/plist++.h>
  5. #include <string>
  6. #include "ExceptionSystem.hpp"
  7. #include "ResourceWrapper.hpp"
  8. #include "ResourceTraitsUnix.hpp"
  9. #include "PatchSolutions.hpp"
  10. #include "Misc.hpp"
  11. static void Welcome(bool bWait) {
  12. puts("**********************************************************");
  13. puts("* Navicat Patcher (macOS) by @DoubleLabyrinth *");
  14. puts("* Version: 5.0 *");
  15. puts("**********************************************************");
  16. puts("");
  17. if (bWait) {
  18. puts("Press Enter to continue or Ctrl + C to abort.");
  19. getchar();
  20. }
  21. }
  22. static void Help() {
  23. puts("Usage:");
  24. puts(" navicat-patcher [--dry-run] <Navicat installation path> [RSA-2048 Private Key File]");
  25. puts("");
  26. puts(" [--dry-run] Run patcher without applying any patches.");
  27. puts(" This parameter is optional.");
  28. puts("");
  29. puts(" <Navicat installation path> Path to `Navicat Premium.app`.");
  30. puts(" Example:");
  31. puts(" /Applications/Navicat\\ Premium.app/");
  32. puts(" This parameter must be specified.");
  33. puts("");
  34. puts(" [RSA-2048 Private Key File] Path to a PEM-format RSA-2048 private key file.");
  35. puts(" This parameter is optional.");
  36. puts("");
  37. }
  38. static bool ParseCommandLine(int argc, char* argv[], bool& bDryrun, std::string& szInstallPath, std::string& szKeyFilePath) {
  39. if (argc == 2) {
  40. bDryrun = false;
  41. szInstallPath = argv[1];
  42. szKeyFilePath.clear();
  43. return true;
  44. } else if (argc == 3) {
  45. if (strcasecmp(argv[1], "--dry-run") == 0) {
  46. bDryrun = true;
  47. szInstallPath = argv[2];
  48. szKeyFilePath.clear();
  49. return true;
  50. } else {
  51. bDryrun = false;
  52. szInstallPath = argv[1];
  53. szKeyFilePath = argv[2];
  54. return true;
  55. }
  56. } else if (argc == 4) {
  57. if (strcasecmp(argv[1], "--dry-run") == 0) {
  58. bDryrun = true;
  59. szInstallPath = argv[2];
  60. szKeyFilePath = argv[3];
  61. return true;
  62. } else {
  63. return false;
  64. }
  65. } else {
  66. return false;
  67. }
  68. }
  69. static std::string GetNavicatVersion(std::string_view AppPath) {
  70. ARL::ResourceWrapper hInfoPlist{ ARL::ResourceTraits::FileDescriptor{} };
  71. ARL::ResourceWrapper lpInfoPlist{ ARL::ResourceTraits::CppObject<PList::Dictionary>{} };
  72. hInfoPlist.TakeOver(open((std::string(AppPath) + "/Contents/Info.plist").c_str(), O_RDONLY));
  73. if (hInfoPlist.IsValid() == false) {
  74. throw ARL::SystemError(__FILE__, __LINE__, errno, "Failed to open Contents/Info.plist.");
  75. }
  76. struct stat statInfoPlist = {};
  77. if (fstat(hInfoPlist, &statInfoPlist) != 0) {
  78. throw ARL::SystemError(__FILE__, __LINE__, errno, "Failed to get file size of Contents/Info.plist.");
  79. }
  80. std::string contentInfoPlist(statInfoPlist.st_size, '\x00');
  81. if (read(hInfoPlist, contentInfoPlist.data(), contentInfoPlist.size()) != contentInfoPlist.size()) {
  82. throw ARL::SystemError(__FILE__, __LINE__, errno, "Failed to read Contents/Info.plist.");
  83. }
  84. lpInfoPlist.TakeOver(dynamic_cast<PList::Dictionary*>(PList::Structure::FromXml(contentInfoPlist)));
  85. if (lpInfoPlist.IsValid() == false) {
  86. throw ARL::Exception(__FILE__, __LINE__, "Failed to parse Contents/Info.plist.");
  87. }
  88. auto key_value = lpInfoPlist->Find("CFBundleShortVersionString");
  89. if (key_value == lpInfoPlist->End()) {
  90. throw ARL::Exception(__FILE__, __LINE__, "Cannot find CFBundleShortVersionString in Contents/Info.plist.");
  91. }
  92. if (key_value->second->GetType() == PLIST_STRING) {
  93. return dynamic_cast<PList::String*>(key_value->second)->GetValue();
  94. } else {
  95. throw ARL::Exception(__FILE__, __LINE__, "Type check failed for CFBundleShortVersionString.");
  96. }
  97. }
  98. static void LoadKey(nkg::RSACipher& Cipher, std::string_view szKeyFileName,
  99. nkg::PatchSolution* lpSolution0,
  100. nkg::PatchSolution* lpSolution1,
  101. nkg::PatchSolution* lpSolution2,
  102. nkg::PatchSolution* lpSolution3) {
  103. if (szKeyFileName.empty() == false) {
  104. printf("[*] Import RSA-2048 key from %s\n", szKeyFileName.data());
  105. Cipher.ImportKeyFromFile<nkg::RSAKeyType::PrivateKey, nkg::RSAKeyFormat::PEM>(szKeyFileName);
  106. if ((lpSolution0 && lpSolution0->CheckKey(Cipher) == false) ||
  107. (lpSolution1 && lpSolution1->CheckKey(Cipher) == false) ||
  108. (lpSolution2 && lpSolution2->CheckKey(Cipher) == false) ||
  109. (lpSolution3 && lpSolution3->CheckKey(Cipher) == false))
  110. {
  111. throw ARL::Exception(__FILE__, __LINE__, "The RSA private key you provide cannot be used.");
  112. }
  113. } else {
  114. puts("[*] Generating new RSA private key, it may take a long time...");
  115. do {
  116. Cipher.GenerateKey(2048);
  117. } while ((lpSolution0 && lpSolution0->CheckKey(Cipher) == false) ||
  118. (lpSolution1 && lpSolution1->CheckKey(Cipher) == false) ||
  119. (lpSolution2 && lpSolution2->CheckKey(Cipher) == false) ||
  120. (lpSolution3 && lpSolution3->CheckKey(Cipher) == false)); // re-generate RSA key if CheckKey return false
  121. }
  122. printf("[*] Your RSA private key:\n");
  123. printf(" %s\n",
  124. [&Cipher]() -> std::string {
  125. auto szPrivateKey = Cipher.ExportKeyString<nkg::RSAKeyType::PrivateKey, nkg::RSAKeyFormat::PEM>();
  126. for (size_t pos = 0; (pos = szPrivateKey.find('\n', pos)) != std::string::npos; pos += strlen("\n ")) {
  127. szPrivateKey.replace(pos, 1, "\n ");
  128. }
  129. return szPrivateKey;
  130. }().c_str()
  131. );
  132. printf("[*] Your RSA public key:\n");
  133. printf(" %s\n",
  134. [&Cipher]() -> std::string {
  135. auto szPublicKey = Cipher.ExportKeyString<nkg::RSAKeyType::PublicKey, nkg::RSAKeyFormat::PEM>();
  136. for (size_t pos = 0; (pos = szPublicKey.find('\n', pos)) != std::string::npos; pos += strlen("\n ")) {
  137. szPublicKey.replace(pos, 1, "\n ");
  138. }
  139. return szPublicKey;
  140. }().c_str()
  141. );
  142. printf("\n");
  143. }
  144. int main(int argc, char* argv[]) {
  145. bool bDryrun;
  146. std::string szInstallPath;
  147. std::string szKeyFilePath;
  148. if (ParseCommandLine(argc, argv, bDryrun, szInstallPath, szKeyFilePath) == false) {
  149. Welcome(false);
  150. Help();
  151. return -1;
  152. } else {
  153. Welcome(true);
  154. try {
  155. if (nkg::Misc::FsIsDirectory(szInstallPath) == false) {
  156. throw ARL::Exception(__FILE__, __LINE__, "Navicat installation path doesn't point to a directory.")
  157. .PushHint("Are you sure the path you specified is correct?")
  158. .PushFormatHint("The path you specified: %s", szInstallPath.c_str());
  159. }
  160. if (szKeyFilePath.empty() == false && nkg::Misc::FsIsFile(szKeyFilePath) == false) {
  161. throw ARL::Exception(__FILE__, __LINE__, "RSA key file path doesn't point to a file.")
  162. .PushHint("Are you sure the path you specified is correct?")
  163. .PushFormatHint("The path you specified: %s", szKeyFilePath.c_str());
  164. }
  165. while (szInstallPath.back() == '/') {
  166. szInstallPath.pop_back();
  167. }
  168. nkg::RSACipher Cipher;
  169. ARL::ResourceWrapper lpSolution0{ ARL::ResourceTraits::CppObject<nkg::PatchSolution>{} };
  170. ARL::ResourceWrapper lpSolution1{ ARL::ResourceTraits::CppObject<nkg::PatchSolution>{} };
  171. ARL::ResourceWrapper lpSolution2{ ARL::ResourceTraits::CppObject<nkg::PatchSolution>{} };
  172. ARL::ResourceWrapper lpSolution3{ ARL::ResourceTraits::CppObject<nkg::PatchSolution>{} };
  173. std::string main_path;
  174. ARL::ResourceWrapper main_fd{ ARL::ResourceTraits::FileDescriptor{} };
  175. ARL::ResourceWrapper main_stat{ ARL::ResourceTraits::CppObject<struct stat>{} };
  176. ARL::ResourceWrapperEx main_mmap{ ARL::ResourceTraits::MapView{}, [&main_stat](void* p) {
  177. if (munmap(p, main_stat->st_size) < 0) {
  178. throw ARL::SystemError(__FILE__, __LINE__, errno, "munmap failed.");
  179. }
  180. } };
  181. ARL::ResourceWrapper main_interpreter{ ARL::ResourceTraits::CppObject<nkg::X64ImageInterpreter>{} };
  182. std::string libcc_path;
  183. ARL::ResourceWrapper libcc_fd{ ARL::ResourceTraits::FileDescriptor{} };
  184. ARL::ResourceWrapper libcc_stat{ ARL::ResourceTraits::CppObject<struct stat>{} };
  185. ARL::ResourceWrapperEx libcc_mmap{ ARL::ResourceTraits::MapView{}, [&libcc_stat](void* p) {
  186. if (munmap(p, libcc_stat->st_size) < 0) {
  187. throw ARL::SystemError(__FILE__, __LINE__, errno, "munmap failed.");
  188. }
  189. } };
  190. ARL::ResourceWrapper libcc_interpreter{ ARL::ResourceTraits::CppObject<nkg::X64ImageInterpreter>{} };
  191. //
  192. // try open "Contents/MacOS/Navicat Premium"
  193. // try open "Contents/Frameworks/libcc-premium.dylib"
  194. //
  195. main_path = szInstallPath + "/Contents/MacOS/Navicat Premium";
  196. main_fd.TakeOver(open(main_path.c_str(), O_RDWR));
  197. if (main_fd.IsValid()) {
  198. printf("[+] Try to open \"%s\" ... Ok!\n", "Contents/MacOS/Navicat Premium");
  199. } else {
  200. if (errno == ENOENT) {
  201. printf("[-] Try to open \"%s\" ... Not found!\n", "Contents/MacOS/Navicat Premium");
  202. } else {
  203. throw ARL::SystemError(__FILE__, __LINE__, errno, "open failed.");
  204. }
  205. }
  206. libcc_path = szInstallPath + "/Contents/Frameworks/libcc-premium.dylib";
  207. libcc_fd.TakeOver(open(libcc_path.c_str(), O_RDWR));
  208. if (libcc_fd.IsValid()) {
  209. printf("[+] Try to open \"%s\" ... Ok!\n", "Contents/Frameworks/libcc-premium.dylib");
  210. } else {
  211. if (errno == ENOENT) {
  212. printf("[-] Try to open \"%s\" ... Not found!\n", "Contents/Frameworks/libcc-premium.dylib");
  213. } else {
  214. throw ARL::SystemError(__FILE__, __LINE__, errno, "open failed.");
  215. }
  216. }
  217. //
  218. // try map "Contents/MacOS/Navicat Premium"
  219. // try map "Contents/Frameworks/libcc-premium.dylib"
  220. //
  221. if (main_fd.IsValid()) {
  222. main_stat.TakeOver(new struct stat());
  223. if (fstat(main_fd, main_stat) != 0) {
  224. throw ARL::SystemError(__FILE__, __LINE__, errno, "fstat failed.");
  225. }
  226. main_mmap.TakeOver(mmap(nullptr, main_stat->st_size, PROT_READ | PROT_WRITE, MAP_SHARED, main_fd, 0));
  227. if (main_mmap.IsValid() == false) {
  228. throw ARL::SystemError(__FILE__, __LINE__, errno, "mmap failed.");
  229. }
  230. main_interpreter.TakeOver(
  231. new nkg::X64ImageInterpreter(nkg::X64ImageInterpreter::Parse(main_mmap, main_stat->st_size))
  232. );
  233. lpSolution0.TakeOver(
  234. new nkg::PatchSolution0(*main_interpreter.Get())
  235. );
  236. lpSolution1.TakeOver(
  237. new nkg::PatchSolution1(*main_interpreter.Get())
  238. );
  239. lpSolution2.TakeOver(
  240. new nkg::PatchSolution2(*main_interpreter.Get())
  241. );
  242. }
  243. if (libcc_fd.IsValid()) {
  244. libcc_stat.TakeOver(new struct stat());
  245. if (fstat(libcc_fd, libcc_stat) != 0) {
  246. throw ARL::SystemError(__FILE__, __LINE__, errno, "fstat failed.");
  247. }
  248. libcc_mmap.TakeOver(mmap(nullptr, libcc_stat->st_size, PROT_READ | PROT_WRITE, MAP_SHARED, libcc_fd, 0));
  249. if (libcc_mmap.IsValid() == false) {
  250. throw ARL::SystemError(__FILE__, __LINE__, errno, "mmap failed.");
  251. }
  252. libcc_interpreter.TakeOver(
  253. new nkg::X64ImageInterpreter(nkg::X64ImageInterpreter::Parse(libcc_mmap, libcc_stat->st_size))
  254. );
  255. lpSolution3.TakeOver(
  256. new nkg::PatchSolution3(*libcc_interpreter.Get())
  257. );
  258. }
  259. puts("");
  260. if (lpSolution0.IsValid() && lpSolution0->FindPatchOffset() == false) {
  261. lpSolution0.Release();
  262. }
  263. if (lpSolution1.IsValid() && lpSolution1->FindPatchOffset() == false) {
  264. lpSolution1.Release();
  265. }
  266. if (lpSolution2.IsValid() && lpSolution2->FindPatchOffset() == false) {
  267. lpSolution2.Release();
  268. }
  269. if (lpSolution3.IsValid() && lpSolution3->FindPatchOffset() == false) {
  270. lpSolution3.Release();
  271. }
  272. if (int Ver0, Ver1, Ver2; sscanf(GetNavicatVersion(szInstallPath).c_str(), "%d.%d.%d", &Ver0, &Ver1, &Ver2) == 3) {
  273. printf("\n");
  274. printf("[*] Your Navicat version: %d.%d.%d\n", Ver0, Ver1, Ver2);
  275. printf("\n");
  276. //
  277. // Begin strategies by different Navicat versions
  278. //
  279. if (Ver0 < 12) { // ver < 12.0.0
  280. throw ARL::SystemError(__FILE__, __LINE__, errno, "Unsupported version of Navicat.");
  281. } else if (Ver0 == 12 && Ver1 == 0 && Ver2 < 24) { // ver < 12.0.24
  282. printf("[*] Your Navicat version is < 12.0.24. So there would be nothing patched.\n");
  283. printf(" Just use `openssl` to generate `RegPrivateKey.pem` and `rpk` file:\n");
  284. printf(" openssl genrsa -out RegPrivateKey.pem 2048\n");
  285. printf(" openssl rsa -in RegPrivateKey.pem -pubout -out rpk\n");
  286. printf(" and replace `%s/Contents/Resources/rpk` with the `rpk` file you just generated.\n", szInstallPath.c_str());
  287. printf("\n");
  288. return 0;
  289. } else if (Ver0 == 12 && (Ver1 == 0 || (Ver1 == 1 && Ver2 < 14))) { // 12.0.24 <= ver && ver < 12.1.14
  290. // In this case, Solution0 must be applied
  291. if (lpSolution0.IsValid() == false) {
  292. puts("[-] Patch abort. None of PatchSolutions will be applied.");
  293. puts(" Are you sure your Navicat has not been patched/modified before?");
  294. return -1;
  295. }
  296. } else if (Ver0 == 12 && Ver1 == 1 && Ver2 == 14) { // ver == 12.1.14
  297. // In this case, Solution0 and Solution1 must be applied
  298. if ((lpSolution0.IsValid() && lpSolution1.IsValid()) == false) {
  299. puts("[-] Patch abort. None of PatchSolutions will be applied.");
  300. puts(" Are you sure your Navicat has not been patched/modified before?");
  301. return -1;
  302. }
  303. } else if (Ver0 == 12) { // ver == 12.x.x
  304. // In this case, Solution0 and Solution2 must be applied
  305. if ((lpSolution0.IsValid() && lpSolution2.IsValid()) == false) {
  306. puts("[-] Patch abort. None of PatchSolutions will be applied.");
  307. puts(" Are you sure your Navicat has not been patched/modified before?");
  308. return -1;
  309. }
  310. } else { // ver == 15.x.x
  311. // In this case, Solution3 must be applied
  312. if (lpSolution3.IsValid() == false) {
  313. puts("[-] Patch abort. None of PatchSolutions will be applied.");
  314. puts(" Are you sure your Navicat has not been patched/modified before?");
  315. return -1;
  316. }
  317. }
  318. //
  319. // End strategies by different Navicat versions
  320. //
  321. } else {
  322. throw ARL::SystemError(__FILE__, __LINE__, errno, "Failed to get version of Navicat.");
  323. }
  324. //
  325. // Make sure that there is one patch solution at least existing.
  326. //
  327. if (lpSolution0.IsValid() == false && lpSolution1.IsValid() == false && lpSolution2.IsValid() == false && lpSolution3.IsValid() == false) {
  328. throw ARL::Exception(__FILE__, __LINE__, "No patch applied. Patch abort!")
  329. .PushHint("Are you sure your Navicat has not been patched/modified before?");
  330. }
  331. LoadKey(Cipher, szKeyFilePath, lpSolution0, lpSolution1, lpSolution2, lpSolution3);
  332. if (bDryrun == false) {
  333. //
  334. // Save private key if not given
  335. //
  336. if (szKeyFilePath.empty()) {
  337. Cipher.ExportKeyToFile<nkg::RSAKeyType::PrivateKey, nkg::RSAKeyFormat::PEM>("RegPrivateKey.pem");
  338. }
  339. //
  340. // Making patch. No way to go back here :-)
  341. //
  342. if (lpSolution0.IsValid()) {
  343. lpSolution0->MakePatch(Cipher);
  344. }
  345. if (lpSolution1.IsValid()) {
  346. lpSolution1->MakePatch(Cipher);
  347. }
  348. if (lpSolution2.IsValid()) {
  349. lpSolution2->MakePatch(Cipher);
  350. }
  351. if (lpSolution3.IsValid()) {
  352. lpSolution3->MakePatch(Cipher);
  353. }
  354. if (lpSolution0.IsValid()) {
  355. puts("[+] PatchSolution0 has been applied.");
  356. }
  357. if (lpSolution1.IsValid()) {
  358. puts("[+] PatchSolution1 has been applied.");
  359. }
  360. if (lpSolution2.IsValid()) {
  361. puts("[+] PatchSolution2 has been applied.");
  362. }
  363. if (lpSolution3.IsValid()) {
  364. puts("[+] PatchSolution3 has been applied.");
  365. }
  366. if (szKeyFilePath.empty()) {
  367. printf("[*] New RSA-2048 private key has been saved to\n");
  368. printf(" %s/RegPrivateKey.pem\n", nkg::Misc::FsCurrentWorkingDirectory().c_str());
  369. printf("\n");
  370. }
  371. puts("");
  372. puts("**************************************************************");
  373. puts("* Patch has been done successfully. Have fun and enjoy~~ *");
  374. puts("* DO NOT FORGET TO SIGN NAVICAT BY YOUR CERTIFICATE!!! *");
  375. puts("**************************************************************");
  376. } else {
  377. puts("**************************************************************");
  378. puts("* DRY-RUN MODE ENABLE! *");
  379. puts("* NO PATCH WILL BE APPLIED! *");
  380. puts("**************************************************************");
  381. }
  382. return 0;
  383. } catch (ARL::Exception& e) {
  384. printf("[-] %s:%zu ->\n", e.ExceptionFile(), e.ExceptionLine());
  385. printf(" %s\n", e.ExceptionMessage());
  386. if (e.HasErrorCode()) {
  387. printf(" %s (0x%zx)\n", e.ErrorString(), e.ErrorCode());
  388. }
  389. for (const auto& Hint : e.Hints()) {
  390. printf(" Hints: %s\n", Hint.c_str());
  391. }
  392. return -1;
  393. } catch (std::exception& e) {
  394. printf("[-] %s\n", e.what());
  395. return -1;
  396. }
  397. }
  398. }