Эх сурвалжийг харах

Add PatchSolution4 for Navicat Modeler 3

Double Sine 6 жил өмнө
parent
commit
70f49762cc

+ 4 - 0
navicat-patcher/CapstoneDisassembler.cpp

@@ -86,6 +86,10 @@ namespace nkg {
         }
     }
 
+    const char* CapstoneEngine::GetRegisterName(unsigned int reg_id) const noexcept {
+        return cs_reg_name(Get(), reg_id);
+    }
+
     [[nodiscard]]
     CapstoneDisassembler CapstoneEngine::CreateDisassembler() const {
         return CapstoneDisassembler(*this);

+ 2 - 0
navicat-patcher/CapstoneDisassembler.hpp

@@ -51,6 +51,8 @@ namespace nkg {
 
         void Option(cs_opt_type Type, cs_opt_value Value);
 
+        const char* GetRegisterName(unsigned int reg_id) const noexcept;
+
         [[nodiscard]]
         CapstoneDisassembler CreateDisassembler() const;
     };

+ 37 - 0
navicat-patcher/ExceptionKeystone.hpp

@@ -0,0 +1,37 @@
+#pragma once
+#include <Exception.hpp>
+#include <keystone/keystone.h>
+
+namespace nkg {
+
+    class KeystoneError final : public Exception {
+    private:
+
+        ks_err _ErrorCode;
+        std::xstring _ErrorString;
+
+    public:
+
+        KeystoneError(PCTSTR SourceFile, SIZE_T SourceLine, ks_err KeystoneErrorCode, PCTSTR CustomMessage) noexcept :
+            Exception(SourceFile, SourceLine, CustomMessage),
+            _ErrorCode(KeystoneErrorCode),
+            _ErrorString(std::xstring_extension{}, ks_strerror(KeystoneErrorCode), CP_UTF8) {}
+
+        [[nodiscard]]
+        virtual bool HasErrorCode() const noexcept override {
+            return true;
+        }
+
+        [[nodiscard]]
+        virtual ULONG_PTR ErrorCode() const noexcept override {
+            return _ErrorCode;
+        }
+
+        [[nodiscard]]
+        virtual PCTSTR ErrorString() const noexcept override {
+            return _ErrorString.c_str();
+        }
+    };
+
+}
+

+ 44 - 0
navicat-patcher/KeystoneAssembler.cpp

@@ -0,0 +1,44 @@
+#include "KeystoneAssembler.hpp"
+
+#undef NKG_CURRENT_SOURCE_FILE
+#undef NKG_CURRENT_SOURCE_LINE
+#define NKG_CURRENT_SOURCE_FILE() TEXT(".\\navicat-patcher\\KeystoneAssembler.cpp")
+#define NKG_CURRENT_SOURCE_LINE() __LINE__
+
+namespace nkg {
+
+    KeystoneAssembler::KeystoneAssembler(const KeystoneEngine& Engine) noexcept :
+        _Engine(Engine) {}
+
+    [[nodiscard]]
+    std::vector<uint8_t> KeystoneAssembler::GenerateMachineCode(const char* AssemblyCode, uint64_t Address) const {
+        ResourceOwned   pbMachineCode(KeystoneMallocTraits{});
+        size_t          cbMachineCode = 0;
+        size_t          InstructionsProcessed = 0;
+
+        if (ks_asm(_Engine, AssemblyCode, Address, pbMachineCode.GetAddressOf(), &cbMachineCode, &InstructionsProcessed) != 0) {
+            throw KeystoneError(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), ks_errno(_Engine), TEXT("ks_asm failed."));
+        }
+
+        return std::vector<uint8_t>(pbMachineCode.Get(), pbMachineCode.Get() + cbMachineCode);
+    }
+
+    KeystoneEngine::KeystoneEngine(ks_arch ArchType, ks_mode Mode) {
+        auto err = ks_open(ArchType, Mode, GetAddressOf());
+        if (err != KS_ERR_OK) {
+            throw KeystoneError(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), err, TEXT("ks_open failed."));
+        }
+    }
+
+    void KeystoneEngine::Option(ks_opt_type Type, ks_opt_value Value) {
+        auto err = ks_option(Get(), Type, Value);
+        if (err != KS_ERR_OK) {
+            throw KeystoneError(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), err, TEXT("ks_option failed."));
+        }
+    }
+
+    KeystoneAssembler KeystoneEngine::CreateAssembler() const {
+        return KeystoneAssembler(*this);
+    }
+}
+

+ 39 - 0
navicat-patcher/KeystoneAssembler.hpp

@@ -0,0 +1,39 @@
+#pragma once
+#include "ExceptionKeystone.hpp"
+#include <ResourceOwned.hpp>
+#include "ResourceTraitsKeystone.hpp"
+#include <vector>
+
+namespace nkg {
+
+    class KeystoneEngine;
+
+    class KeystoneAssembler {
+        friend class KeystoneEngine;
+    private:
+
+        const KeystoneEngine& _Engine;
+
+        KeystoneAssembler(const KeystoneEngine& Engine) noexcept;
+
+    public:
+
+        [[nodiscard]]
+        std::vector<uint8_t> GenerateMachineCode(const char* AssemblyCode, uint64_t Address = 0) const;
+
+    };
+
+    class KeystoneEngine : private ResourceOwned<KeystoneHandleTraits> {
+        friend class KeystoneAssembler;
+    public:
+
+        KeystoneEngine(ks_arch ArchType, ks_mode Mode);
+
+        void Option(ks_opt_type Type, ks_opt_value Value);
+
+        [[nodiscard]]
+        KeystoneAssembler CreateAssembler() const;
+    };
+
+}
+

+ 150 - 0
navicat-patcher/PatchSolution4-amd64.cpp

@@ -0,0 +1,150 @@
+#include "PatchSolutions.hpp"
+#include <xstring.hpp>
+
+#undef NKG_CURRENT_SOURCE_FILE
+#undef NKG_CURRENT_SOURCE_LINE
+#define NKG_CURRENT_SOURCE_FILE() TEXT(".\\navicat-patcher\\PatchSolution4-amd64.cpp")
+#define NKG_CURRENT_SOURCE_LINE() __LINE__
+
+namespace nkg {
+
+    PatchSolution4::PatchSolution4(const ImageInterpreter& Image) :
+        _Image(Image),
+        _DisassemblyEngine(CS_ARCH_X86, CS_MODE_64),
+        _AssemblyEngine(KS_ARCH_X86, KS_MODE_64),
+        _pbPatchMachineCode(nullptr),
+        _pbPatchNewPublicKey(nullptr) { _DisassemblyEngine.Option(CS_OPT_DETAIL, CS_OPT_ON); }
+
+    PatchSolution4::PatchSolution4(const ImageInterpreter* Image) :
+        _Image(*Image),
+        _DisassemblyEngine(CS_ARCH_X86, CS_MODE_64),
+        _AssemblyEngine(KS_ARCH_X86, KS_MODE_64),
+        _pbPatchMachineCode(nullptr),
+        _pbPatchNewPublicKey(nullptr) { _DisassemblyEngine.Option(CS_OPT_DETAIL, CS_OPT_ON); }
+
+    bool PatchSolution4::FindPatchOffset() noexcept {
+        try {
+            _pbPatchMachineCode = _Image.SearchSection<uint8_t*>(".text", [](const uint8_t* p) {
+                __try {
+                    return
+                        p[0] == 0x48 && p[1] == 0x8d &&                     // prefix of "lea       rcx, [rbp+5Fh+var_38]"
+                        p[4] == 0x48 && p[5] == 0x83 &&                     // prefix of "cmp       [rbp+5Fh+var_20], 10h"
+                        p[9] == 0x48 && p[10] == 0x0f && p[11] == 0x43 &&   // prefix of "cmovnb    rcx, [rbp+5Fh+var_38]"
+                        p[14] == 0x48 && p[15] == 0x8d &&                   // prefix of "lea       rax, [rbp+5Fh+var_58]"
+                        p[18] == 0x48 && p[19] == 0x83 &&                   // prefix of "cmp       [rbp+5Fh+var_40], 10h"
+                        p[23] == 0x48 && p[24] == 0x0f && p[25] == 0x43 &&  // prefix of "cmovnb    rax, [rbp+5Fh+var_58]"
+                        p[28] == 0x44 && p[29] == 0x0f && p[30] == 0xb6 &&  // prefix of "movzx     r8d, byte ptr [rax+rdi]"
+                        p[33] == 0x44 && p[34] == 0x02 &&                   // prefix of "add       r8b, [rcx+rdi]"
+                        p[37] == 0xba &&                                    // prefix of "mov       edx, 1"
+                        p[42] == 0x48 && p[43] == 0x8b &&                   // prefix of "mov       rcx, rbx"
+                        p[45] == 0xe8;                                      // prefix of "call      sub_1806E65F0"
+                } __except (EXCEPTION_EXECUTE_HANDLER) {
+                    return false;
+                }
+            });
+
+            auto RegisterIndex = X86_REG_INVALID;
+            auto Disassembler = _DisassemblyEngine.CreateDisassembler();
+            Disassembler.SetContext({ _pbPatchMachineCode, 45, _Image.PointerToVa(_pbPatchMachineCode) });
+
+            if (Disassembler.Next() && Disassembler.Next() && Disassembler.Next() && Disassembler.Next() && Disassembler.Next() && Disassembler.Next() && Disassembler.Next()) {
+                auto lpInsn = Disassembler.GetInstruction();
+                if (_stricmp(lpInsn->mnemonic, "movzx") == 0 && 
+                    lpInsn->detail->x86.op_count == 2 &&
+                    lpInsn->detail->x86.operands[0].type == X86_OP_REG &&
+                    lpInsn->detail->x86.operands[1].type == X86_OP_MEM && 
+                    lpInsn->detail->x86.operands[1].size == 1 &&
+                    lpInsn->detail->x86.operands[1].mem.scale == 1) 
+                {
+                    RegisterIndex = lpInsn->detail->x86.operands[1].mem.index;
+                } else {
+                    throw nkg::Exception(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), TEXT("Unexpected machine code."));
+                }
+            } else {
+                throw nkg::Exception(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), TEXT("Disassemble failed."));
+            }
+
+            if (Disassembler.Next() && Disassembler.Next() && Disassembler.Next()) {
+                auto lpInsn = Disassembler.GetInstruction();
+
+                //
+                // The previous instruction of "call sub_1806E65F0" should set RCX register.
+                //
+                if (_stricmp(lpInsn->mnemonic, "mov") != 0 || 
+                    lpInsn->detail->x86.op_count < 1 ||
+                    lpInsn->detail->x86.operands[0].type != X86_OP_REG || 
+                    lpInsn->detail->x86.operands[0].reg != X86_REG_RCX) 
+                {
+                    throw nkg::Exception(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), TEXT("Unexpected machine code."));
+                }
+            } else {
+                throw nkg::Exception(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), TEXT("Disassemble failed."));
+            }
+
+            for (size_t i = 0; i < _Image.NumberOfSections(); ++i) {
+                auto lpSectionHeader = _Image.ImageSectionHeader(i);
+                if (lpSectionHeader->SizeOfRawData > lpSectionHeader->Misc.VirtualSize) {
+                    auto cbReserved = 
+                        lpSectionHeader->SizeOfRawData - 
+                        lpSectionHeader->Misc.VirtualSize;
+
+                    if (cbReserved >= 0x188) {
+                        _pbPatchNewPublicKey = _Image.ImageSectionView<uint8_t*>(lpSectionHeader, lpSectionHeader->Misc.VirtualSize);
+
+                        auto Assembler = _AssemblyEngine.CreateAssembler();
+                        _NewMachineCode = Assembler.GenerateMachineCode(
+                            std::xstring::format(
+                                TEXT(
+                                    "lea rax, qword ptr[0x%.16llx];"
+                                    "mov r8b, byte ptr[rax + %hs];"
+                                    "mov edx, 1;"
+                                ),
+                                _Image.PointerToVa(_pbPatchNewPublicKey),
+                                _DisassemblyEngine.GetRegisterName(RegisterIndex)
+                            ).explicit_string().c_str(),
+                            _Image.PointerToVa(_pbPatchMachineCode)
+                        );
+
+                        // >>>>>>>>>>>> .text:00000001819B02C0 48 8D 4D 27       lea     rcx, [rbp + 5Fh + var_38]
+                        //              .text:00000001819B02C4 48 83 7D 3F 10    cmp[rbp + 5Fh + var_20], 10h
+                        //  42 BYTES    .text:00000001819B02C9 48 0F 43 4D 27    cmovnb  rcx, [rbp + 5Fh + var_38]
+                        //              .text:00000001819B02CE 48 8D 45 07       lea     rax, [rbp + 5Fh + var_58]
+                        //  THESE CODE  .text:00000001819B02D2 48 83 7D 1F 10    cmp[rbp + 5Fh + var_40], 10h
+                        //  WILL BE     .text:00000001819B02D7 48 0F 43 45 07    cmovnb  rax, [rbp + 5Fh + var_58]
+                        //  REPLACED    .text:00000001819B02DC 44 0F B6 04 38    movzx   r8d, byte ptr[rax + rdi]
+                        //              .text:00000001819B02E1 44 02 04 39       add     r8b, [rcx + rdi]
+                        // <<<<<<<<<<<< .text:00000001819B02E5 BA 01 00 00 00    mov     edx, 1
+                        //              .text:00000001819B02EA 48 8B CB          mov     rcx, rbx
+                        //              .text:00000001819B02ED E8 FE 62 D3 FE    call    sub_1806E65F0
+                        while (_NewMachineCode.size() < 42) {
+                            _NewMachineCode.emplace_back(0x90);  // padding with "nop"
+                        }
+
+                        if (_NewMachineCode.size() != 42) {
+                            throw nkg::Exception(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), TEXT("Something unexpected happens."));
+                        }
+
+                        LOG_SUCCESS(0, "PatchSolution4 ...... Ready to apply");
+                        LOG_HINT(4, "Machine code patch VA  = 0x%zx", _Image.PointerToVa(_pbPatchMachineCode));
+                        LOG_HINT(4, "New public key VA      = 0x%zx", _Image.PointerToVa(_pbPatchNewPublicKey));
+                        LOG_HINT(4, "New public key offset  = 0x%zx", _Image.PointerToFileOffset(_pbPatchNewPublicKey));
+
+                        return true;
+                    }
+                }
+            }
+
+            throw nkg::Exception(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), TEXT("No space to store public key."));
+        } catch (nkg::Exception&) {
+            _pbPatchMachineCode = nullptr;
+            _pbPatchNewPublicKey = nullptr;
+            _NewMachineCode.clear();
+
+            LOG_FAILURE(0, "PatchSolution4 ...... Omitted");
+
+            return false;
+        }
+    }
+
+}
+

+ 72 - 0
navicat-patcher/PatchSolution4-generic.cpp

@@ -0,0 +1,72 @@
+#include "PatchSolutions.hpp"
+
+#undef NKG_CURRENT_SOURCE_FILE
+#undef NKG_CURRENT_SOURCE_LINE
+#define NKG_CURRENT_SOURCE_FILE() TEXT(".\\navicat-patcher\\PatchSolution4-generic.cpp")
+#define NKG_CURRENT_SOURCE_LINE() __LINE__
+
+namespace nkg {
+
+    bool PatchSolution4::CheckKey(const RSACipher& Cipher) const noexcept {
+        auto szPublicKey = Cipher.ExportKeyString<RSAKeyType::PublicKey, RSAKeyFormat::PEM>();
+
+        for (auto pos = szPublicKey.find("-----BEGIN PUBLIC KEY-----"); pos != std::string::npos; pos = szPublicKey.find("-----BEGIN PUBLIC KEY-----", pos)) {
+            szPublicKey.erase(pos, literal_length("-----BEGIN PUBLIC KEY-----"));
+        }
+
+        for (auto pos = szPublicKey.find("-----END PUBLIC KEY-----"); pos != std::string::npos; pos = szPublicKey.find("-----END PUBLIC KEY-----", pos)) {
+            szPublicKey.erase(pos, literal_length("-----END PUBLIC KEY-----"));
+        }
+
+        for (auto pos = szPublicKey.find("\n"); pos != std::string::npos; pos = szPublicKey.find("\n", pos)) {
+            szPublicKey.erase(pos, literal_length("\n"));
+        }
+
+        return szPublicKey.length() == 0x188;
+    }
+
+    void PatchSolution4::MakePatch(const RSACipher& Cipher) const {
+        if (_pbPatchMachineCode == nullptr || _pbPatchNewPublicKey == nullptr || _NewMachineCode.empty()) {
+            throw Exception(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), TEXT("PatchSolution4 has not been ready yet."));
+        }
+
+        auto szPublicKey = Cipher.ExportKeyString<RSAKeyType::PublicKey, RSAKeyFormat::PEM>();
+
+        for (auto pos = szPublicKey.find("-----BEGIN PUBLIC KEY-----"); pos != std::string::npos; pos = szPublicKey.find("-----BEGIN PUBLIC KEY-----", pos)) {
+            szPublicKey.erase(pos, literal_length("-----BEGIN PUBLIC KEY-----"));
+        }
+
+        for (auto pos = szPublicKey.find("-----END PUBLIC KEY-----"); pos != std::string::npos; pos = szPublicKey.find("-----END PUBLIC KEY-----", pos)) {
+            szPublicKey.erase(pos, literal_length("-----END PUBLIC KEY-----"));
+        }
+
+        for (auto pos = szPublicKey.find("\n"); pos != std::string::npos; pos = szPublicKey.find("\n", pos)) {
+            szPublicKey.erase(pos, literal_length("\n"));
+        }
+
+        _putts(TEXT("*******************************************************"));
+        _putts(TEXT("*                   PatchSolution4                    *"));
+        _putts(TEXT("*******************************************************"));
+
+        LOG_HINT(0, "Previous:");
+        PrintMemory(_pbPatchMachineCode, _NewMachineCode.size(), _Image.ImageBase());
+
+        memcpy(_pbPatchMachineCode, _NewMachineCode.data(), _NewMachineCode.size());
+
+        LOG_HINT(0, "After:");
+        PrintMemory(_pbPatchMachineCode, _NewMachineCode.size(), _Image.ImageBase());
+
+        _putts(TEXT(""));
+
+        LOG_HINT(0, "Previous:");
+        PrintMemory(_pbPatchNewPublicKey, szPublicKey.size(), _Image.ImageBase());
+
+        memcpy(_pbPatchNewPublicKey, szPublicKey.data(), szPublicKey.size());
+
+        LOG_HINT(0, "After:");
+        PrintMemory(_pbPatchNewPublicKey, szPublicKey.size(), _Image.ImageBase());
+
+        _putts(TEXT(""));
+    }
+}
+

+ 145 - 0
navicat-patcher/PatchSolution4-i386.cpp

@@ -0,0 +1,145 @@
+#include "PatchSolutions.hpp"
+#include <xstring.hpp>
+
+#undef NKG_CURRENT_SOURCE_FILE
+#undef NKG_CURRENT_SOURCE_LINE
+#define NKG_CURRENT_SOURCE_FILE() TEXT(".\\navicat-patcher\\PatchSolution4-i386.cpp")
+#define NKG_CURRENT_SOURCE_LINE() __LINE__
+
+namespace nkg {
+
+    PatchSolution4::PatchSolution4(const ImageInterpreter& Image) :
+        _Image(Image),
+        _DisassemblyEngine(CS_ARCH_X86, CS_MODE_32),
+        _AssemblyEngine(KS_ARCH_X86, KS_MODE_32),
+        _pbPatchMachineCode(nullptr),
+        _pbPatchNewPublicKey(nullptr) { _DisassemblyEngine.Option(CS_OPT_DETAIL, CS_OPT_ON); }
+
+    PatchSolution4::PatchSolution4(const ImageInterpreter* Image) :
+        _Image(*Image),
+        _DisassemblyEngine(CS_ARCH_X86, CS_MODE_32),
+        _AssemblyEngine(KS_ARCH_X86, KS_MODE_32),
+        _pbPatchMachineCode(nullptr),
+        _pbPatchNewPublicKey(nullptr) { _DisassemblyEngine.Option(CS_OPT_DETAIL, CS_OPT_ON); }
+
+    bool PatchSolution4::FindPatchOffset() noexcept {
+        try {
+            _pbPatchMachineCode = _Image.SearchSection<uint8_t*>(".text", [](const uint8_t* p) {
+                __try {
+                    return
+                        p[0] == 0x83 &&                     // prefix of "cmp     [ebp+var_30], 10h"
+                        p[4] == 0x8d &&                     // prefix of "lea     ecx, [ebp+Dst]"
+                        p[7] == 0x8d &&                     // prefix of "lea     eax, [ebp+Memory]"
+                        p[10] == 0x0f && p[11] == 0x43 &&   // prefix of "cmovnb  ecx, [ebp+Dst]"
+                        p[14] == 0x83 &&                    // prefix of "cmp     [ebp+var_18], 10h"
+                        p[18] == 0x0f && p[19] == 0x43 &&   // prefix of "cmovnb  eax, [ebp+Memory]"
+                        p[22] == 0x8a &&                    // prefix of "mov     dl, [eax+ebx]"
+                        p[25] == 0x02;                      // prefix of "add     dl, [ecx+ebx]"
+                    //  p[28]
+                } __except (EXCEPTION_EXECUTE_HANDLER) {
+                    return false;
+                }
+            });
+
+            auto RegisterIndex = X86_REG_INVALID;
+            auto RegisterFinalValue = X86_REG_INVALID;
+            auto Disassembler = _DisassemblyEngine.CreateDisassembler();
+            Disassembler.SetContext({ _pbPatchMachineCode, 28, _Image.PointerToVa(_pbPatchMachineCode) });
+
+            if (Disassembler.Next() && Disassembler.Next() && Disassembler.Next() && Disassembler.Next() && Disassembler.Next() && Disassembler.Next() && Disassembler.Next()) {
+                auto lpInsn = Disassembler.GetInstruction();
+                if (_stricmp(lpInsn->mnemonic, "mov") == 0 && 
+                    lpInsn->detail->x86.op_count == 2 &&
+                    lpInsn->detail->x86.operands[0].type == X86_OP_REG &&
+                    lpInsn->detail->x86.operands[1].type == X86_OP_MEM && 
+                    lpInsn->detail->x86.operands[1].size == 1 &&
+                    lpInsn->detail->x86.operands[1].mem.scale == 1) 
+                {
+                    RegisterIndex = lpInsn->detail->x86.operands[1].mem.index;
+                } else {
+                    throw nkg::Exception(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), TEXT("Unexpected machine code."));
+                }
+            } else {
+                throw nkg::Exception(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), TEXT("Disassemble failed."));
+            }
+
+            if (Disassembler.Next()) {
+                auto lpInsn = Disassembler.GetInstruction();
+                if (_stricmp(lpInsn->mnemonic, "add") == 0 && 
+                    lpInsn->detail->x86.op_count >= 1 &&
+                    lpInsn->detail->x86.operands[0].type == X86_OP_REG &&
+                    lpInsn->detail->x86.operands[0].size == 1) 
+                {
+                    RegisterFinalValue = lpInsn->detail->x86.operands[0].reg;
+                } else {
+                    throw nkg::Exception(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), TEXT("Unexpected machine code."));
+                }
+            } else {
+                throw nkg::Exception(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), TEXT("Disassemble failed."));
+            }
+
+            for (size_t i = 0; i < _Image.NumberOfSections(); ++i) {
+                auto lpSectionHeader = _Image.ImageSectionHeader(i);
+                if (lpSectionHeader->SizeOfRawData > lpSectionHeader->Misc.VirtualSize) {
+                    auto cbReserved = 
+                        lpSectionHeader->SizeOfRawData - 
+                        lpSectionHeader->Misc.VirtualSize;
+
+                    if (cbReserved >= 0x188) {
+                        _pbPatchNewPublicKey = _Image.ImageSectionView<uint8_t*>(lpSectionHeader, lpSectionHeader->Misc.VirtualSize);
+
+                        auto Assembler = _AssemblyEngine.CreateAssembler();
+                        _NewMachineCode = Assembler.GenerateMachineCode(
+                            std::xstring::format(
+                                TEXT(
+                                    "call +5;"
+                                    "pop eax;"
+                                    "add eax, 0x%.8x;"
+                                    "mov %hs, byte ptr [eax + %hs];"
+                                ),
+                                _Image.PointerToVa(_pbPatchNewPublicKey) - (_Image.PointerToVa(_pbPatchMachineCode) + 5),
+                                _DisassemblyEngine.GetRegisterName(RegisterFinalValue),
+                                _DisassemblyEngine.GetRegisterName(RegisterIndex)
+                            ).explicit_string().c_str()
+                        );
+
+                        // >>>>>>>>>>>> .text:113FE4A0 83 7D D0 10  cmp     [ebp+var_30], 10h
+                        //   28 BYTES   .text:113FE4A4 8D 4D BC     lea     ecx, [ebp+Dst]
+                        //              .text:113FE4A7 8D 45 D4     lea     eax, [ebp+Memory]
+                        //  THESE CODE  .text:113FE4AA 0F 43 4D BC  cmovnb  ecx, [ebp+Dst]
+                        //  WILL BE     .text:113FE4AE 83 7D E8 10  cmp     [ebp+var_18], 10h
+                        //  REPLACED    .text:113FE4B2 0F 43 45 D4  cmovnb  eax, [ebp+Memory]
+                        //              .text:113FE4B6 8A 14 18     mov     dl, [eax+ebx]
+                        // <<<<<<<<<<<< .text:113FE4B9 02 14 19     add     dl, [ecx+ebx]
+                        while (_NewMachineCode.size() < 28) {
+                            _NewMachineCode.emplace_back(0x90);     // padding with "nop"
+                        }
+
+                        if (_NewMachineCode.size() != 28) {
+                            throw nkg::Exception(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), TEXT("Something unexpected happens."));
+                        }
+
+                        LOG_SUCCESS(0, "PatchSolution4 ...... Ready to apply");
+                        LOG_HINT(4, "Machine code patch VA  = 0x%zx", _Image.PointerToVa(_pbPatchMachineCode));
+                        LOG_HINT(4, "New public key VA      = 0x%zx", _Image.PointerToVa(_pbPatchNewPublicKey));
+                        LOG_HINT(4, "New public key offset  = 0x%zx", _Image.PointerToFileOffset(_pbPatchNewPublicKey));
+
+                        return true;
+                    }
+                }
+            }
+
+            throw nkg::Exception(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), TEXT("No space to store public key."));
+        } catch (nkg::Exception&) {
+            _pbPatchMachineCode = nullptr;
+            _pbPatchNewPublicKey = nullptr;
+            _NewMachineCode.clear();
+
+            LOG_FAILURE(0, "PatchSolution4 ...... Omitted");
+
+            return false;
+        }
+    }
+
+}
+

+ 33 - 0
navicat-patcher/PatchSolutions.hpp

@@ -2,6 +2,7 @@
 #include <RSACipher.hpp>
 #include "ImageInterpreter.hpp"
 #include "CapstoneDisassembler.hpp"
+#include "KeystoneAssembler.hpp"
 #include "Misc.hpp"
 
 namespace nkg {
@@ -198,4 +199,36 @@ namespace nkg {
 
         virtual void MakePatch(const RSACipher& Cipher) const override;
     };
+
+    //
+    // PatchSolution3 will replace the RSA public key stored in libcc.dll
+    // For Navicat Data Modeler 3
+    //
+    class PatchSolution4 : public PatchSolution {
+    private:
+
+        static const uint8_t KeywordA[0x188];
+        static const uint8_t KeywordB[0x188];
+
+        const ImageInterpreter& _Image;
+        CapstoneEngine          _DisassemblyEngine;
+        KeystoneEngine          _AssemblyEngine;
+        uint8_t*                _pbPatchMachineCode;
+        uint8_t*                _pbPatchNewPublicKey;
+        std::vector<uint8_t>    _NewMachineCode;
+
+    public:
+
+        PatchSolution4(const ImageInterpreter& Image);
+
+        PatchSolution4(const ImageInterpreter* lpImage);
+
+        [[nodiscard]]
+        virtual bool FindPatchOffset() noexcept override;
+
+        [[nodiscard]]
+        virtual bool CheckKey(const RSACipher& Cipher) const noexcept override;
+
+        virtual void MakePatch(const RSACipher& Cipher) const override;
+    };
 }

+ 33 - 0
navicat-patcher/ResourceTraitsKeystone.hpp

@@ -0,0 +1,33 @@
+#pragma once
+#include <keystone/keystone.h>
+
+struct KeystoneHandleTraits {
+    using HandleType = ks_engine*;
+
+    static inline const HandleType InvalidValue = nullptr;
+
+    [[nodiscard]]
+    static bool IsValid(const HandleType& Handle) noexcept {
+        return Handle != InvalidValue;
+    }
+
+    static void Releasor(const HandleType& Handle) noexcept {
+        ks_close(Handle);
+    }
+};
+
+struct KeystoneMallocTraits {
+    using HandleType = uint8_t*;
+
+    static inline const HandleType InvalidValue = nullptr;
+
+    [[nodiscard]]
+    static bool IsValid(const HandleType& Handle) noexcept {
+        return Handle != InvalidValue;
+    }
+
+    static void Releasor(const HandleType& Handle) noexcept {
+        ks_free(Handle);
+    }
+};
+

+ 20 - 7
navicat-patcher/_tmain.cpp

@@ -131,7 +131,8 @@ static void LoadKey(
     nkg::PatchSolution* pSolution0,
     nkg::PatchSolution* pSolution1,
     nkg::PatchSolution* pSolution2,
-    nkg::PatchSolution* pSolution3) 
+    nkg::PatchSolution* pSolution3,
+    nkg::PatchSolution* pSolution4) 
 {
     if (KeyFilePath.empty() == false) {
         LOG_HINT(0, "Import RSA-2048 key from %s", KeyFilePath.c_str());
@@ -141,7 +142,8 @@ static void LoadKey(
         if (pSolution0 && !pSolution0->CheckKey(Cipher) ||
             pSolution1 && !pSolution1->CheckKey(Cipher) ||
             pSolution2 && !pSolution2->CheckKey(Cipher) ||
-            pSolution3 && !pSolution3->CheckKey(Cipher)) 
+            pSolution3 && !pSolution3->CheckKey(Cipher) ||
+            pSolution4 && !pSolution4->CheckKey(Cipher))
         {
             throw nkg::Exception(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), TEXT("The RSA private key you provide cannot be used."));
         }
@@ -153,7 +155,8 @@ static void LoadKey(
         } while (pSolution0 && !pSolution0->CheckKey(Cipher) ||
                  pSolution1 && !pSolution1->CheckKey(Cipher) ||
                  pSolution2 && !pSolution2->CheckKey(Cipher) ||
-                 pSolution3 && !pSolution3->CheckKey(Cipher));   // re-generate RSA key if one of 'CheckKey's return false
+                 pSolution3 && !pSolution3->CheckKey(Cipher) ||
+                 pSolution4 && !pSolution4->CheckKey(Cipher));   // re-generate RSA key if one of 'CheckKey's return false
     }
 
     LOG_HINT(0, "Your RSA public key:\n%hs", Cipher.ExportKeyString<nkg::RSAKeyType::PublicKey, nkg::RSAKeyFormat::PEM>().c_str());
@@ -207,6 +210,7 @@ int _tmain(int argc, PTSTR argv[]) {
             ResourceOwned lpSolution1(CppObjectTraits<nkg::PatchSolution>{});
             ResourceOwned lpSolution2(CppObjectTraits<nkg::PatchSolution>{});
             ResourceOwned lpSolution3(CppObjectTraits<nkg::PatchSolution>{});
+            ResourceOwned lpSolution4(CppObjectTraits<nkg::PatchSolution>{});
 
             //
             // Open main application
@@ -316,6 +320,7 @@ int _tmain(int argc, PTSTR argv[]) {
                 lpSolution1.TakeOver(new nkg::PatchSolution1(lpLibccDllInterpreter));
                 lpSolution2.TakeOver(new nkg::PatchSolution2(lpLibccDllInterpreter));
                 lpSolution3.TakeOver(new nkg::PatchSolution3(lpLibccDllInterpreter));
+                lpSolution4.TakeOver(new nkg::PatchSolution4(lpLibccDllInterpreter));
             }
 
             //
@@ -338,6 +343,10 @@ int _tmain(int argc, PTSTR argv[]) {
                 lpSolution3.Release();
             }
 
+            if (lpSolution4.IsValid() && lpSolution4->FindPatchOffset() == false) {
+                lpSolution4.Release();
+            }
+
             _putts(TEXT(""));
 
             //
@@ -345,7 +354,7 @@ int _tmain(int argc, PTSTR argv[]) {
             //
             SelectPatchSolutions(lpSolution0, lpSolution1, lpSolution2, lpSolution3);
 
-            if (lpSolution0.IsValid() == false && lpSolution1.IsValid() == false && lpSolution2.IsValid() == false && lpSolution3.IsValid() == false) {
+            if (lpSolution0.IsValid() == false && lpSolution1.IsValid() == false && lpSolution2.IsValid() == false && lpSolution3.IsValid() == false && lpSolution4.IsValid() == false) {
                 throw nkg::Exception(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), TEXT("No patch applied. Patch abort!"))
                     .AddHint(TEXT("Are you sure your Navicat has not been patched/modified before?"));
             }
@@ -359,14 +368,14 @@ int _tmain(int argc, PTSTR argv[]) {
                 NavicatBackupDetect(MainExePath);
             }
 
-            if (lpSolution1.IsValid() || lpSolution2.IsValid() || lpSolution3.IsValid()) {
+            if (lpSolution1.IsValid() || lpSolution2.IsValid() || lpSolution3.IsValid() || lpSolution4.IsValid()) {
                 NavicatBackupDetect(LibccDllPath);
             }
 
             //
             // Loading key
             //
-            LoadKey(Cipher, RsaPrivateKeyPath, lpSolution0, lpSolution1, lpSolution2, lpSolution3);
+            LoadKey(Cipher, RsaPrivateKeyPath, lpSolution0, lpSolution1, lpSolution2, lpSolution3, lpSolution4);
 
             if (bDryRun == false) {
                 //
@@ -383,7 +392,7 @@ int _tmain(int argc, PTSTR argv[]) {
                     NavicatBackupMake(MainExePath);
                 }
 
-                if (lpSolution1.IsValid() || lpSolution2.IsValid() || lpSolution3.IsValid()) {
+                if (lpSolution1.IsValid() || lpSolution2.IsValid() || lpSolution3.IsValid() || lpSolution4.IsValid()) {
                     NavicatBackupMake(LibccDllPath);
                 }
 
@@ -405,6 +414,10 @@ int _tmain(int argc, PTSTR argv[]) {
                 if (lpSolution3.IsValid()) {
                     lpSolution3->MakePatch(Cipher);
                 }
+
+                if (lpSolution4.IsValid()) {
+                    lpSolution4->MakePatch(Cipher);
+                }
                 
                 if (RsaPrivateKeyPath.empty()) {
                     LOG_HINT(

+ 13 - 0
navicat-patcher/navicat-patcher.vcxproj

@@ -168,6 +168,7 @@
   <ItemGroup>
     <ClCompile Include="CapstoneDisassembler.cpp" />
     <ClCompile Include="ImageInterpreter.cpp" />
+    <ClCompile Include="KeystoneAssembler.cpp" />
     <ClCompile Include="Misc.cpp" />
     <ClCompile Include="PatchSolution0.cpp" />
     <ClCompile Include="PatchSolution1.cpp" />
@@ -189,16 +190,28 @@
       <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
       <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
     </ClCompile>
+    <ClCompile Include="PatchSolution4-amd64.cpp">
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
+    </ClCompile>
+    <ClCompile Include="PatchSolution4-generic.cpp" />
+    <ClCompile Include="PatchSolution4-i386.cpp">
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
+    </ClCompile>
     <ClCompile Include="_tmain.cpp" />
   </ItemGroup>
   <ItemGroup>
     <ClInclude Include="CapstoneDisassembler.hpp" />
     <ClInclude Include="ExceptionCapstone.hpp" />
+    <ClInclude Include="ExceptionKeystone.hpp" />
     <ClInclude Include="ImageInterpreter.hpp" />
+    <ClInclude Include="KeystoneAssembler.hpp" />
     <ClInclude Include="Misc.hpp" />
     <ClInclude Include="NavicatCrypto.hpp" />
     <ClInclude Include="PatchSolutions.hpp" />
     <ClInclude Include="ResourceTraitsCapstone.hpp" />
+    <ClInclude Include="ResourceTraitsKeystone.hpp" />
   </ItemGroup>
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
   <ImportGroup Label="ExtensionTargets">

+ 21 - 0
navicat-patcher/navicat-patcher.vcxproj.filters

@@ -51,6 +51,18 @@
     <ClCompile Include="CapstoneDisassembler.cpp">
       <Filter>源文件</Filter>
     </ClCompile>
+    <ClCompile Include="PatchSolution4-generic.cpp">
+      <Filter>源文件</Filter>
+    </ClCompile>
+    <ClCompile Include="PatchSolution4-amd64.cpp">
+      <Filter>源文件</Filter>
+    </ClCompile>
+    <ClCompile Include="KeystoneAssembler.cpp">
+      <Filter>源文件</Filter>
+    </ClCompile>
+    <ClCompile Include="PatchSolution4-i386.cpp">
+      <Filter>源文件</Filter>
+    </ClCompile>
   </ItemGroup>
   <ItemGroup>
     <ClInclude Include="NavicatCrypto.hpp">
@@ -74,5 +86,14 @@
     <ClInclude Include="Misc.hpp">
       <Filter>头文件</Filter>
     </ClInclude>
+    <ClInclude Include="KeystoneAssembler.hpp">
+      <Filter>头文件</Filter>
+    </ClInclude>
+    <ClInclude Include="ExceptionKeystone.hpp">
+      <Filter>头文件</Filter>
+    </ClInclude>
+    <ClInclude Include="ResourceTraitsKeystone.hpp">
+      <Filter>头文件</Filter>
+    </ClInclude>
   </ItemGroup>
 </Project>