commit 9017721a5c083146fedd3d8c451b1014167fc337 Author: Double Sine Date: Mon Feb 14 15:47:41 2022 +0800 upload codebase Signed-off-by: Double Sine diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..628a3c3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.idea +.vscode +cmake-build-* diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..4b9a5ef --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,84 @@ +cmake_minimum_required(VERSION 3.21) +project(navicat_keygen) + +set(CMAKE_CXX_STANDARD 17) + +include(FetchContent) +set(KEYSTONE_BUILD_STATIC_RUNTIME ON CACHE BOOL "" FORCE) +set(BUILD_LIBS_ONLY ON CACHE BOOL "" FORCE) +set(UNICORN_ARCH "x86" CACHE STRING "" FORCE) +set(UNICORN_INSTALL OFF CACHE BOOL "" FORCE) +set(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE) +set(UNICORN_BUILD_SAMPLES OFF CACHE BOOL "" FORCE) +FetchContent_Declare( + keystone + GIT_REPOSITORY "https://github.com/keystone-engine/keystone.git" + GIT_TAG "1475885daa7e566c064ae9754706e1a0ba24be3b" +) +FetchContent_Declare( + unicorn + URL "https://github.com/unicorn-engine/unicorn/archive/refs/tags/1.0.3.tar.gz" + URL_HASH "SHA256=64fba177dec64baf3f11c046fbb70e91483e029793ec6a3e43b028ef14dc0d65" +) +FetchContent_MakeAvailable(keystone unicorn) + +set( + NKG_COMMON_SOURCE + ./common/exception.hpp + ./common/exceptions/index_exception.hpp + ./common/exceptions/key_exception.hpp + ./common/exceptions/operation_canceled_exception.hpp + ./common/exceptions/unix_exception.hpp + ./common/resource_wrapper.hpp + ./common/resource_traits/cxx_object_traits.hpp + ./common/resource_traits/cxx_dynamic_array_traits.hpp + ./common/resource_traits/keystone/keystone_alloc.hpp + ./common/resource_traits/keystone/keystone_handle.hpp + ./common/resource_traits/openssl/bignum.hpp + ./common/resource_traits/openssl/bio.hpp + ./common/resource_traits/openssl/bio_chain.hpp + ./common/resource_traits/openssl/rsa.hpp + ./common/resource_traits/unicorn/unicorn_handle.hpp + ./common/resource_traits/unicorn/unicorn_alloc.hpp + ./common/resource_traits/unix_os/file_descriptor.hpp + ./common/resource_traits/unix_os/map_view.hpp + ./common/rsa_cipher.hpp + ./common/rsa_cipher.cpp +) + +set( + NKG_KEYGEN_SOURCE + ./navicat-keygen/base32_rfc4648.hpp + ./navicat-keygen/base32_rfc4648.cpp + ./navicat-keygen/base64_rfc4648.hpp + ./navicat-keygen/base64_rfc4648.cpp + ./navicat-keygen/navicat_serial_generator.hpp + ./navicat-keygen/navicat_serial_generator.cpp + ./navicat-keygen/CollectInformation.cpp + ./navicat-keygen/GenerateLicense.cpp + ./navicat-keygen/main.cpp +) + +set( + NKG_PATCHER_SOURCE + ./navicat-patcher/amd64_emulator.hpp + ./navicat-patcher/amd64_emulator.cpp + ./navicat-patcher/keystone_assembler.hpp + ./navicat-patcher/keystone_assembler.cpp + ./navicat-patcher/elf64_interpreter.hpp + ./navicat-patcher/elf64_interpreter.cpp + ./navicat-patcher/patch_solution.hpp + ./navicat-patcher/patch_solution_since.hpp + ./navicat-patcher/patch_solution_since_16.0.7.0.hpp + ./navicat-patcher/patch_solution_since_16.0.7.0.cpp + ./navicat-patcher/memory_utility.hpp + ./navicat-patcher/main.cpp +) + +add_executable(navicat-keygen ${NKG_COMMON_SOURCE} ${NKG_KEYGEN_SOURCE}) +target_include_directories(navicat-keygen PRIVATE ./common) +target_link_libraries(navicat-keygen fmt crypto) + +add_executable(navicat-patcher ${NKG_COMMON_SOURCE} ${NKG_PATCHER_SOURCE}) +target_include_directories(navicat-patcher PRIVATE ./common ${keystone_SOURCE_DIR}/include) +target_link_libraries(navicat-patcher fmt crypto keystone unicorn pthread) diff --git a/common/exception.hpp b/common/exception.hpp new file mode 100644 index 0000000..4d002d5 --- /dev/null +++ b/common/exception.hpp @@ -0,0 +1,90 @@ +#pragma once +#include +#include +#include +#include + +namespace nkg { + + class exception : public std::exception { + private: + int m_source_line; + std::string m_source_file; + std::string m_custom_message; + std::vector m_hints; + + public: + [[noreturn]] + static void trap_then_terminate() { +#if _MSC_VER + __debugbreak(); +#elif defined(__GNUC__) || defined(__GNUG__) || defined(__clang__) + __builtin_trap(); + +#else +#error "exception.hpp: unknown compiler is detected." +#endif + std::terminate(); + } + + exception(std::string_view file, int line, std::string_view message) noexcept : + std::exception(), m_source_line(line), m_source_file(file), m_custom_message(message) {} + + exception(const exception&) noexcept = default; + exception(exception&&) noexcept = default; + + exception& operator=(const exception&) noexcept = default; + exception& operator=(exception&&) noexcept = default; + + [[nodiscard]] + int source_line() const noexcept { + return m_source_line; + } + + [[nodiscard]] + const std::string& source_file() const noexcept { + return m_source_file; + } + + [[nodiscard]] + const std::string& custom_message() const noexcept { + return m_custom_message; + } + + exception&& push_hint(std::string_view hint) noexcept { + m_hints.emplace_back(hint); + return std::move(*this); + } + + exception&& pop_hint() noexcept { + m_hints.pop_back(); + return std::move(*this); + } + + const std::vector& hints() const noexcept { + return m_hints; + } + + virtual const char* what() const noexcept override { + return m_custom_message.c_str(); + } + + [[nodiscard]] + virtual bool error_code_exists() const noexcept { + return false; + } + + [[nodiscard]] + virtual intptr_t error_code() const noexcept { + trap_then_terminate(); + } + + [[nodiscard]] + virtual const std::string& error_string() const noexcept { + trap_then_terminate(); + } + + virtual ~exception() override = default; + }; + +} diff --git a/common/exceptions/index_exception.hpp b/common/exceptions/index_exception.hpp new file mode 100644 index 0000000..f49d1c1 --- /dev/null +++ b/common/exceptions/index_exception.hpp @@ -0,0 +1,12 @@ +#pragma once +#include "../exception.hpp" + +namespace nkg::exceptions { + + class index_exception : public ::nkg::exception { + public: + index_exception(std::string_view file, int line, std::string_view message) noexcept : + ::nkg::exception(file, line, message) {} + }; + +} diff --git a/common/exceptions/key_exception.hpp b/common/exceptions/key_exception.hpp new file mode 100644 index 0000000..6809748 --- /dev/null +++ b/common/exceptions/key_exception.hpp @@ -0,0 +1,12 @@ +#pragma once +#include "../exception.hpp" + +namespace nkg::exceptions { + + class key_exception : public ::nkg::exception { + public: + key_exception(std::string_view file, int line, std::string_view message) noexcept : + ::nkg::exception(file, line, message) {} + }; + +} diff --git a/common/exceptions/operation_canceled_exception.hpp b/common/exceptions/operation_canceled_exception.hpp new file mode 100644 index 0000000..5a1053c --- /dev/null +++ b/common/exceptions/operation_canceled_exception.hpp @@ -0,0 +1,12 @@ +#pragma once +#include "../exception.hpp" + +namespace nkg::exceptions { + + class operation_canceled_exception : public ::nkg::exception { + public: + operation_canceled_exception(std::string_view file, int line, std::string_view message) noexcept : + ::nkg::exception(file, line, message) {} + }; + +} diff --git a/common/exceptions/unix_exception.hpp b/common/exceptions/unix_exception.hpp new file mode 100644 index 0000000..6d678ae --- /dev/null +++ b/common/exceptions/unix_exception.hpp @@ -0,0 +1,36 @@ +#pragma once +#include +#include +#include "../exception.hpp" + +namespace nkg::exceptions { + + class unix_exception : public ::nkg::exception { + public: + using error_code_t = decltype(errno); + + private: + error_code_t m_error_code; + std::string m_error_string; + + public: + unix_exception(std::string_view file, int line, error_code_t unix_errno, std::string_view message) noexcept : + ::nkg::exception(file, line, message), m_error_code(unix_errno), m_error_string(strerror(unix_errno)) {} + + [[nodiscard]] + virtual bool error_code_exists() const noexcept override { + return true; + } + + [[nodiscard]] + virtual intptr_t error_code() const noexcept override { + return m_error_code; + } + + [[nodiscard]] + virtual const std::string& error_string() const noexcept override { + return m_error_string; + } + }; + +} diff --git a/common/resource_traits/cxx_dynamic_array_traits.hpp b/common/resource_traits/cxx_dynamic_array_traits.hpp new file mode 100644 index 0000000..15132ed --- /dev/null +++ b/common/resource_traits/cxx_dynamic_array_traits.hpp @@ -0,0 +1,21 @@ +#pragma once + +namespace nkg::resource_traits { + + template + struct cxx_dynamic_array_traits { + using handle_t = element_t*; + + static constexpr handle_t invalid_value = nullptr; + + [[nodiscard]] + static bool is_valid(const handle_t& handle) noexcept { + return handle != invalid_value; + } + + static void release(const handle_t& handle) { + delete[] handle; + } + }; + +} diff --git a/common/resource_traits/cxx_object_traits.hpp b/common/resource_traits/cxx_object_traits.hpp new file mode 100644 index 0000000..d0ddee1 --- /dev/null +++ b/common/resource_traits/cxx_object_traits.hpp @@ -0,0 +1,21 @@ +#pragma once + +namespace nkg::resource_traits { + + template + struct cxx_object_traits { + using handle_t = object_t*; + + static constexpr handle_t invalid_value = nullptr; + + [[nodiscard]] + static bool is_valid(const handle_t& handle) noexcept { + return handle != invalid_value; + } + + static void release(const handle_t& handle) { + delete handle; + } + }; + +} diff --git a/common/resource_traits/keystone/keystone_alloc.hpp b/common/resource_traits/keystone/keystone_alloc.hpp new file mode 100644 index 0000000..5f9a70a --- /dev/null +++ b/common/resource_traits/keystone/keystone_alloc.hpp @@ -0,0 +1,20 @@ +#pragma once +#include + +namespace nkg::resource_traits::keystone { + + struct keystone_alloc { + using handle_t = unsigned char*; + + static constexpr handle_t invalid_value = nullptr; + + [[nodiscard]] + static bool is_valid(const handle_t& handle) noexcept { + return handle != invalid_value; + } + + static void release(const handle_t& handle) noexcept { + ks_free(handle); + } + }; +} diff --git a/common/resource_traits/keystone/keystone_handle.hpp b/common/resource_traits/keystone/keystone_handle.hpp new file mode 100644 index 0000000..d114982 --- /dev/null +++ b/common/resource_traits/keystone/keystone_handle.hpp @@ -0,0 +1,21 @@ +#pragma once +#include + +namespace nkg::resource_traits::keystone { + + struct keystone_handle { + using handle_t = ks_engine*; + + static constexpr handle_t invalid_value = nullptr; + + [[nodiscard]] + static bool is_valid(const handle_t& handle) noexcept { + return handle != invalid_value; + } + + static void release(const handle_t& handle) { + ks_close(handle); + } + }; + +} diff --git a/common/resource_traits/openssl/bignum.hpp b/common/resource_traits/openssl/bignum.hpp new file mode 100644 index 0000000..17584b8 --- /dev/null +++ b/common/resource_traits/openssl/bignum.hpp @@ -0,0 +1,21 @@ +#pragma once +#include + +namespace nkg::resource_traits::openssl { + + struct bignum { + using handle_t = BIGNUM*; + + static constexpr handle_t invalid_value = nullptr; + + [[nodiscard]] + static bool is_valid(const handle_t& handle) noexcept { + return handle != invalid_value; + } + + static void release(const handle_t& handle) noexcept { + BN_free(handle); + } + }; + +} diff --git a/common/resource_traits/openssl/bio.hpp b/common/resource_traits/openssl/bio.hpp new file mode 100644 index 0000000..fa4e387 --- /dev/null +++ b/common/resource_traits/openssl/bio.hpp @@ -0,0 +1,21 @@ +#pragma once +#include + +namespace nkg::resource_traits::openssl { + + struct bio { + using handle_t = BIO*; + + static constexpr handle_t invalid_value = nullptr; + + [[nodiscard]] + static bool is_valid(const handle_t& handle) noexcept { + return handle != invalid_value; + } + + static void release(const handle_t& handle) noexcept { + BIO_free(handle); + } + }; + +} diff --git a/common/resource_traits/openssl/bio_chain.hpp b/common/resource_traits/openssl/bio_chain.hpp new file mode 100644 index 0000000..cf321a5 --- /dev/null +++ b/common/resource_traits/openssl/bio_chain.hpp @@ -0,0 +1,21 @@ +#pragma once +#include + +namespace nkg::resource_traits::openssl { + + struct bio_chain { + using handle_t = BIO*; + + static constexpr handle_t invalid_value = nullptr; + + [[nodiscard]] + static bool is_valid(const handle_t& handle) noexcept { + return handle != invalid_value; + } + + static void release(const handle_t& handle) noexcept { + BIO_free_all(handle); + } + }; + +} diff --git a/common/resource_traits/openssl/rsa.hpp b/common/resource_traits/openssl/rsa.hpp new file mode 100644 index 0000000..3b9f49a --- /dev/null +++ b/common/resource_traits/openssl/rsa.hpp @@ -0,0 +1,21 @@ +#pragma once +#include + +namespace nkg::resource_traits::openssl { + + struct rsa { + using handle_t = RSA*; + + static constexpr handle_t invalid_value = nullptr; + + [[nodiscard]] + static bool is_valid(const handle_t& handle) noexcept { + return handle != invalid_value; + } + + static void release(const handle_t& handle) noexcept { + RSA_free(handle); + } + }; + +} diff --git a/common/resource_traits/unicorn/unicorn_alloc.hpp b/common/resource_traits/unicorn/unicorn_alloc.hpp new file mode 100644 index 0000000..5dfeb95 --- /dev/null +++ b/common/resource_traits/unicorn/unicorn_alloc.hpp @@ -0,0 +1,21 @@ +#pragma once +#include + +namespace nkg::resource_traits::unicorn { + + struct unicorn_alloc { + using handle_t = void*; + + static constexpr handle_t invalid_value = nullptr; + + [[nodiscard]] + static bool is_valid(const handle_t& handle) noexcept { + return handle != invalid_value; + } + + static void release(const handle_t& handle) { + uc_free(handle); + } + }; + +} diff --git a/common/resource_traits/unicorn/unicorn_handle.hpp b/common/resource_traits/unicorn/unicorn_handle.hpp new file mode 100644 index 0000000..67fc254 --- /dev/null +++ b/common/resource_traits/unicorn/unicorn_handle.hpp @@ -0,0 +1,21 @@ +#pragma once +#include + +namespace nkg::resource_traits::unicorn { + + struct unicorn_handle { + using handle_t = uc_engine*; + + static constexpr handle_t invalid_value = nullptr; + + [[nodiscard]] + static bool is_valid(const handle_t& handle) noexcept { + return handle != invalid_value; + } + + static void release(const handle_t& handle) { + uc_close(handle); + } + }; + +} diff --git a/common/resource_traits/unix_os/file_descriptor.hpp b/common/resource_traits/unix_os/file_descriptor.hpp new file mode 100644 index 0000000..41e0b2a --- /dev/null +++ b/common/resource_traits/unix_os/file_descriptor.hpp @@ -0,0 +1,21 @@ +#pragma once +#include + +namespace nkg::resource_traits::unix_os { + + struct file_descriptor { + using handle_t = int; + + static constexpr handle_t invalid_value = -1; + + [[nodiscard]] + static bool is_valid(const handle_t& handle) noexcept { + return handle != invalid_value; + } + + static void release(const handle_t& handle) { + close(handle); + } + }; + +} diff --git a/common/resource_traits/unix_os/map_view.hpp b/common/resource_traits/unix_os/map_view.hpp new file mode 100644 index 0000000..30f855f --- /dev/null +++ b/common/resource_traits/unix_os/map_view.hpp @@ -0,0 +1,19 @@ +#pragma once +#include +#include +#include + +namespace nkg::resource_traits::unix_os { + + struct map_view { + using handle_t = void*; + + static inline const handle_t invalid_value = MAP_FAILED; + + [[nodiscard]] + static bool is_valid(const handle_t& handle) noexcept { + return handle != invalid_value; + } + }; + +} diff --git a/common/resource_wrapper.hpp b/common/resource_wrapper.hpp new file mode 100644 index 0000000..8462ce0 --- /dev/null +++ b/common/resource_wrapper.hpp @@ -0,0 +1,238 @@ +#pragma once +#include +#include + +namespace nkg { + + template + class resource_wrapper { + public: + using handle_t = typename resource_traits_t::handle_t; + static_assert(std::is_trivial_v && std::is_standard_layout_v, "`resource_wrapper` requires a handle with POD type."); + + private: + handle_t m_handle; + releaser_t m_releaser; + + public: + template + resource_wrapper(releaser_arg_t&& releaser) noexcept : + m_handle(resource_traits_t::invalid_value), + m_releaser(std::forward(releaser)) {} + + template + resource_wrapper(const handle_t& handle, releaser_arg_t&& releaser) noexcept : + m_handle(handle), + m_releaser(std::forward(releaser)) {} + + template + resource_wrapper(resource_traits_t, releaser_arg_t&& releaser) noexcept : + m_handle(resource_traits_t::invalid_value), + m_releaser(std::forward(releaser)) {} + + template + resource_wrapper(resource_traits_t, const handle_t& handle, releaser_arg_t&& releaser) noexcept : + m_handle(handle), + m_releaser(std::forward(releaser)) {} + + // + // `resource_wrapper` does not allow copy-construct + // + resource_wrapper(const resource_wrapper& other) = delete; + + // + // `resource_wrapper` allows move-construct. + // + resource_wrapper(resource_wrapper&& other) noexcept : + m_handle(other.m_handle), + m_releaser(std::move(other.m_releaser)) + { + other.m_handle = resource_traits_t::invalid_value; + } + + // + // `resource_wrapper` does not allow to copy. + // + resource_wrapper& operator=(const resource_wrapper& other) = delete; + + // + // `resource_wrapper` allows to move. + // + resource_wrapper& operator=(resource_wrapper&& other) noexcept { + if (this != std::addressof(other)) { + m_handle = other.m_handle; + m_releaser = std::move(other.m_releaser); + other.m_handle = resource_traits_t::invalid_value; + } + return *this; + } + + template, ptr_t> = nullptr> + [[nodiscard]] + ptr_t operator->() const noexcept { + return m_handle; + } + + template + [[nodiscard]] + as_t as() const noexcept { + return reinterpret_cast(m_handle); + } + + [[nodiscard]] + bool is_valid() const noexcept { + return resource_traits_t::is_valid(m_handle); + } + + [[nodiscard]] + const handle_t& get() const noexcept { + return m_handle; + } + + template + [[nodiscard]] + as_t* unsafe_addressof() noexcept { + return reinterpret_cast(std::addressof(m_handle)); + } + + void set(const handle_t& handle) { + if (is_valid()) { + m_releaser(m_handle); + } + m_handle = handle; + } + + void discard() noexcept { + m_handle = resource_traits_t::invalid_value; + } + + [[nodiscard]] + handle_t transfer() noexcept { + handle_t t = m_handle; + m_handle = resource_traits_t::invalid_value; + return t; + } + + void release() { + if (is_valid()) { + m_releaser(m_handle); + m_handle = resource_traits_t::invalid_value; + } + } + + ~resource_wrapper() { + release(); + } + }; + + template + class resource_wrapper { + public: + using handle_t = typename resource_traits_t::handle_t; + static_assert(std::is_trivial_v&& std::is_standard_layout_v, "`resource_wrapper` requires a handle with POD type."); + + private: + handle_t m_handle; + + public: + resource_wrapper() noexcept : + m_handle(resource_traits_t::invalid_value) {} + + resource_wrapper(const handle_t& handle) noexcept : + m_handle(handle) {} + + resource_wrapper(resource_traits_t) noexcept : + m_handle(resource_traits_t::invalid_value) {} + + resource_wrapper(resource_traits_t, const handle_t& handle) noexcept : + m_handle(handle) {} + + resource_wrapper(const resource_wrapper& other) = delete; + + resource_wrapper(resource_wrapper&& other) noexcept : + m_handle(other.m_handle) + { + other.m_handle = resource_traits_t::invalid_value; + } + + resource_wrapper& operator=(const resource_wrapper& other) = delete; + + resource_wrapper& operator=(resource_wrapper&& other) noexcept { + if (this != std::addressof(other)) { + m_handle = other.m_handle; + other.m_handle = resource_traits_t::invalid_value; + } + return *this; + } + + template, ptr_t> = nullptr> + [[nodiscard]] + ptr_t operator->() const noexcept { + return m_handle; + } + + template + [[nodiscard]] + as_t as() const noexcept { + return reinterpret_cast(m_handle); + } + + [[nodiscard]] + bool is_valid() const noexcept { + return resource_traits_t::is_valid(m_handle); + } + + [[nodiscard]] + const handle_t& get() const noexcept { + return m_handle; + } + + template + [[nodiscard]] + as_t* unsafe_addressof() noexcept { + return reinterpret_cast(std::addressof(m_handle)); + } + + void set(const handle_t& handle) { + if (is_valid()) { + resource_traits_t::release(m_handle); + } + m_handle = handle; + } + + void discard() noexcept { + m_handle = resource_traits_t::invalid_value; + } + + [[nodiscard]] + handle_t transfer() noexcept { + handle_t t = m_handle; + m_handle = resource_traits_t::invalid_value; + return t; + } + + void release() { + if (is_valid()) { + resource_traits_t::release(m_handle); + m_handle = resource_traits_t::invalid_value; + } + } + + ~resource_wrapper() { + release(); + } + }; + + template + resource_wrapper(resource_traits_t) -> + resource_wrapper; + + template + resource_wrapper(resource_traits_t, arg_t&&) -> + resource_wrapper>, typename resource_traits_t::handle_t>, std::remove_reference_t, void>>; + + template + resource_wrapper(resource_traits_t, const handle_t&, releaser_t&&) -> + resource_wrapper>; + +} diff --git a/common/rsa_cipher.cpp b/common/rsa_cipher.cpp new file mode 100644 index 0000000..1fe4647 --- /dev/null +++ b/common/rsa_cipher.cpp @@ -0,0 +1,327 @@ +#include "rsa_cipher.hpp" +#include +#include + +#include "resource_traits/openssl/bio.hpp" +#include "resource_traits/openssl/bignum.hpp" + +#define NKG_CURRENT_SOURCE_FILE() u8".\\common\\rsa_cipher.cpp" +#define NKG_CURRENT_SOURCE_LINE() __LINE__ + +namespace nkg { + + RSA* rsa_cipher::_read_private_key_from_bio(BIO* p_bio) { + resource_wrapper new_rsa + { resource_traits::openssl::rsa{}, PEM_read_bio_RSAPrivateKey(p_bio, nullptr, nullptr, nullptr) }; + + if (new_rsa.is_valid()) { + return new_rsa.transfer(); + } else { + throw backend_error(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), u8"PEM_read_bio_RSAPrivateKey failed.") + .push_hint(u8"Are you sure that you DO provide a valid RSA private key file?"); + } + } + + RSA* rsa_cipher::_read_public_key_pem_from_bio(BIO* p_bio) { + resource_wrapper new_rsa + { resource_traits::openssl::rsa{}, PEM_read_bio_RSA_PUBKEY(p_bio, nullptr, nullptr, nullptr) }; + + if (new_rsa.is_valid()) { + return new_rsa.transfer(); + } else { + throw backend_error(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), u8"PEM_read_bio_RSA_PUBKEY failed.") + .push_hint(u8"Are you sure that you DO provide a valid RSA public key file with PEM format?"); + } + } + + RSA* rsa_cipher::_read_public_key_pkcs1_from_bio(BIO* p_bio) { + resource_wrapper new_rsa + { resource_traits::openssl::rsa{}, PEM_read_bio_RSAPublicKey(p_bio, nullptr, nullptr, nullptr) }; + + if (new_rsa.is_valid()) { + return new_rsa.transfer(); + } else { + throw backend_error(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), u8"PEM_read_bio_RSAPublicKey failed.") + .push_hint(u8"Are you sure that you DO provide a valid RSA public key file with PKCS1 format?"); + } + } + + void rsa_cipher::_write_private_key_to_bio(RSA* p_rsa, BIO* p_bio) { + auto r = PEM_write_bio_RSAPrivateKey(p_bio, p_rsa, nullptr, nullptr, 0, nullptr, nullptr); + if (r == 0) { + throw backend_error(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), u8"PEM_write_bio_RSAPrivateKey failed."); + }; + } + + void rsa_cipher::_write_public_key_pem_to_bio(RSA* p_rsa, BIO* p_bio) { + auto r = PEM_write_bio_RSA_PUBKEY(p_bio, p_rsa); + if (r == 0) { + throw backend_error(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), u8"PEM_write_bio_RSA_PUBKEY failed."); + } + } + + void rsa_cipher::_write_public_key_pkcs1_to_bio(RSA* p_rsa, BIO* p_bio) { + auto r = PEM_write_bio_RSAPublicKey(p_bio, p_rsa); + if (r == 0) { + throw backend_error(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), u8"PEM_write_bio_RSAPublicKey failed."); + } + } + + rsa_cipher::rsa_cipher() : m_rsa(RSA_new()) { + if (!m_rsa.is_valid()) { + throw backend_error(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), ERR_get_error(), u8"RSA_new failed."); + } + } + + [[nodiscard]] + size_t rsa_cipher::bits() const { +#if (OPENSSL_VERSION_NUMBER & 0xffff0000) == 0x10000000 // openssl 1.0.x + if (m_rsa->n) { + return BN_num_bits(m_rsa->n); + } else { + throw no_key_assigned_error(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), u8"RSA modulus has not been set."); + } +#elif (OPENSSL_VERSION_NUMBER & 0xffff0000) == 0x10100000 // openssl 1.1.x + return RSA_bits(m_rsa.get()); +#else +#error "rsa_cipher.cpp: uexpected OpenSSL version" +#endif + } + + void rsa_cipher::generate_key(int bits, unsigned int e) { + resource_wrapper bn_e{ resource_traits::openssl::bignum{}, BN_new() }; + + if (!bn_e.is_valid()) { + throw backend_error(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), ERR_get_error(), u8"BN_new failed."); + } + + if (BN_set_word(bn_e.get(), e) == 0) { + throw backend_error(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), u8"BN_set_word failed."); + } + + if (RSA_generate_key_ex(m_rsa.get(), bits, bn_e.get(), nullptr) == 0) { + throw backend_error(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), ERR_get_error(), u8"RSA_generate_key_ex failed."); + } + } + + void rsa_cipher::export_private_key_file(std::string_view file_path) const { + resource_wrapper bio_file + { resource_traits::openssl::bio{}, BIO_new_file(file_path.data(), "w")}; + + if (bio_file.is_valid()) { + _write_private_key_to_bio(m_rsa.get(), bio_file.get()); + } else { + throw backend_error(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), u8"BIO_new_file failed."); + } + } + + void rsa_cipher::export_public_key_file_pem(std::string_view file_path) const { + resource_wrapper bio_file + { resource_traits::openssl::bio{}, BIO_new_file(file_path.data(), "w")}; + + if (bio_file.is_valid()) { + _write_public_key_pem_to_bio(m_rsa.get(), bio_file.get()); + } else { + throw backend_error(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), u8"BIO_new_file failed."); + } + } + + void rsa_cipher::export_public_key_file_pkcs1(std::string_view file_path) const { + resource_wrapper bio_file + { resource_traits::openssl::bio{}, BIO_new_file(file_path.data(), "w")}; + + if (bio_file.is_valid()) { + _write_public_key_pkcs1_to_bio(m_rsa.get(), bio_file.get()); + } else { + throw backend_error(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), u8"BIO_new_file failed."); + } + } + + void rsa_cipher::import_private_key_file(std::string_view file_path) { + resource_wrapper bio_file + { resource_traits::openssl::bio{}, BIO_new_file(file_path.data(), "r") }; + + if (bio_file.is_valid()) { + m_rsa.set(_read_private_key_from_bio(bio_file.get())); + } else { + throw backend_error(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), u8"BIO_new_file failed."); + } + } + + void rsa_cipher::import_public_key_file_pem(std::string_view file_path) { + resource_wrapper bio_file + { resource_traits::openssl::bio{}, BIO_new_file(file_path.data(), "r") }; + + if (bio_file.is_valid()) { + m_rsa.set(_read_public_key_pem_from_bio(bio_file.get())); + } else { + throw backend_error(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), u8"BIO_new_file failed."); + } + } + + void rsa_cipher::import_public_key_file_pkcs1(std::string_view file_path) { + resource_wrapper bio_file + { resource_traits::openssl::bio{}, BIO_new_file(file_path.data(), "r") }; + + if (bio_file.is_valid()) { + m_rsa.set(_read_public_key_pkcs1_from_bio(bio_file.get())); + } else { + throw backend_error(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), u8"BIO_new_file failed."); + } + } + + [[nodiscard]] + std::string rsa_cipher::export_private_key_string() const { + resource_wrapper bio_memory{ resource_traits::openssl::bio{}, BIO_new(BIO_s_mem()) }; + + if (bio_memory.is_valid()) { + _write_private_key_to_bio(m_rsa.get(), bio_memory.get()); + + const char* pch = nullptr; + long lch = BIO_get_mem_data(bio_memory.get(), &pch); + + return std::string(pch, lch); + } else { + throw backend_error(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), u8"BIO_new failed."); + } + } + + [[nodiscard]] + std::string rsa_cipher::export_public_key_string_pem() const { + resource_wrapper bio_memory{ resource_traits::openssl::bio{}, BIO_new(BIO_s_mem()) }; + + if (bio_memory.is_valid()) { + _write_public_key_pem_to_bio(m_rsa.get(), bio_memory.get()); + + const char* pch = nullptr; + long lch = BIO_get_mem_data(bio_memory.get(), &pch); + + return std::string(pch, lch); + } else { + throw backend_error(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), u8"BIO_new failed."); + } + } + + [[nodiscard]] + std::string rsa_cipher::export_public_key_string_pkcs1() const { + resource_wrapper bio_memory{ resource_traits::openssl::bio{}, BIO_new(BIO_s_mem()) }; + + if (bio_memory.is_valid()) { + _write_public_key_pkcs1_to_bio(m_rsa.get(), bio_memory.get()); + + const char* pch = nullptr; + long lch = BIO_get_mem_data(bio_memory.get(), &pch); + + return std::string(pch, lch); + } else { + throw backend_error(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), u8"BIO_new failed."); + } + } + + void rsa_cipher::import_private_key_string(std::string_view key_string) { + resource_wrapper bio_memory{ resource_traits::openssl::bio{}, BIO_new(BIO_s_mem()) }; + + if (!bio_memory.is_valid()) { + throw backend_error(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), u8"BIO_new failed."); + } + + if (BIO_puts(bio_memory.get(), key_string.data()) <= 0) { + throw backend_error(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), u8"BIO_puts failed."); + } + + m_rsa.set(_read_private_key_from_bio(bio_memory.get())); + } + + void rsa_cipher::import_public_key_string_pem(std::string_view key_string) { + resource_wrapper bio_memory{ resource_traits::openssl::bio{}, BIO_new(BIO_s_mem()) }; + + if (!bio_memory.is_valid()) { + throw backend_error(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), u8"BIO_new failed."); + } + + if (BIO_puts(bio_memory.get(), key_string.data()) <= 0) { + throw backend_error(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), u8"BIO_puts failed."); + } + + m_rsa.set(_read_public_key_pem_from_bio(bio_memory.get())); + } + + void rsa_cipher::import_public_key_string_pkcs1(std::string_view key_string) { + resource_wrapper bio_memory{ resource_traits::openssl::bio{}, BIO_new(BIO_s_mem()) }; + + if (!bio_memory.is_valid()) { + throw backend_error(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), u8"BIO_new failed."); + } + + if (BIO_puts(bio_memory.get(), key_string.data()) <= 0) { + throw backend_error(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), u8"BIO_puts failed."); + } + + m_rsa.set(_read_public_key_pkcs1_from_bio(bio_memory.get())); + } + + size_t rsa_cipher::public_encrypt(const void* plaintext, size_t plaintext_size, void* ciphertext, int padding) const { + if (plaintext_size <= INT_MAX) { + int bytes_written = + RSA_public_encrypt(static_cast(plaintext_size), reinterpret_cast(plaintext), reinterpret_cast(ciphertext), m_rsa.get(), padding); + + if (bytes_written != -1) { + return bytes_written; + } else { + throw backend_error(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), ERR_get_error(), u8"RSA_public_encrypt failed."); + } + } else { + throw backend_error(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), u8"plaintext_size > INT_MAX"); + } + } + + size_t rsa_cipher::private_encrypt(const void* plaintext, size_t plaintext_size, void* ciphertext, int padding) const { + if (plaintext_size <= INT_MAX) { + int bytes_written = + RSA_private_encrypt(static_cast(plaintext_size), reinterpret_cast(plaintext), reinterpret_cast(ciphertext), m_rsa.get(), padding); + + if (bytes_written != -1) { + return bytes_written; + } else { + throw backend_error(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), ERR_get_error(), u8"RSA_public_encrypt failed."); + } + } else { + throw backend_error(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), u8"plaintext_size > INT_MAX"); + } + } + + size_t rsa_cipher::public_decrypt(const void* ciphertext, size_t ciphertext_size, void* plaintext, int padding) const { + if (ciphertext_size <= INT_MAX) { + int bytes_written = + RSA_public_decrypt(static_cast(ciphertext_size), reinterpret_cast(ciphertext), reinterpret_cast(plaintext), m_rsa.get(), padding); + + if (bytes_written != -1) { + return bytes_written; + } else { + throw backend_error(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), ERR_get_error(), u8"RSA_public_decrypt failed.") + .push_hint(u8"Are your sure you DO provide a correct public key?"); + } + } else { + throw backend_error(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), u8"ciphertext_size > INT_MAX"); + } + } + + size_t rsa_cipher::private_decrypt(const void* ciphertext, size_t ciphertext_size, void* plaintext, int padding) const { + if (ciphertext_size <= INT_MAX) { + int bytes_written = + RSA_private_decrypt(static_cast(ciphertext_size), reinterpret_cast(ciphertext), reinterpret_cast(plaintext), m_rsa.get(), padding); + + if (bytes_written != -1) { + return bytes_written; + } else { + throw backend_error(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), ERR_get_error(), u8"RSA_public_decrypt failed.") + .push_hint(u8"Are your sure you DO provide a correct private key?"); + } + } else { + throw backend_error(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), u8"ciphertext_size > INT_MAX"); + } + } +} + +#undef NKG_CURRENT_SOURCE_FILE +#undef NKG_CURRENT_SOURCE_LINE diff --git a/common/rsa_cipher.hpp b/common/rsa_cipher.hpp new file mode 100644 index 0000000..8f0c212 --- /dev/null +++ b/common/rsa_cipher.hpp @@ -0,0 +1,123 @@ +#pragma once +#include +#include + +#include +#include + +#include "resource_wrapper.hpp" +#include "resource_traits/openssl/rsa.hpp" + +#include "exception.hpp" + +#define NKG_CURRENT_SOURCE_FILE() u8".\\common\\rsa_cipher.hpp" +#define NKG_CURRENT_SOURCE_LINE() __LINE__ + +namespace nkg { + + class rsa_cipher { + public: + class no_key_assigned_error : public ::nkg::exception { + public: + no_key_assigned_error(std::string_view file, int line, std::string_view message) noexcept : + ::nkg::exception(file, line, message) {} + }; + + class backend_error : public ::nkg::exception { + public: + using error_code_t = decltype(ERR_get_error()); + + private: + std::optional m_error_code; + std::string m_error_string; + + public: + backend_error(std::string_view file, int line, std::string_view message) noexcept : + ::nkg::exception(file, line, message) {} + + backend_error(std::string_view file, int line, error_code_t openssl_errno, std::string_view message) noexcept : + ::nkg::exception(file, line, message), m_error_code(openssl_errno) {} + + [[nodiscard]] + virtual bool error_code_exists() const noexcept override { + return m_error_code.has_value(); + } + + [[nodiscard]] + virtual intptr_t error_code() const noexcept override { + if (error_code_exists()) { return m_error_code.value(); } else { trap_then_terminate(); } + } + + [[nodiscard]] + virtual const std::string& error_string() const noexcept override { + if (error_code_exists()) { return m_error_string; } else { trap_then_terminate(); } + } + }; + + private: + resource_wrapper m_rsa; + + [[nodiscard]] + static RSA* _read_private_key_from_bio(BIO* p_bio); + + [[nodiscard]] + static RSA* _read_public_key_pem_from_bio(BIO* p_bio); + + [[nodiscard]] + static RSA* _read_public_key_pkcs1_from_bio(BIO* p_bio); + + static void _write_private_key_to_bio(RSA* p_rsa, BIO* p_bio); + + static void _write_public_key_pem_to_bio(RSA* p_rsa, BIO* p_bio); + + static void _write_public_key_pkcs1_to_bio(RSA* p_rsa, BIO* p_bio); + + public: + + rsa_cipher(); + + [[nodiscard]] + size_t bits() const; + + void generate_key(int bits, unsigned int e = RSA_F4); + + void export_private_key_file(std::string_view file_path) const; + + void export_public_key_file_pem(std::string_view file_path) const; + + void export_public_key_file_pkcs1(std::string_view file_path) const; + + void import_private_key_file(std::string_view file_path); + + void import_public_key_file_pem(std::string_view file_path); + + void import_public_key_file_pkcs1(std::string_view file_path); + + [[nodiscard]] + std::string export_private_key_string() const; + + [[nodiscard]] + std::string export_public_key_string_pem() const; + + [[nodiscard]] + std::string export_public_key_string_pkcs1() const; + + void import_private_key_string(std::string_view key_string); + + void import_public_key_string_pem(std::string_view key_string); + + void import_public_key_string_pkcs1(std::string_view key_string); + + size_t public_encrypt(const void* plaintext, size_t plaintext_size, void* ciphertext, int padding) const; + + size_t private_encrypt(const void* plaintext, size_t plaintext_size, void* ciphertext, int padding) const; + + size_t public_decrypt(const void* ciphertext, size_t ciphertext_size, void* plaintext, int padding) const; + + size_t private_decrypt(const void* ciphertext, size_t ciphertext_size, void* plaintext, int padding) const; + }; + +} + +#undef NKG_CURRENT_SOURCE_FILE +#undef NKG_CURRENT_SOURCE_LINE diff --git a/navicat-keygen/CollectInformation.cpp b/navicat-keygen/CollectInformation.cpp new file mode 100644 index 0000000..fd788ef --- /dev/null +++ b/navicat-keygen/CollectInformation.cpp @@ -0,0 +1,132 @@ +#include +#include "navicat_serial_generator.hpp" +#include "exceptions/operation_canceled_exception.hpp" + +#define NKG_CURRENT_SOURCE_FILE() u8".\\navicat-keygen\\CollectInformation.cpp" +#define NKG_CURRENT_SOURCE_LINE() __LINE__ + +namespace nkg { + + [[nodiscard]] + static int read_int(int min_val, int max_val, std::string_view prompt, std::string_view error_msg) { + int val; + + for (std::string s;;) { + std::cout << prompt; + if (!std::getline(std::cin, s)) { + throw exceptions::operation_canceled_exception(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), u8"Operation is canceled by user."); + } + + if (s.empty()) + continue; + + try { + val = std::stoi(s, nullptr, 0); + if (min_val <= val && val <= max_val) { + return val; + } else { + throw std::invalid_argument("Out of range."); + } + } catch (std::invalid_argument&) { + std::cout << error_msg << std::endl; + } + } + } + + [[nodiscard]] + static int read_int(int min_val, int max_val, int default_val, std::string_view prompt, std::string_view error_msg) { + int val; + + for (std::string s;;) { + std::cout << prompt; + if (!std::getline(std::cin, s)) { + throw exceptions::operation_canceled_exception(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), u8"Operation is canceled by user."); + } + + if (s.empty()) { + return default_val; + } + + try { + val = std::stoi(s, nullptr, 0); + if (min_val <= val && val <= max_val) { + return val; + } else { + throw std::invalid_argument("Out of range."); + } + } catch (std::invalid_argument&) { + std::cout << error_msg << std::endl; + } + } + } + + [[nodiscard]] + navicat_serial_generator CollectInformationNormal() { + navicat_serial_generator sn_generator; + + std::cout << "[*] Select Navicat product:" << std::endl; + std::cout << " 0. DataModeler" << std::endl; + std::cout << " 1. Premium" << std::endl; + std::cout << " 2. MySQL" << std::endl; + std::cout << " 3. PostgreSQL" << std::endl; + std::cout << " 4. Oracle" << std::endl; + std::cout << " 5. SQLServer" << std::endl; + std::cout << " 6. SQLite" << std::endl; + std::cout << " 7. MariaDB" << std::endl; + std::cout << " 8. MongoDB" << std::endl; + std::cout << " 9. ReportViewer" << std::endl; + std::cout << std::endl; + sn_generator.set_software_type(static_cast(read_int(0, 9, "(Input index)> ", "Invalid index."))); + + std::cout << std::endl; + std::cout << "[*] Select product language:" << std::endl; + std::cout << " 0. English" << std::endl; + std::cout << " 1. Simplified Chinese" << std::endl; + std::cout << " 2. Traditional Chinese" << std::endl; + std::cout << " 3. Japanese" << std::endl; + std::cout << " 4. Polish" << std::endl; + std::cout << " 5. Spanish" << std::endl; + std::cout << " 6. French" << std::endl; + std::cout << " 7. German" << std::endl; + std::cout << " 8. Korean" << std::endl; + std::cout << " 9. Russian" << std::endl; + std::cout << " 10. Portuguese" << std::endl; + std::cout << std::endl; + sn_generator.set_software_language(static_cast(read_int(0, 10, "(Input index)> ", "Invalid index."))); + + std::cout << std::endl; + std::cout << "[*] Input major version number:" << std::endl; + sn_generator.set_software_version(read_int(11, 16, 16, "(range: 11 ~ 16, default: 16)> ", "Invalid number.")); + + std::cout << std::endl; + return sn_generator; + } + + [[nodiscard]] + navicat_serial_generator CollectInformationAdvanced() { + navicat_serial_generator sn_generator; + + std::cout << "[*] Navicat Product Signature:" << std::endl; + sn_generator.set_software_type(static_cast(read_int(0x00, 0xff, "(range: 0x00 ~ 0xFF)> ", "Invalid number."))); + + std::cout << std::endl; + std::cout << "[*] Navicat Language Signature 0:" << std::endl; + auto s1 = static_cast(read_int(0x00, 0xff, "(range: 0x00 ~ 0xFF)> ", "Invalid number.")); + + std::cout << std::endl; + std::cout << "[*] Navicat Language Signature 1:" << std::endl; + auto s2 = static_cast(read_int(0x00, 0xff, "(range: 0x00 ~ 0xFF)> ", "Invalid number.")); + + sn_generator.set_software_language(s1, s2); + + std::cout << std::endl; + std::cout << "[*] Input major version number:" << std::endl; + sn_generator.set_software_version(read_int(0, 15, 12, "(range: 0 ~ 15, default: 12)> ", "Invalid number.")); + + std::cout << std::endl; + return sn_generator; + } +} + +#undef NKG_CURRENT_SOURCE_FILE +#undef NKG_CURRENT_SOURCE_LINE diff --git a/navicat-keygen/GenerateLicense.cpp b/navicat-keygen/GenerateLicense.cpp new file mode 100644 index 0000000..89db14c --- /dev/null +++ b/navicat-keygen/GenerateLicense.cpp @@ -0,0 +1,211 @@ +#include +#include +#include +#include + +#include "exceptions/operation_canceled_exception.hpp" +#include "exceptions/unix_exception.hpp" + +#include "resource_wrapper.hpp" +#include "resource_traits/unix_os/file_descriptor.hpp" + +#include "rsa_cipher.hpp" +#include "navicat_serial_generator.hpp" +#include "base64_rfc4648.hpp" + +#include +#include +#include + +#define NKG_CURRENT_SOURCE_FILE() u8".\\navicat-keygen\\GenerateLicense.cpp" +#define NKG_CURRENT_SOURCE_LINE() __LINE__ + +namespace nkg { + + void GenerateLicenseText(const rsa_cipher& cipher, const navicat_serial_generator& sn_generator) { + std::string u8_username; + std::string u8_organization; + + std::string b64_request_code; + std::vector request_code; + std::string u8_request_info; + std::string u8_response_info; + std::vector response_code; + std::string b64_response_code; + + std::cout << "[*] Your name: "; + if (!std::getline(std::cin, u8_username)) { + throw exceptions::operation_canceled_exception(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), u8"Operation is canceled by user."); + } + + std::cout << "[*] Your organization: "; + if (!std::getline(std::cin, u8_organization)) { + throw exceptions::operation_canceled_exception(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), u8"Operation is canceled by user."); + } + + std::cout << std::endl; + + std::cout << "[*] Input request code in Base64: (Double press ENTER to end)" << std::endl; + while (true) { + std::string temp; + if (!std::getline(std::cin, temp)) { + throw exceptions::operation_canceled_exception(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), u8"Operation is canceled by user."); + } + + if (temp.empty()) { + break; + } + + b64_request_code.append(temp); + } + + request_code = base64_rfc4648::decode(b64_request_code); + if (request_code.size() != 256) { + throw ::nkg::exception(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), fmt::format("Broken request code. %zu", request_code.size())); + } + + u8_request_info.resize((cipher.bits() + 7) / 8); + u8_request_info.resize(cipher.private_decrypt(request_code.data(), request_code.size(), u8_request_info.data(), RSA_PKCS1_PADDING)); + + std::cout << "[*] Request Info:" << std::endl; + std::cout << u8_request_info << std::endl; + std::cout << std::endl; + + 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 writer(buffer); + + // + // Begin to parse + // + json.Parse(u8_request_info.c_str()); + // + // Remove "Platform" info + // + json.RemoveMember("P"); + // + // Set "Name" info + // + N_Key.SetString("N", 1); + N_Value.SetString(u8_username.c_str(), static_cast(u8_username.length())); + // + // Set "Organization" info + // + O_Key.SetString("O", 1); + O_Value.SetString(u8_organization.c_str(), static_cast(u8_organization.length())); + // + // Set "Time" info + // + T_Key.SetString("T", 1); + T_Value.SetUint(static_cast(std::time(nullptr))); + // + // Add "Name", "Organization" and "Time" + // + 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) { + throw ::nkg::exception(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), u8"Response Info is too long."); + } + + u8_response_info.assign(buffer.GetString(), buffer.GetSize()); + + std::cout << "[*] Response Info:" << std::endl; + std::cout << u8_response_info << std::endl; + std::cout << std::endl; + + response_code.resize((cipher.bits() + 7) / 8); + response_code.resize(cipher.private_encrypt(u8_response_info.data(), u8_response_info.size(), response_code.data(), RSA_PKCS1_PADDING)); + + b64_response_code = base64_rfc4648::encode(response_code); + + std::cout << "[*] Activation Code:" << std::endl; + std::cout << b64_response_code << std::endl; + std::cout << std::endl; + } + + void GenerateLicenseBinary(const rsa_cipher& cipher, const navicat_serial_generator& sn_generator) { + std::string u8_serial_number = sn_generator.serial_number(); + + std::string u8_username; + std::string u8_organization; + + std::string u8_response_info; + std::vector response_code; + + std::cout << "[*] Your name: "; + if (!std::getline(std::cin, u8_username)) { + throw exceptions::operation_canceled_exception(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), u8"Operation is canceled by user."); + } + + std::cout << "[*] Your organization: "; + if (!std::getline(std::cin, u8_organization)) { + throw exceptions::operation_canceled_exception(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), u8"Operation is canceled by user."); + } + + std::cout << std::endl; + + 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::Value K_Key; + rapidjson::Value K_Value; + rapidjson::StringBuffer buffer; + rapidjson::Writer writer(buffer); + + json.Parse("{}"); + K_Key.SetString("K", 1); + K_Value.SetString(u8_serial_number.c_str(), static_cast(u8_serial_number.length())); + N_Key.SetString("N", 1); + N_Value.SetString(u8_username.c_str(), static_cast(u8_username.length())); + O_Key.SetString("O", 1); + O_Value.SetString(u8_organization.c_str(), static_cast(u8_organization.length())); + T_Key.SetString("T", 1); + T_Value.SetUint(static_cast(std::time(nullptr))); + + json.AddMember(K_Key, K_Value, json.GetAllocator()); + 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) { + throw ::nkg::exception(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), u8"Response Info is too long."); + } + + u8_response_info.assign(buffer.GetString(), buffer.GetSize()); + + std::cout << "[*] Response Info:" << std::endl; + std::cout << u8_response_info << std::endl; + std::cout << std::endl; + + response_code.resize((cipher.bits() + 7) / 8); + response_code.resize(cipher.private_encrypt(u8_response_info.data(), u8_response_info.size(), response_code.data(), RSA_PKCS1_PADDING)); + + resource_wrapper license_file{ resource_traits::unix_os::file_descriptor{}, open("license_file", O_WRONLY | O_CREAT) }; + if (!license_file.is_valid()) { + throw exceptions::unix_exception(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), errno, u8"open failed."); + } + + if (write(license_file.get(), response_code.data(), response_code.size()) < 0) { + throw exceptions::unix_exception(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), errno, u8"write failed."); + } + + std::cout << "[+] license_file has been generated." << std::endl; + } +} + +#undef NKG_CURRENT_SOURCE_FILE +#undef NKG_CURRENT_SOURCE_LINE diff --git a/navicat-keygen/base32_rfc4648.cpp b/navicat-keygen/base32_rfc4648.cpp new file mode 100644 index 0000000..89e011e --- /dev/null +++ b/navicat-keygen/base32_rfc4648.cpp @@ -0,0 +1,122 @@ +#include "base32_rfc4648.hpp" +#include +#include + +#define NKG_CURRENT_SOURCE_FILE() u8".\\navicat-keygen\\base32_rfc4648.cpp" +#define NKG_CURRENT_SOURCE_LINE() __LINE__ + +namespace nkg { + + char base32_rfc4648::symbol(alphabet_index_t idx) { + return alphabet[idx]; + } + + base32_rfc4648::alphabet_index_t base32_rfc4648::reverse_symbol(char c) { + if ('A' <= c && c <= 'Z') { + return c - 'A'; + } else if ('2' <= c && c <= '7') { + return c - '2' + 26; + } else { + throw decoding_error(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), u8"Non-base32 digit is found."); + } + } + + std::string base32_rfc4648::encode(const std::vector& data) { + return encode(data.data(), data.size()); + } + + std::string base32_rfc4648::encode(const void* data_ptr, size_t data_size) { + std::string retval; + + if (data_size) { + retval.reserve((data_size * 8 + 4) / 5); + + auto p = reinterpret_cast(data_ptr); + alphabet_index_t left_bits = 0; + alphabet_index_t bit_buffer = 0; + for (size_t i = 0; i < data_size; ++i) { + bit_buffer = (bit_buffer << 8) | p[i]; + left_bits += 8; + + while (left_bits >= 5) { + alphabet_index_t idx = (bit_buffer >> (left_bits - 5)) & 0x1f; + retval.push_back(symbol(idx)); + left_bits -= 5; + } + } + + if (left_bits > 0) { + alphabet_index_t idx = (bit_buffer << (5 - left_bits)) & 0x1f; + retval.push_back(symbol(idx)); + } + + switch (data_size % 5) { + case 0: + break; + case 1: + retval.append(6, padding_character); + break; + case 2: + retval.append(4, padding_character); + break; + case 3: + retval.append(3, padding_character); + break; + case 4: + retval.append(1, padding_character); + break; + default: + __builtin_unreachable(); + } + } + + return retval; + } + + std::vector base32_rfc4648::decode(std::string_view b32_string) { + if (b32_string.length() % 8 == 0) { + std::vector retval; + + size_t count_of_padding = std::distance(b32_string.crbegin(), std::find_if_not(b32_string.crbegin(), b32_string.crend(), [](char c) -> bool { return c == padding_character; })); + switch (count_of_padding) { + case 1: + retval.reserve(b32_string.length() / 8 * 5 - (5 - 4)); + break; + case 3: + retval.reserve(b32_string.length() / 8 * 5 - (5 - 3)); + break; + case 4: + retval.reserve(b32_string.length() / 8 * 5 - (5 - 2)); + break; + case 6: + retval.reserve(b32_string.length() / 8 * 5 - (5 - 1)); + break; + default: + throw decoding_error(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), u8"Incorrect padding"); + } + + size_t count_of_encoded = b32_string.length() - count_of_padding; + + alphabet_index_t left_bits = 0; + alphabet_index_t bit_buffer = 0; + for (size_t i = 0; i < count_of_encoded; ++i) { + bit_buffer = (bit_buffer << 5) | reverse_symbol(b32_string[i]); + left_bits += 5; + + while (left_bits >= 8) { + auto val = static_cast((bit_buffer >> (left_bits - 8)) & 0xff); + retval.push_back(val); + left_bits -= 8; + } + } + + return retval; + } else { + throw decoding_error(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), u8"Incorrect padding"); + } + } + +} + +#undef NKG_CURRENT_SOURCE_LINE +#undef NKG_CURRENT_SOURCE_FILE diff --git a/navicat-keygen/base32_rfc4648.hpp b/navicat-keygen/base32_rfc4648.hpp new file mode 100644 index 0000000..530c0d9 --- /dev/null +++ b/navicat-keygen/base32_rfc4648.hpp @@ -0,0 +1,31 @@ +#pragma once +#include +#include +#include "exception.hpp" + +namespace nkg { + + struct base32_rfc4648 { + using alphabet_index_t = size_t; + + static constexpr const char alphabet[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; + static constexpr const char padding_character = '='; + + class decoding_error : public ::nkg::exception { + public: + decoding_error(std::string_view file, int line, std::string_view message) noexcept : + ::nkg::exception(file, line, message) {} + }; + + static char symbol(alphabet_index_t idx); + + static alphabet_index_t reverse_symbol(char c); + + static std::string encode(const std::vector& data); + + static std::string encode(const void* data_ptr, size_t data_size); + + static std::vector decode(std::string_view b32_string); + }; + +} diff --git a/navicat-keygen/base64_rfc4648.cpp b/navicat-keygen/base64_rfc4648.cpp new file mode 100644 index 0000000..8969100 --- /dev/null +++ b/navicat-keygen/base64_rfc4648.cpp @@ -0,0 +1,99 @@ +#include "base64_rfc4648.hpp" + +#include +#include + +#include "resource_wrapper.hpp" +#include "resource_traits/openssl/bio.hpp" +#include "resource_traits/openssl/bio_chain.hpp" + +#define NKG_CURRENT_SOURCE_FILE() u8".\\navicat-keygen\\base64_rfc4648.cpp" +#define NKG_CURRENT_SOURCE_LINE() __LINE__ + +namespace nkg { + + std::string base64_rfc4648::encode(const std::vector& data) { + resource_wrapper bio_b64{ resource_traits::openssl::bio_chain{}, BIO_new(BIO_f_base64()) }; + if (!bio_b64.is_valid()) { + throw backend_error(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), u8"BIO_new failed."); + } + + BIO_set_flags(bio_b64.get(), BIO_FLAGS_BASE64_NO_NL); + + resource_wrapper bio_memory{ resource_traits::openssl::bio{}, BIO_new(BIO_s_mem()) }; + if (!bio_memory.is_valid()) { + throw backend_error(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), u8"BIO_new failed."); + } + + BIO_push(bio_b64.get(), bio_memory.get()); + + for (size_t written_size = 0, left_size = data.size(); left_size != 0;) { + int size_to_write = static_cast(std::min(left_size, static_cast(INT_MAX))); + + int r = BIO_write(bio_b64.get(), data.data() + written_size, size_to_write); + if (r > 0) { + written_size += r; + left_size -= r; + } else { + throw backend_error(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), u8"BIO_write failed."); + } + } + + BIO_flush(bio_b64.get()); + + const char* pch = nullptr; + long lch = BIO_get_mem_data(bio_memory.get(), &pch); + + bio_memory.discard(); // the bio_chain `bio_b64` will free it + + return std::string(pch, lch); + } + + std::vector base64_rfc4648::decode(std::string_view b64_string) { + resource_wrapper bio_b64{ resource_traits::openssl::bio_chain{}, BIO_new(BIO_f_base64()) }; + if (!bio_b64.is_valid()) { + throw backend_error(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), u8"BIO_new failed."); + } + + BIO_set_flags(bio_b64.get(), BIO_FLAGS_BASE64_NO_NL); + + resource_wrapper bio_memory{ resource_traits::openssl::bio{}, BIO_new(BIO_s_mem()) }; + if (!bio_memory.is_valid()) { + throw backend_error(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), u8"BIO_new failed."); + } + + BIO_push(bio_b64.get(), bio_memory.get()); + + for (size_t written_length = 0, left_length = b64_string.length(); left_length != 0;) { + int length_to_write = static_cast(std::min(left_length, static_cast(INT_MAX))); + + int r = BIO_write(bio_memory.get(), b64_string.data() + written_length, length_to_write); + if (r > 0) { + written_length += r; + left_length -= r; + } else { + throw backend_error(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), u8"BIO_write failed."); + } + } + + std::vector retval; + retval.reserve(b64_string.length() * 3 / 4 + 1); + + for (uint8_t buf[256];;) { + auto len = BIO_read(bio_b64.get(), buf, sizeof(buf)); + if (len > 0) { + retval.insert(retval.end(), buf, buf + len); + } else { + break; + } + } + + bio_memory.discard(); // the bio_chain `bio_b64` will free it + + return retval; + } + +} + +#undef NKG_CURRENT_SOURCE_FILE +#undef NKG_CURRENT_SOURCE_LINE diff --git a/navicat-keygen/base64_rfc4648.hpp b/navicat-keygen/base64_rfc4648.hpp new file mode 100644 index 0000000..a67b435 --- /dev/null +++ b/navicat-keygen/base64_rfc4648.hpp @@ -0,0 +1,20 @@ +#pragma once +#include +#include +#include "exception.hpp" + +namespace nkg { + + struct base64_rfc4648 { + class backend_error : public ::nkg::exception { + public: + backend_error(std::string_view file, int line, std::string_view message) noexcept : + ::nkg::exception(file, line, message) {} + }; + + static std::string encode(const std::vector& data); + + static std::vector decode(std::string_view str_b64); + }; + +} diff --git a/navicat-keygen/main.cpp b/navicat-keygen/main.cpp new file mode 100644 index 0000000..2eeb6b6 --- /dev/null +++ b/navicat-keygen/main.cpp @@ -0,0 +1,123 @@ +#include +#include +#include + +#include "exception.hpp" +#include "exceptions/operation_canceled_exception.hpp" + +#include "base64_rfc4648.hpp" +#include "navicat_serial_generator.hpp" +#include "rsa_cipher.hpp" + +#define NKG_CURRENT_SOURCE_FILE() u8".\\navicat-keygen\\wmain.cpp" +#define NKG_CURRENT_SOURCE_LINE() __LINE__ + +namespace nkg { + using fnCollectInformation = std::function; + using fnGenerateLicense = std::function; + + navicat_serial_generator CollectInformationNormal(); + navicat_serial_generator CollectInformationAdvanced(); + void GenerateLicenseText(const rsa_cipher& cipher, const navicat_serial_generator& sn_generator); + void GenerateLicenseBinary(const rsa_cipher& cipher, const navicat_serial_generator& sn_generator); +} + +void welcome() { + puts("***************************************************"); + puts("* navicat-keygen by @DoubleLabyrinth *"); + puts("* version: 16.0.7.0 *"); + puts("***************************************************"); + puts(""); +} + +void help() { + puts("Usage:"); + puts(" navicat-keygen.exe <-bin|-text> [-adv] "); + puts(""); + puts(" <-bin|-text> Specify \"-bin\" to generate \"license_file\" used by Navicat 11."); + puts(" Specify \"-text\" to generate base64-encoded activation code."); + puts(" This parameter must be specified."); + puts(""); + puts(" [-adv] Enable advance mode."); + puts(" This parameter is optional."); + puts(""); + puts(" A path to an RSA-2048 private key file."); + puts(" This parameter must be specified."); + puts(""); + puts("Example:"); + puts(" navicat-keygen.exe -text .\\RegPrivateKey.pem"); +} + +int main(int argc, char* argv[]) { + welcome(); + + if (argc == 3 || argc == 4) { + nkg::fnCollectInformation lpfnCollectInformation; + nkg::fnGenerateLicense lpfnGenerateLicense; + + if (strcmp(argv[1], "-bin") == 0) { + lpfnGenerateLicense = nkg::GenerateLicenseBinary; + } else if (strcmp(argv[1], "-text") == 0) { + lpfnGenerateLicense = nkg::GenerateLicenseText; + } else { + help(); + return -1; + } + + if (argc == 4) { + if (strcmp(argv[2], "-adv") == 0) { + lpfnCollectInformation = nkg::CollectInformationAdvanced; + } else { + help(); + return -1; + } + } else { + lpfnCollectInformation = nkg::CollectInformationNormal; + } + + try { + nkg::rsa_cipher cipher; + + cipher.import_private_key_file(argv[argc - 1]); + if (cipher.bits() != 2048) { + throw nkg::exception(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), u8"RSA key length mismatches.") + .push_hint(u8"You must provide an RSA key whose modulus length is 2048 bits."); + } + + auto sn_generator = lpfnCollectInformation(); + sn_generator.generate(); + + puts("[*] Serial number:"); + puts(sn_generator.serial_number_formatted().c_str()); + puts(""); + + lpfnGenerateLicense(cipher, sn_generator); + + return 0; + } catch (nkg::exceptions::operation_canceled_exception&) { + return -1; + } catch (nkg::exception& e) { + printf("[-] %s:%d ->\n", e.source_file().c_str(), e.source_line()); + printf(" %s\n", e.custom_message().c_str()); + + if (e.error_code_exists()) { + printf(" %s (0x%zx)\n", e.error_string().c_str(), e.error_code()); + } + + for (auto& hint : e.hints()) { + printf(" Hints: %s\n", hint.c_str()); + } + + return -1; + } catch (std::exception& e) { + printf("[-] %s\n", e.what()); + return -1; + } + } else { + help(); + return -1; + } +} + +#undef NKG_CURRENT_SOURCE_FILE +#undef NKG_CURRENT_SOURCE_LINE diff --git a/navicat-keygen/navicat_serial_generator.cpp b/navicat-keygen/navicat_serial_generator.cpp new file mode 100644 index 0000000..1b939e9 --- /dev/null +++ b/navicat-keygen/navicat_serial_generator.cpp @@ -0,0 +1,160 @@ +#include "navicat_serial_generator.hpp" +#include +#include +#include +#include "base32_rfc4648.hpp" + +#define NKG_CURRENT_SOURCE_FILE() u8".\\navicat-keygen\\navicat_serial_generator.cpp" +#define NKG_CURRENT_SOURCE_LINE() __LINE__ + +namespace nkg { + + char navicat_serial_generator::_replace_confusing_chars(char c) { + if (c == 'I') { + return '8'; + } else if (c == 'O') { + return '9'; + } else { + return c; + } + }; + + navicat_serial_generator::navicat_serial_generator() noexcept : + m_data{ 0x68 , 0x2A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x32 }, m_des_key{} {} + + void navicat_serial_generator::set_software_language(navicat_software_language lang) noexcept { + switch (lang) { + case navicat_software_language::English: + m_data[5] = 0xAC; // Must be 0xAC for English version. + m_data[6] = 0x88; // Must be 0x88 for English version. + break; + case navicat_software_language::SimplifiedChinese: + m_data[5] = 0xCE; // Must be 0xCE for Simplified Chinese version. + m_data[6] = 0x32; // Must be 0x32 for Simplified Chinese version. + break; + case navicat_software_language::TraditionalChinese: + m_data[5] = 0xAA; // Must be 0xAA for Traditional Chinese version. + m_data[6] = 0x99; // Must be 0x99 for Traditional Chinese version. + break; + case navicat_software_language::Japanese: + m_data[5] = 0xAD; // Must be 0xAD for Japanese version. Discoverer: @dragonflylee + m_data[6] = 0x82; // Must be 0x82 for Japanese version. Discoverer: @dragonflylee + break; + case navicat_software_language::Polish: + m_data[5] = 0xBB; // Must be 0xBB for Polish version. Discoverer: @dragonflylee + m_data[6] = 0x55; // Must be 0x55 for Polish version. Discoverer: @dragonflylee + break; + case navicat_software_language::Spanish: + m_data[5] = 0xAE; // Must be 0xAE for Spanish version. Discoverer: @dragonflylee + m_data[6] = 0x10; // Must be 0x10 for Spanish version. Discoverer: @dragonflylee + break; + case navicat_software_language::French: + m_data[5] = 0xFA; // Must be 0xFA for French version. Discoverer: @Deltafox79 + m_data[6] = 0x20; // Must be 0x20 for French version. Discoverer: @Deltafox79 + break; + case navicat_software_language::German: + m_data[5] = 0xB1; // Must be 0xB1 for German version. Discoverer: @dragonflylee + m_data[6] = 0x60; // Must be 0x60 for German version. Discoverer: @dragonflylee + break; + case navicat_software_language::Korean: + m_data[5] = 0xB5; // Must be 0xB5 for Korean version. Discoverer: @dragonflylee + m_data[6] = 0x60; // Must be 0x60 for Korean version. Discoverer: @dragonflylee + break; + case navicat_software_language::Russian: + m_data[5] = 0xEE; // Must be 0xB5 for Russian version. Discoverer: @dragonflylee + m_data[6] = 0x16; // Must be 0x60 for Russian version. Discoverer: @dragonflylee + break; + case navicat_software_language::Portuguese: + m_data[5] = 0xCD; // Must be 0xCD for Portuguese version. Discoverer: @dragonflylee + m_data[6] = 0x49; // Must be 0x49 for Portuguese version. Discoverer: @dragonflylee + break; + default: + __builtin_unreachable(); + } + } + + void navicat_serial_generator::set_software_language(uint8_t lang_sig0, uint8_t lang_sig1) noexcept { + m_data[5] = lang_sig0; + m_data[6] = lang_sig1; + } + + void navicat_serial_generator::set_software_type(navicat_software_type software_type) noexcept { + switch (software_type) { + case navicat_software_type::DataModeler: + m_data[7] = 0x84; + break; + case navicat_software_type::Premium: + m_data[7] = 0x65; + break; + case navicat_software_type::MySQL: + m_data[7] = 0x68; + break; + case navicat_software_type::PostgreSQL: + m_data[7] = 0x6C; + break; + case navicat_software_type::Oracle: + m_data[7] = 0x70; + break; + case navicat_software_type::SQLServer: + m_data[7] = 0x74; + break; + case navicat_software_type::SQLite: + m_data[7] = 0x78; + break; + case navicat_software_type::MariaDB: + m_data[7] = 0x7C; + break; + case navicat_software_type::MongoDB: + m_data[7] = 0x80; + break; + case navicat_software_type::ReportViewer: + m_data[7] = 0xb; + break; + default: + __builtin_unreachable(); + } + } + + void navicat_serial_generator::set_software_type(uint8_t software_type_sig) noexcept { + m_data[7] = software_type_sig; + } + + void navicat_serial_generator::set_software_version(int ver) { + if (11 <= ver && ver < 16) { + m_data[8] = static_cast((ver << 4) | (m_data[8] & 0x0f)); + memcpy(m_des_key, s_des_key0, sizeof(s_des_key0)); + } else if (16 <= ver && ver < 32) { + m_data[8] = static_cast(((ver - 16) << 4) | (m_data[8] & 0x0f)); + memcpy(m_des_key, s_des_key1, sizeof(s_des_key1)); + } else { + throw version_error(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), u8"Invalid navicat version."); + } + } + + void navicat_serial_generator::generate() { + RAND_bytes(m_data + 2, 3); + + DES_key_schedule schedule; + DES_set_key_unchecked(&m_des_key, &schedule); + DES_ecb_encrypt(reinterpret_cast(m_data + 2), reinterpret_cast(m_data + 2), &schedule, DES_ENCRYPT); + + m_serial_number = base32_rfc4648::encode(m_data, sizeof(m_data)); + std::transform(m_serial_number.begin(), m_serial_number.end(), m_serial_number.begin(), _replace_confusing_chars); + + std::string_view sn = m_serial_number; + m_serial_number_formatted = fmt::format("{}-{}-{}-{}", sn.substr(0, 4), sn.substr(4, 4), sn.substr(8, 4), sn.substr(12, 4)); + } + + [[nodiscard]] + const std::string& navicat_serial_generator::serial_number() const noexcept { + return m_serial_number; + } + + [[nodiscard]] + const std::string& navicat_serial_generator::serial_number_formatted() const noexcept { + return m_serial_number_formatted; + } +} + +#undef NKG_CURRENT_SOURCE_LINE +#undef NKG_CURRENT_SOURCE_FILE diff --git a/navicat-keygen/navicat_serial_generator.hpp b/navicat-keygen/navicat_serial_generator.hpp new file mode 100644 index 0000000..d086132 --- /dev/null +++ b/navicat-keygen/navicat_serial_generator.hpp @@ -0,0 +1,75 @@ +#pragma once +#include +#include +#include +#include "exception.hpp" + +namespace nkg { + + enum class navicat_software_language { + English, + SimplifiedChinese, + TraditionalChinese, + Japanese, + Polish, + Spanish, + French, + German, + Korean, + Russian, + Portuguese + }; + + enum class navicat_software_type { + DataModeler, + Premium, + MySQL, + PostgreSQL, + Oracle, + SQLServer, + SQLite, + MariaDB, + MongoDB, + ReportViewer + }; + + class navicat_serial_generator { + public: + class version_error : public ::nkg::exception { + public: + version_error(std::string_view file, int line, std::string_view message) noexcept : + ::nkg::exception(file, line, message) {} + }; + + private: + static inline const DES_cblock s_des_key0 = { 0x64, 0xAD, 0xF3, 0x2F, 0xAE, 0xF2, 0x1A, 0x27 }; + static inline const DES_cblock s_des_key1 = { 0xE9, 0x7F, 0xB0, 0x60, 0x77, 0x45, 0x90, 0xAE }; + + uint8_t m_data[10]; + DES_cblock m_des_key; + std::string m_serial_number; + std::string m_serial_number_formatted; + + static char _replace_confusing_chars(char c); + + public: + navicat_serial_generator() noexcept; + + void set_software_language(navicat_software_language lang) noexcept; + void set_software_language(uint8_t lang_sig0, uint8_t lang_sig1) noexcept; + + void set_software_type(navicat_software_type software_type) noexcept; + void set_software_type(uint8_t software_type_sig) noexcept; + + void set_software_version(int Version); + + void generate(); + + [[nodiscard]] + const std::string& serial_number() const noexcept; + + [[nodiscard]] + const std::string& serial_number_formatted() const noexcept; + }; + +} diff --git a/navicat-patcher/amd64_emulator.cpp b/navicat-patcher/amd64_emulator.cpp new file mode 100644 index 0000000..c45ce70 --- /dev/null +++ b/navicat-patcher/amd64_emulator.cpp @@ -0,0 +1,199 @@ +#define _CRT_SECURE_NO_WARNINGS +#include "amd64_emulator.hpp" +#include "exceptions/key_exception.hpp" +#include "resource_traits/unicorn/unicorn_alloc.hpp" + +#define NKG_CURRENT_SOURCE_FILE() u8".\\navicat-patcher\\amd64_emulator.cpp" +#define NKG_CURRENT_SOURCE_LINE() __LINE__ + +namespace nkg { + + void amd64_emulator::_unicorn_hookcode_cb_stub(uc_engine* uc, uint64_t address, uint32_t size, void* user_data) { + auto hook_stub_ctx = reinterpret_cast(user_data); + auto& hook_callback = std::any_cast&>(hook_stub_ctx->self->m_unicorn_hook_callbacks[hook_stub_ctx->unicorn_hook_handle]); + hook_callback(address, size); + } + + void amd64_emulator::_unicorn_hookmem_cb_stub(uc_engine* uc, uc_mem_type type, uint64_t address, int size, int64_t value, void* user_data) { + auto hook_stub_ctx = reinterpret_cast(user_data); + auto& hook_callback = std::any_cast&>(hook_stub_ctx->self->m_unicorn_hook_callbacks[hook_stub_ctx->unicorn_hook_handle]); + hook_callback(type, address, static_cast(size), value); + } + + bool amd64_emulator::_unicorn_eventmem_cb_stub(uc_engine* uc, uc_mem_type type, uint64_t address, int size, int64_t value, void* user_data) { + auto hook_stub_ctx = reinterpret_cast(user_data); + auto& hook_callback = std::any_cast&>(hook_stub_ctx->self->m_unicorn_hook_callbacks[hook_stub_ctx->unicorn_hook_handle]); + return hook_callback(type, address, static_cast(size), value); + } + + amd64_emulator::amd64_emulator() { + auto err = uc_open(UC_ARCH_X86, UC_MODE_64, m_unicorn_engine.unsafe_addressof()); + if (err != UC_ERR_OK) { + throw backend_error(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), err, u8"uc_open failed."); + } + } + + void amd64_emulator::reg_read(int regid, void* value) { + auto err = uc_reg_read(m_unicorn_engine.get(), regid, value); + if (err != UC_ERR_OK) { + throw backend_error(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), err, u8"uc_reg_read failed."); + } + } + + void amd64_emulator::reg_write(int regid, const void* value) { + auto err = uc_reg_write(m_unicorn_engine.get(), regid, value); + if (err != UC_ERR_OK) { + throw backend_error(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), err, u8"uc_reg_write failed."); + } + } + + uint64_t amd64_emulator::msr_read(uint32_t rid) { + uc_x86_msr msr; + msr.rid = rid; + + auto err = uc_reg_read(m_unicorn_engine.get(), UC_X86_REG_MSR, &msr); + if (err != UC_ERR_OK) { + throw backend_error(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), err, u8"uc_reg_write failed."); + } + + return msr.value; + } + + void amd64_emulator::msr_write(uint32_t rid, uint64_t value) { + uc_x86_msr msr; + msr.rid = rid; + msr.value = value; + + auto err = uc_reg_write(m_unicorn_engine.get(), UC_X86_REG_MSR, &msr); + if (err != UC_ERR_OK) { + throw backend_error(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), err, u8"uc_reg_write failed."); + } + } + + void amd64_emulator::mem_map(uint64_t address, size_t size, uint32_t perms) { + auto err = uc_mem_map(m_unicorn_engine.get(), address, size, perms); + if (err != UC_ERR_OK) { + throw backend_error(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), err, u8"uc_mem_map failed."); + } + } + + void amd64_emulator::mem_unmap(uint64_t address, size_t size) { + auto err = uc_mem_unmap(m_unicorn_engine.get(), address, size); + if (err != UC_ERR_OK) { + throw backend_error(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), err, u8"uc_mem_unmap failed."); + } + } + + void amd64_emulator::mem_read(uint64_t address, void* buf, size_t size) { + auto err = uc_mem_read(m_unicorn_engine.get(), address, buf, size); + if (err != UC_ERR_OK) { + throw backend_error(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), err, u8"uc_mem_read failed."); + } + } + + std::vector amd64_emulator::mem_read(uint64_t address, size_t size) { + std::vector ret_buf(size); + + auto err = uc_mem_read(m_unicorn_engine.get(), address, ret_buf.data(), ret_buf.size()); + if (err != UC_ERR_OK) { + throw backend_error(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), err, u8"uc_mem_read failed."); + } + + return ret_buf; + } + + void amd64_emulator::mem_write(uint64_t address, const void* buf, size_t size) { + auto err = uc_mem_write(m_unicorn_engine.get(), address, buf, size); + if (err != UC_ERR_OK) { + throw backend_error(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), err, u8"uc_mem_write failed."); + } + } + + void amd64_emulator::mem_write(uint64_t address, const std::vector& buf) { + mem_write(address, buf.data(), buf.size()); + } + + bool amd64_emulator::is_address_mapped(uint64_t address) { + resource_wrapper mapped_regions{ resource_traits::unicorn::unicorn_alloc{} }; + uint32_t mapped_regions_num; + + auto err = uc_mem_regions(m_unicorn_engine.get(), mapped_regions.unsafe_addressof(), &mapped_regions_num); + if (err != UC_ERR_OK) { + throw backend_error(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), err, u8"uc_mem_regions failed."); + } + + for (size_t i = 0; i < mapped_regions_num; ++i) { + auto& region = mapped_regions.as()[i]; + if (region.begin <= address && address <= region.end) { + return true; + } + } + + return false; + } + + void amd64_emulator::hook_del(uc_hook hook_handle) { + auto iter_of_hook_stub_ctxs = m_unicorn_hook_stub_ctxs.find(hook_handle); + if (iter_of_hook_stub_ctxs == m_unicorn_hook_stub_ctxs.end()) { + throw exceptions::key_exception(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), u8"Target hook is not found."); + } + + auto iter_of_hook_callbacks = m_unicorn_hook_callbacks.find(hook_handle); + if (iter_of_hook_callbacks != m_unicorn_hook_callbacks.end()) { + auto err = uc_hook_del(m_unicorn_engine.get(), hook_handle); + if (err) { + throw backend_error(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), err, u8"hook_del failed."); + } + + m_unicorn_hook_callbacks.erase(iter_of_hook_callbacks); + m_unicorn_hook_stub_ctxs.erase(iter_of_hook_stub_ctxs); + return; + } + + __builtin_unreachable(); + } + + void amd64_emulator::emu_start(uint64_t begin_address, uint64_t end_address, uint64_t timeout, size_t count) { + auto err = uc_emu_start(m_unicorn_engine.get(), begin_address, end_address, timeout, count); + if (err != UC_ERR_OK) { + throw backend_error(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), err, u8"emu_start failed."); + } + } + + void amd64_emulator::emu_stop() { + auto err = uc_emu_stop(m_unicorn_engine.get()); + if (err != UC_ERR_OK) { + throw backend_error(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), err, u8"uc_emu_stop failed."); + } + } + + //void amd64_emulator::create_gdt_entry(uint64_t gdt_entry_address, uint32_t base, uint32_t limit, uint8_t access_byte, uint8_t flags) { + // struct { + // uint16_t limit0; + // uint16_t base0; + // uint8_t base1; + // uint8_t access_byte; + // uint8_t limit1 : 4; + // uint8_t flags : 4; + // uint8_t base2; + // } segment_descriptor; + + // static_assert(sizeof(segment_descriptor) == 8); + + // segment_descriptor.limit0 = limit & 0xffff; + // segment_descriptor.base0 = base & 0xffff; + // segment_descriptor.base1 = (base >> 16) & 0xff; + // segment_descriptor.access_byte = access_byte; + // segment_descriptor.limit1 = (limit >> 16) & 0xf; + // segment_descriptor.flags = flags & 0xf; + // segment_descriptor.base2 = (base >> 24) & 0xff; + + // auto err = uc_mem_write(m_unicorn_engine.get(), gdt_entry_address, &segment_descriptor, sizeof(segment_descriptor)); + // if (err != UC_ERR_OK) { + // throw backend_error(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), err, u8"uc_mem_write failed."); + // } + //} +} + +#undef NKG_CURRENT_SOURCE_LINE +#undef NKG_CURRENT_SOURCE_FILE diff --git a/navicat-patcher/amd64_emulator.hpp b/navicat-patcher/amd64_emulator.hpp new file mode 100644 index 0000000..e60f072 --- /dev/null +++ b/navicat-patcher/amd64_emulator.hpp @@ -0,0 +1,152 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include "resource_wrapper.hpp" +#include "resource_traits/unicorn/unicorn_handle.hpp" +#include "exception.hpp" + +#define NKG_CURRENT_SOURCE_FILE() u8".\\navicat-patcher\\amd64_emulator.hpp" +#define NKG_CURRENT_SOURCE_LINE() __LINE__ + +namespace nkg { + + class amd64_emulator { + public: + class backend_error : public ::nkg::exception { + public: + using error_code_t = uc_err; + + private: + error_code_t m_error_code; + std::string m_error_string; + + public: + backend_error(std::string_view file, int line, error_code_t unicorn_err, std::string_view message) noexcept : + ::nkg::exception(file, line, message), m_error_code(unicorn_err), m_error_string(uc_strerror(unicorn_err)) {} + + [[nodiscard]] + virtual bool error_code_exists() const noexcept override { + return true; + } + + [[nodiscard]] + virtual intptr_t error_code() const noexcept override { + return m_error_code; + } + + [[nodiscard]] + virtual const std::string& error_string() const noexcept override { + return m_error_string; + } + }; + + using hookcode_cb_t = void(uint64_t address, size_t size); + using hookmem_cb_t = void(uc_mem_type type, uint64_t address, size_t size, int64_t value); + using eventmem_cb_t = bool(uc_mem_type type, uint64_t address, size_t size, int64_t value); + + private: + struct hook_stub_context_t { + amd64_emulator* self; + uc_hook unicorn_hook_handle; + }; + + resource_wrapper m_unicorn_engine; + std::unordered_map m_unicorn_user_ctx; + std::unordered_map> m_unicorn_hook_stub_ctxs; + std::unordered_map m_unicorn_hook_callbacks; + + static void _unicorn_hookcode_cb_stub(uc_engine* uc, uint64_t address, uint32_t size, void* user_data); + static void _unicorn_hookmem_cb_stub(uc_engine* uc, uc_mem_type type, uint64_t address, int size, int64_t value, void* user_data); + static bool _unicorn_eventmem_cb_stub(uc_engine* uc, uc_mem_type type, uint64_t address, int size, int64_t value, void* user_data); + + public: + amd64_emulator(); + + void reg_read(int regid, void* buf); + + void reg_write(int regid, const void* buf); + + uint64_t msr_read(uint32_t rid); + + void msr_write(uint32_t rid, uint64_t value); + + void mem_map(uint64_t address, size_t size, uint32_t perms); + + void mem_unmap(uint64_t address, size_t size); + + void mem_read(uint64_t address, void* buf, size_t size); + + std::vector mem_read(uint64_t address, size_t size); + + void mem_write(uint64_t address, const void* buf, size_t size); + + void mem_write(uint64_t address, const std::vector& buf); + + bool is_address_mapped(uint64_t address); + + template + uc_hook hook_add(callable_t&& hook_callback, uint64_t begin_address = 1, uint64_t end_address = 0) { + uc_err err; + + auto hook_stub_ctx = std::make_unique(); + hook_stub_ctx->self = this; + hook_stub_ctx->unicorn_hook_handle = 0; + + if constexpr (hook_type == UC_HOOK_CODE) { + err = uc_hook_add(m_unicorn_engine.get(), &hook_stub_ctx->unicorn_hook_handle, hook_type, reinterpret_cast(_unicorn_hookcode_cb_stub), hook_stub_ctx.get(), begin_address, end_address); + if (err != UC_ERR_OK) { + throw backend_error(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), err, u8"uc_hook_add failed."); + } + + m_unicorn_hook_callbacks.emplace(std::make_pair(hook_stub_ctx->unicorn_hook_handle, std::function{ std::forward(hook_callback) })); + } else if constexpr ((hook_type & ~UC_HOOK_MEM_VALID) == 0) { + err = uc_hook_add(m_unicorn_engine.get(), &hook_stub_ctx->unicorn_hook_handle, hook_type, reinterpret_cast(_unicorn_hookmem_cb_stub), hook_stub_ctx.get(), begin_address, end_address); + if (err != UC_ERR_OK) { + throw backend_error(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), err, u8"uc_hook_add failed."); + } + + m_unicorn_hook_callbacks.emplace(std::make_pair(hook_stub_ctx->unicorn_hook_handle, std::function{ std::forward(hook_callback) })); + } else if constexpr ((hook_type & ~UC_HOOK_MEM_UNMAPPED) == 0 || (hook_type & ~UC_HOOK_MEM_PROT) == 0) { + err = uc_hook_add(m_unicorn_engine.get(), &hook_stub_ctx->unicorn_hook_handle, hook_type, reinterpret_cast(_unicorn_eventmem_cb_stub), hook_stub_ctx.get(), begin_address, end_address); + if (err != UC_ERR_OK) { + throw backend_error(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), err, u8"uc_hook_add failed."); + } + + m_unicorn_hook_callbacks.emplace(std::make_pair(hook_stub_ctx->unicorn_hook_handle, std::function{ std::forward(hook_callback) })); + } else { + static_assert( + hook_type == UC_HOOK_CODE || + (hook_type & ~UC_HOOK_MEM_VALID) == 0 || + (hook_type & ~UC_HOOK_MEM_UNMAPPED) == 0 || (hook_type & ~UC_HOOK_MEM_PROT) == 0, "Unsupported hook type."); + } + + return m_unicorn_hook_stub_ctxs.emplace(std::make_pair(hook_stub_ctx->unicorn_hook_handle, std::move(hook_stub_ctx))).first->first; + } + + void hook_del(uc_hook hook_handle); + + void emu_start(uint64_t begin_address, uint64_t end_address = 0, uint64_t timeout = 0, size_t count = 0); + + void emu_stop(); + + // void create_gdt_entry(uint64_t gdt_entry_address, uint32_t base, uint32_t limit, uint8_t access_byte, uint8_t flags); + + template + void context_set(const std::string& name, val_t&& value) { + m_unicorn_user_ctx[name] = std::forward(value); + } + + template + val_t context_get(const std::string& name) { + return std::any_cast(m_unicorn_user_ctx[name]); + } + }; + +} + +#undef NKG_CURRENT_SOURCE_LINE +#undef NKG_CURRENT_SOURCE_FILE diff --git a/navicat-patcher/elf64_interpreter.cpp b/navicat-patcher/elf64_interpreter.cpp new file mode 100644 index 0000000..8e6d0d0 --- /dev/null +++ b/navicat-patcher/elf64_interpreter.cpp @@ -0,0 +1,635 @@ +#include "elf64_interpreter.hpp" +#include "exceptions/index_exception.hpp" +#include "exceptions/key_exception.hpp" +#include +#include +#include + +#define NKG_CURRENT_SOURCE_FILE() ".\\navicat-patcher\\elf64_interpreter.cpp" +#define NKG_CURRENT_SOURCE_LINE() __LINE__ + +namespace nkg { + + elf64_interpreter::elf64_interpreter() : + m_elf_size(0), + m_elf_header(nullptr), + m_elf_program_headers(nullptr), + m_elf_section_headers(nullptr), + m_dynamic_rela(nullptr), + m_dynamic_relasz(nullptr), + m_dynamic_rel(nullptr), + m_dynamic_relsz(nullptr), + m_dynamic_pltgot(nullptr), + m_dynamic_jmprel(nullptr), + m_dynamic_pltrel(nullptr), + m_dynamic_pltrelsz(nullptr), + m_dynamic_symtab(nullptr), + m_dynamic_strtab(nullptr) {} + + [[nodiscard]] + elf64_interpreter elf64_interpreter::parse(void* image_ptr, size_t image_size) { + elf64_interpreter new_image; + + // check ELF header + new_image.m_elf_size = image_size; + new_image.m_elf_header = reinterpret_cast(image_ptr); + if (is_address_in_range(new_image.m_elf_header, sizeof(Elf64_Ehdr), image_ptr, image_size) == false) { + throw parse_error(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), "Bad ELF file: image is corrupted."); + } + + if (memcmp(new_image.m_elf_header->e_ident, ELFMAG, SELFMAG) != 0) { + throw parse_error(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), "Bad ELF file: header magic check failed."); + } + + if (new_image.m_elf_header->e_ident[EI_CLASS] != ELFCLASS64) { + throw parse_error(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), "Unsupported ELF file: not ELF64 image."); + } + + if (new_image.m_elf_header->e_ident[EI_DATA] == ELFDATA2LSB && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) { + ; // pass + } else if (new_image.m_elf_header->e_ident[EI_DATA] == ELFDATA2MSB && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) { + ; // pass + } else { + throw parse_error(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), "Unsupported ELF file: unsupported endian."); + } + + if (new_image.m_elf_header->e_ident[EI_VERSION] != EV_CURRENT) { + throw parse_error(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), "Bad ELF file: Elf64_Ehdr::e_ident[EI_VERSION] check failed."); + } + + // new_image.m_elf_header->e_ident[EI_OSABI] + // new_image.m_elf_header->e_ident[EI_ABIVERSION] + + for (int i = EI_PAD; i < sizeof(new_image.m_elf_header->e_ident); ++i) { + if (new_image.m_elf_header->e_ident[i] != 0) { + throw parse_error(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), "Bad ELF file: Elf64_Ehdr::e_ident padding contains non-zero byte(s)."); + } + } + + if (new_image.m_elf_header->e_version != EV_CURRENT) { + throw parse_error(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), "Bad ELF file: Elf64_Ehdr::e_version check failed."); + } + + if (new_image.m_elf_header->e_ehsize != sizeof(Elf64_Ehdr)) { + throw parse_error(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), "Bad ELF file: Elf64_Ehdr::e_ehsize check failed."); + } + + if (new_image.m_elf_header->e_phoff && new_image.m_elf_header->e_phentsize && new_image.m_elf_header->e_phnum) { + if (new_image.m_elf_header->e_phentsize != sizeof(Elf64_Phdr)) { + throw parse_error(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), "Bad ELF file: Elf64_Ehdr::e_phentsize check failed."); + } + + new_image.m_elf_program_headers = address_offset_cast(image_ptr, new_image.m_elf_header->e_phoff); + + if (is_address_in_range(new_image.m_elf_program_headers, new_image.m_elf_header->e_phnum * sizeof(Elf64_Phdr), image_ptr, image_size) == false) { + throw parse_error(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), "Bad ELF file: image is corrupted."); + } + } else if (new_image.m_elf_header->e_phoff == 0 && new_image.m_elf_header->e_phentsize == 0 && new_image.m_elf_header->e_phnum == 0) { + new_image.m_elf_program_headers = nullptr; + } else { + throw parse_error(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), "Bad ELF file: Elf64_Ehdr::e_ph* check failed."); + } + + if (new_image.m_elf_header->e_shoff && new_image.m_elf_header->e_shentsize && new_image.m_elf_header->e_shnum) { + if (new_image.m_elf_header->e_shentsize != sizeof(Elf64_Shdr)) { + throw parse_error(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), "Bad ELF file: Elf64_Ehdr::e_shentsize check failed."); + } + + new_image.m_elf_section_headers = address_offset_cast(image_ptr, new_image.m_elf_header->e_shoff); + + if (is_address_in_range(new_image.m_elf_section_headers, new_image.m_elf_header->e_shnum * sizeof(Elf64_Shdr), image_ptr, image_size) == false) { + throw parse_error(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), "Bad ELF file: image is corrupted."); + } + } else if (new_image.m_elf_header->e_shoff == 0 && new_image.m_elf_header->e_shentsize == 0 && new_image.m_elf_header->e_shnum == 0) { + new_image.m_elf_section_headers = nullptr; + } else { + throw parse_error(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), "Bad ELF file: Elf64_Ehdr::e_sh* check failed."); + } + + if (new_image.m_elf_header->e_shstrndx != SHN_UNDEF) { + if (new_image.m_elf_header->e_shstrndx >= new_image.m_elf_header->e_shnum) { + throw parse_error(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), "Bad ELF file: Elf64_Ehdr::e_shstrndx is out of range."); + } + } + + // check program header table and section header table are not overlapped + if (new_image.m_elf_program_headers && new_image.m_elf_section_headers) { + auto a1 = new_image.m_elf_program_headers; + auto a2 = new_image.m_elf_program_headers + new_image.m_elf_header->e_phnum; + auto b1 = new_image.m_elf_section_headers; + auto b2 = new_image.m_elf_section_headers + new_image.m_elf_header->e_shnum; + bool not_overlapped = address_delta(a1, b1) < 0 && address_delta(a2, b1) <= 0 || address_delta(b1, a1) < 0 && address_delta(b2, a1) <= 0; + if (!not_overlapped) { + throw parse_error(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), "Bad ELF file: program header table and section header table overlapped."); + } + } + + // parse program header + for (size_t i = 0; i < new_image.m_elf_header->e_phnum; ++i) { + auto& prog_hdr = new_image.m_elf_program_headers[i]; + if (!is_address_in_range(address_offset(image_ptr, static_cast(prog_hdr.p_offset)), prog_hdr.p_filesz, image_ptr, image_size)) { + throw parse_error(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), "Bad ELF file: image is corrupted."); + } + + auto prog_hdr_align = prog_hdr.p_align; + if (prog_hdr_align) { + // align must be a power of 2 + if ((prog_hdr_align & (prog_hdr_align - 1)) != 0) { + throw parse_error(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), fmt::format("Bad ELF file: Elf64_Phdr[{}]: p_align is not a power of 2.", i)); + } + + if (prog_hdr.p_offset % prog_hdr_align != prog_hdr.p_vaddr % prog_hdr_align) { + throw parse_error(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), fmt::format("Bad ELF file: Elf64_Phdr[{}]: p_offset !== p_vaddr (mod prog_hdr_align).", i)); + } + } + + if (prog_hdr.p_type == PT_LOAD) { + new_image.m_segment_va_lookup_table.emplace(std::make_pair(prog_hdr.p_vaddr, &prog_hdr)); + new_image.m_segment_fo_lookup_table.emplace(std::make_pair(prog_hdr.p_offset, &prog_hdr)); + } + } + + // parse section header + if (new_image.m_elf_header->e_shstrndx != SHN_UNDEF) { + auto sect_hdr_strtab = &new_image.m_elf_section_headers[new_image.m_elf_header->e_shstrndx]; + auto sect_view_strtab = address_offset_cast(image_ptr, sect_hdr_strtab->sh_offset); + + if (sect_hdr_strtab->sh_type != SHT_STRTAB) { + throw parse_error(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), "Bad ELF file: sect_hdr_strtab->sh_type != SHT_STRTAB."); + } + + if (!is_address_in_range(sect_view_strtab, sect_hdr_strtab->sh_size, image_ptr, image_size)) { + throw parse_error(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), "Bad ELF file: image is corrupted."); + } + + for (size_t i = 0; i < new_image.m_elf_header->e_shnum; ++i) { + new_image.m_section_name_lookup_table + .emplace(std::make_pair(std::string_view(address_offset(sect_view_strtab, new_image.m_elf_section_headers[i].sh_name)), &new_image.m_elf_section_headers[i])); + } + } + + for (int i = 0; i < new_image.m_elf_header->e_shnum; ++i) { + auto& sect_hdr = new_image.m_elf_section_headers[i]; + switch (sect_hdr.sh_type) { + case SHT_SYMTAB: + if (sect_hdr.sh_entsize != sizeof(Elf64_Sym)) { + throw parse_error(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), fmt::format("Bad ELF file: Elf64_Shdr[{}]: sh_entsize != sizeof(Elf64_Sym).", i)); + } + + // check sh_link + if (sect_hdr.sh_link == SHN_UNDEF) { + throw parse_error(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), fmt::format("Bad ELF file: Elf64_Shdr[{}]: sh_link == SHN_UNDEF.", i)); + } + + if (sect_hdr.sh_link >= new_image.m_elf_header->e_shnum) { + throw parse_error(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), fmt::format("Bad ELF file: Elf64_Shdr[{}]: sh_link is out of range.", i)); + } + + if (new_image.m_elf_section_headers[sect_hdr.sh_link].sh_type != SHT_STRTAB) { + throw parse_error(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), fmt::format("Bad ELF file: Elf64_Shdr[{}]: bad value of sh_link.", i)); + } + + // todo: check sh_info + break; + case SHT_RELA: + if (sect_hdr.sh_entsize != sizeof(Elf64_Rela)) { + throw parse_error(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), fmt::format("Bad ELF file: Elf64_Shdr[{}]: sh_entsize != sizeof(Elf64_Rela).", i)); + } + + // check sh_link + if (sect_hdr.sh_link == SHN_UNDEF) { + throw parse_error(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), fmt::format("Bad ELF file: Elf64_Shdr[{}]: sh_link == SHN_UNDEF.", i)); + } + + if (sect_hdr.sh_link >= new_image.m_elf_header->e_shnum) { + throw parse_error(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), fmt::format("Bad ELF file: Elf64_Shdr[{}]: sh_link is out of range.", i)); + } + + if (new_image.m_elf_section_headers[sect_hdr.sh_link].sh_type != SHT_SYMTAB && new_image.m_elf_section_headers[sect_hdr.sh_link].sh_type != SHT_DYNSYM) { + throw parse_error(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), fmt::format("Bad ELF file: Elf64_Shdr[%u]: bad value of sh_link.", i)); + } + + // check sh_info + if (sect_hdr.sh_flags & SHF_INFO_LINK) { + if (sect_hdr.sh_info == SHN_UNDEF) { + throw parse_error(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), fmt::format("Bad ELF file: Elf64_Shdr[{}]: sh_info == SHN_UNDEF.", i)); + } + + if (sect_hdr.sh_info >= new_image.m_elf_header->e_shnum) { + throw parse_error(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), fmt::format("Bad ELF file: Elf64_Shdr[{}]: sh_info is out of range.", i)); + } + } else { + if (sect_hdr.sh_info != 0) { + throw parse_error(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), fmt::format("Bad ELF file: Elf64_Shdr[{}]: sh_info != 0.", i)); + } + } + break; + case SHT_HASH: + if (sect_hdr.sh_link == SHN_UNDEF) { + throw parse_error(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), fmt::format("Bad ELF file: Elf64_Shdr[{}]: sh_link == SHN_UNDEF.", i)); + } + + if (sect_hdr.sh_link >= new_image.m_elf_header->e_shnum) { + throw parse_error(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), fmt::format("Bad ELF file: Elf64_Shdr[{}]: sh_link is out of range.", i)); + } + + if (new_image.m_elf_section_headers[sect_hdr.sh_link].sh_type != SHT_SYMTAB && new_image.m_elf_section_headers[sect_hdr.sh_link].sh_type != SHT_DYNSYM) { + throw parse_error(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), fmt::format("Bad ELF file: Elf64_Shdr[{}]: bad value of sh_link.", i)); + } + + if (sect_hdr.sh_info != 0) { + throw parse_error(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), fmt::format("Bad ELF file: Elf64_Shdr[{}]: sh_info != 0.", i)); + } + break; + case SHT_DYNAMIC: + if (sect_hdr.sh_entsize != sizeof(Elf64_Dyn)) { + throw parse_error(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), fmt::format("Bad ELF file: Elf64_Shdr[{}]: sh_entsize != sizeof(Elf64_Dyn).", i)); + } + + // check sh_link + if (sect_hdr.sh_link == SHN_UNDEF) { + throw parse_error(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), fmt::format("Bad ELF file: Elf64_Shdr[{}]: sh_link == SHN_UNDEF.", i)); + } + + if (sect_hdr.sh_link >= new_image.m_elf_header->e_shnum) { + throw parse_error(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), fmt::format("Bad ELF file: Elf64_Shdr[{}]: sh_link is out of range.", i)); + } + + if (new_image.m_elf_section_headers[sect_hdr.sh_link].sh_type != SHT_STRTAB) { + throw parse_error(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), fmt::format("Bad ELF file: Elf64_Shdr[{}]: bad value of sh_link.", i)); + } + + // check sh_info + if (sect_hdr.sh_info != 0) { + throw parse_error(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), fmt::format("Bad ELF file: Elf64_Shdr[{}]: sh_info != 0.", i)); + } + break; + case SHT_REL: + if (sect_hdr.sh_entsize != sizeof(Elf64_Rel)) { + throw parse_error(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), fmt::format("Bad ELF file: Elf64_Shdr[{}]: sh_entsize != sizeof(Elf64_Rel).", i)); + } + + // check sh_link + if (sect_hdr.sh_link == SHN_UNDEF) { + throw parse_error(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), fmt::format("Bad ELF file: Elf64_Shdr[{}]: sh_link == SHN_UNDEF.", i)); + } + + if (sect_hdr.sh_link >= new_image.m_elf_header->e_shnum) { + throw parse_error(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), fmt::format("Bad ELF file: Elf64_Shdr[{}]: sh_link is out of range.", i)); + } + + if (new_image.m_elf_section_headers[sect_hdr.sh_link].sh_type != SHT_SYMTAB && new_image.m_elf_section_headers[sect_hdr.sh_link].sh_type != SHT_DYNSYM) { + throw parse_error(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), fmt::format("Bad ELF file: Elf64_Shdr[%u]: bad value of sh_link.", i)); + } + + // check sh_info + if (sect_hdr.sh_flags & SHF_INFO_LINK) { + if (sect_hdr.sh_info == SHN_UNDEF) { + throw parse_error(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), fmt::format("Bad ELF file: Elf64_Shdr[{}]: sh_info == SHN_UNDEF.", i)); + } + + if (sect_hdr.sh_info >= new_image.m_elf_header->e_shnum) { + throw parse_error(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), fmt::format("Bad ELF file: Elf64_Shdr[{}]: sh_info is out of range.", i)); + } + } else { + if (sect_hdr.sh_info != 0) { + throw parse_error(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), fmt::format("Bad ELF file: Elf64_Shdr[{}]: sh_info != 0.", i)); + } + } + break; + case SHT_DYNSYM: + if (sect_hdr.sh_entsize != sizeof(Elf64_Sym)) { + throw parse_error(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), fmt::format("Bad ELF file: Elf64_Shdr[{}]: sh_entsize != sizeof(Elf64_Dyn).", i)); + } + + // check sh_link + if (sect_hdr.sh_link == SHN_UNDEF) { + throw parse_error(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), fmt::format("Bad ELF file: Elf64_Shdr[{}]: sh_link == SHN_UNDEF.", i)); + } + + if (sect_hdr.sh_link >= new_image.m_elf_header->e_shnum) { + throw parse_error(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), fmt::format("Bad ELF file: Elf64_Shdr[{}]: sh_link is out of range.", i)); + } + + if (new_image.m_elf_section_headers[sect_hdr.sh_link].sh_type != SHT_STRTAB) { + throw parse_error(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), fmt::format("Bad ELF file: Elf64_Shdr[{}]: bad value of sh_link.", i)); + } + + // todo: check sh_info + break; + default: + break; + } + + if (sect_hdr.sh_type != SHT_NOBITS) { + if (is_address_in_range(address_offset(image_ptr, sect_hdr.sh_offset), sect_hdr.sh_size, image_ptr, image_size) == false) { + throw parse_error(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), fmt::format("Bad ELF file: Elf64_Shdr[{}]: image is corrupted.", i)); + } + new_image.m_section_fo_lookup_table.emplace(std::make_pair(sect_hdr.sh_offset, new_image.m_elf_section_headers + i)); + } + + if (sect_hdr.sh_addr) { + if (sect_hdr.sh_addralign && sect_hdr.sh_addr % sect_hdr.sh_addralign != 0) { + throw parse_error(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), fmt::format("Bad ELF file: Elf64_Shdr[{}]: sh_addr is not aligned to sh_addralign.", i)); + } + new_image.m_section_va_lookup_table.emplace(std::make_pair(sect_hdr.sh_addr, &new_image.m_elf_section_headers[i])); + } + } + + // parse program header, second parse + for (size_t i = 0; i < new_image.m_elf_header->e_phnum; ++i) { + auto& prog_hdr = new_image.m_elf_program_headers[i]; + if (prog_hdr.p_type == PT_DYNAMIC) { + auto seg_dynamic_base = address_offset_cast(image_ptr, prog_hdr.p_offset); + auto seg_dyncmic_size = prog_hdr.p_filesz; + + // first parse + for (size_t j = 0; j * sizeof(Elf64_Dyn) < seg_dyncmic_size && seg_dynamic_base[j].d_tag != DT_NULL; ++j) { + auto& dyn_entry = seg_dynamic_base[j]; + switch (dyn_entry.d_tag) { + case DT_RELA: + new_image.m_dynamic_rela = &seg_dynamic_base[j]; + break; + case DT_RELAENT: + if (seg_dynamic_base[j].d_un.d_val != sizeof(Elf64_Rela)) { + throw parse_error(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), "Bad ELF file: the value of DT_RELAENT.dval != sizeof(Elf64_Rela)."); + } + break; + case DT_RELASZ: + new_image.m_dynamic_relasz = &seg_dynamic_base[j]; + break; + case DT_REL: + new_image.m_dynamic_rel = &seg_dynamic_base[j]; + break; + case DT_RELENT: + if (seg_dynamic_base[j].d_un.d_val != sizeof(Elf64_Rel)) { + throw parse_error(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), "Bad ELF file: the value of DT_RELENT.dval != sizeof(Elf64_Rel)."); + } + break; + case DT_RELSZ: + new_image.m_dynamic_relsz = &seg_dynamic_base[j]; + break; + case DT_PLTGOT: + new_image.m_dynamic_pltgot = &seg_dynamic_base[j]; + break; + case DT_JMPREL: + new_image.m_dynamic_jmprel = &seg_dynamic_base[j]; + break; + case DT_PLTREL: + if (seg_dynamic_base[j].d_un.d_val == DT_REL || seg_dynamic_base[j].d_un.d_val == DT_RELA) { + new_image.m_dynamic_pltrel = &seg_dynamic_base[j]; + } else { + throw parse_error(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), "Bad ELF file: the value of DT_PLTREL.dval is neither DT_REL nor DT_RELA."); + } + break; + case DT_PLTRELSZ: + new_image.m_dynamic_pltrelsz = &seg_dynamic_base[j]; + break; + case DT_STRTAB: + new_image.m_dynamic_strtab = &seg_dynamic_base[j]; + break; + case DT_SYMTAB: + new_image.m_dynamic_symtab = &seg_dynamic_base[j]; + break; + default: + break; + } + } + } + } + + if (new_image.m_dynamic_rela && new_image.m_dynamic_relasz) { + auto rela_base = new_image.convert_va_to_ptr(new_image.m_dynamic_rela->d_un.d_ptr); + auto rela_size = new_image.m_dynamic_relasz->d_un.d_val; + for (size_t i = 0; i * sizeof(Elf64_Rela) < rela_size; ++i) { + auto reloc_va = rela_base[i].r_offset; + auto reloc_type = ELF64_R_TYPE(rela_base[i].r_info); + switch(reloc_type) { + case R_X86_64_64: + case R_X86_64_GLOB_DAT: + case R_X86_64_RELATIVE: + new_image.m_relocation_distribute.emplace(std::make_pair(reloc_va, 8)); + break; + default: + throw parse_error(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), fmt::format("Unsupported ELF file: unhandled relocation type({}).", reloc_type)); + } + } + } + + if (new_image.m_dynamic_rel && new_image.m_dynamic_relsz) { + throw parse_error(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), "Unsupported ELF file: DT_REL is not parsed."); + } + + if (new_image.m_dynamic_jmprel && new_image.m_dynamic_pltrel && new_image.m_dynamic_pltrelsz) { + if (new_image.m_dynamic_pltrel->d_un.d_val == DT_RELA) { + auto jmprel_base = new_image.convert_va_to_ptr(new_image.m_dynamic_jmprel->d_un.d_ptr); + auto jmprel_size = new_image.m_dynamic_pltrelsz->d_un.d_val; + for (size_t i = 0; i * sizeof(Elf64_Rela) < jmprel_size; ++i) { + auto reloc_va = jmprel_base[i].r_offset; + auto reloc_type = ELF64_R_TYPE(jmprel_base[i].r_info); + if (reloc_type == R_X86_64_JUMP_SLOT) { + new_image.m_relocation_distribute.emplace(std::make_pair(reloc_va, 8)); + } else { + throw parse_error(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), fmt::format("Bad ELF file: bad relocation type({}) in JMPREL relocation table.", reloc_type)); + } + } + } else { + auto jmprel_base = new_image.convert_va_to_ptr(new_image.m_dynamic_jmprel->d_un.d_ptr); + auto jmprel_size = new_image.m_dynamic_pltrelsz->d_un.d_val; + for (size_t i = 0; i * sizeof(Elf64_Rela) < jmprel_size; ++i) { + auto reloc_va = jmprel_base[i].r_offset; + auto reloc_type = ELF64_R_TYPE(jmprel_base[i].r_info); + if (reloc_type == R_X86_64_JUMP_SLOT) { + new_image.m_relocation_distribute.emplace(std::make_pair(reloc_va, 8)); + } else { + throw parse_error(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), fmt::format("Bad ELF file: bad relocation type({}) in JMPREL relocation table.", reloc_type)); + } + } + } + } + + return new_image; + } + + [[nodiscard]] + size_t elf64_interpreter::elf_size() const noexcept { + return m_elf_size; + } + + [[nodiscard]] + Elf64_Ehdr* elf64_interpreter::elf_header() const noexcept { + return m_elf_header; + } + + [[nodiscard]] + Elf64_Phdr* elf64_interpreter::elf_program_header(size_t n) const { + if (n < m_elf_header->e_phnum) { + return m_elf_program_headers + n; + } else { + throw exceptions::index_exception(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), "Out of range."); + } + } + + [[nodiscard]] + Elf64_Phdr* elf64_interpreter::elf_program_header_from_fo(fo_t file_offset) const { + auto it = m_segment_fo_lookup_table.upper_bound(file_offset); + if (it != m_segment_fo_lookup_table.begin()) { + --it; + if (it->second->p_offset <= file_offset && file_offset < it->second->p_offset + it->second->p_filesz) { + return it->second; + } + } + throw bad_fo_exception(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), fmt::format("File offset({:#x}) doesn't point to any segment.")); + } + + [[nodiscard]] + Elf64_Phdr* elf64_interpreter::elf_program_header_from_rva(rva_t rva) const { + return elf_program_header_from_va(convert_rva_to_va(rva)); + } + + [[nodiscard]] + Elf64_Phdr* elf64_interpreter::elf_program_header_from_va(va_t va) const { + auto it = m_segment_va_lookup_table.upper_bound(va); + if (it != m_segment_va_lookup_table.begin()) { + --it; + if (it->second->p_vaddr <= va && va < it->second->p_vaddr + it->second->p_memsz) { + return it->second; + } + } + throw bad_va_exception(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), fmt::format("Invalid virtual address({:#016x}).", va)); + } + + [[nodiscard]] + size_t elf64_interpreter::elf_program_headers_num() const noexcept { + return m_elf_header->e_shnum; + } + + [[nodiscard]] + Elf64_Shdr* elf64_interpreter::elf_section_header(size_t n) const { + if (n < m_elf_header->e_shnum) { + return m_elf_section_headers + n; + } else { + throw exceptions::index_exception(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), "Out of range."); + } + } + + [[nodiscard]] + Elf64_Shdr* elf64_interpreter::elf_section_header(std::string_view section_name) const { + auto it = m_section_name_lookup_table.find(section_name); + if (it != m_section_name_lookup_table.end()) { + return it->second; + } else { + throw exceptions::key_exception(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), fmt::format("Section `{}` is not found.", section_name.data())); + } + } + + [[nodiscard]] + size_t elf64_interpreter::elf_section_headers_num() const noexcept { + return m_elf_header->e_shnum; + } + + [[nodiscard]] + elf64_interpreter::fo_t elf64_interpreter::convert_rva_to_fo(rva_t rva) const { + return convert_va_to_fo(convert_rva_to_va(rva)); + } + + [[nodiscard]] + elf64_interpreter::fo_t elf64_interpreter::convert_va_to_fo(va_t va) const { + auto it = m_segment_va_lookup_table.upper_bound(va); + if (it != m_segment_va_lookup_table.begin()) { + --it; + if (it->second->p_vaddr <= va && va < it->second->p_vaddr + it->second->p_memsz) { + if (va - it->second->p_vaddr < it->second->p_filesz) { + return it->second->p_offset + (va - it->second->p_vaddr); + } else { + throw bad_va_exception(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), fmt::format("Virtual address({:#016x}) doesn't have corresponding file offset.", va)); + } + } + } + throw bad_va_exception(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), fmt::format("Invalid virtual address({:#016x})", va)); + } + + [[nodiscard]] + elf64_interpreter::rva_t elf64_interpreter::convert_fo_to_rva(fo_t file_offset) const { + return convert_va_to_rva(convert_fo_to_va(file_offset)); + } + + elf64_interpreter::rva_t elf64_interpreter::convert_va_to_rva(va_t va) const { + return va - m_segment_va_lookup_table.begin()->first; + } + + [[nodiscard]] + elf64_interpreter::va_t elf64_interpreter::convert_fo_to_va(fo_t file_offset) const { + auto it = m_segment_fo_lookup_table.upper_bound(file_offset); + if (it != m_segment_fo_lookup_table.begin()) { + --it; + if (it->second->p_offset <= file_offset && file_offset < it->second->p_offset + it->second->p_filesz) { + return it->second->p_vaddr + (file_offset - it->second->p_offset); + } + } + throw bad_fo_exception(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), fmt::format("File offset({:#x}) doesn't have corresponding virtual address.", file_offset)); + } + + [[nodiscard]] + elf64_interpreter::va_t elf64_interpreter::convert_rva_to_va(rva_t rva) const { + return m_segment_va_lookup_table.begin()->first + rva; + } + + [[nodiscard]] + std::optional elf64_interpreter::elf_dynamic_rela() const { + return m_dynamic_rela ? std::make_optional(m_dynamic_rela->d_un.d_ptr) : std::nullopt; + } + + [[nodiscard]] + std::optional elf64_interpreter::elf_dynamic_relasz() const { + return m_dynamic_relasz ? std::make_optional(m_dynamic_relasz->d_un.d_ptr) : std::nullopt; + } + + [[nodiscard]] + std::optional elf64_interpreter::elf_dynamic_rel() const { + return m_dynamic_rel ? std::make_optional(m_dynamic_rel->d_un.d_ptr) : std::nullopt; + } + + [[nodiscard]] + std::optional elf64_interpreter::elf_dynamic_relsz() const { + return m_dynamic_relsz ? std::make_optional(m_dynamic_relsz->d_un.d_ptr) : std::nullopt; + } + + [[nodiscard]] + std::optional elf64_interpreter::elf_dynamic_pltgot() const { + return m_dynamic_pltgot ? std::make_optional(m_dynamic_pltgot->d_un.d_ptr) : std::nullopt; + } + + [[nodiscard]] + std::optional elf64_interpreter::elf_dynamic_jmprel() const { + return m_dynamic_jmprel ? std::make_optional(m_dynamic_jmprel->d_un.d_ptr) : std::nullopt; + } + + [[nodiscard]] + std::optional elf64_interpreter::elf_dynamic_pltrel() const { + return m_dynamic_pltrel ? std::make_optional(m_dynamic_pltrel->d_un.d_val) : std::nullopt; + } + + [[nodiscard]] + std::optional elf64_interpreter::elf_dynamic_pltrelsz() const { + return m_dynamic_pltrelsz ? std::make_optional(m_dynamic_pltrelsz->d_un.d_val) : std::nullopt; + } + + [[nodiscard]] + std::optional elf64_interpreter::elf_dynamic_symtab() const { + return m_dynamic_symtab ? std::make_optional(m_dynamic_symtab->d_un.d_ptr) : std::nullopt; + } + + [[nodiscard]] + std::optional elf64_interpreter::elf_dynamic_strtab() const { + return m_dynamic_strtab ? std::make_optional(m_dynamic_strtab->d_un.d_ptr) : std::nullopt; + } + + [[nodiscard]] + const std::map& elf64_interpreter::relocation_distribute() const { + return m_relocation_distribute; + } +} + +#undef NKG_CURRENT_SOURCE_LINE +#undef NKG_CURRENT_SOURCE_FILE diff --git a/navicat-patcher/elf64_interpreter.hpp b/navicat-patcher/elf64_interpreter.hpp new file mode 100644 index 0000000..377f0d3 --- /dev/null +++ b/navicat-patcher/elf64_interpreter.hpp @@ -0,0 +1,239 @@ +#pragma once +#include +#include +#include + +#include +#include +#include + +#include "exception.hpp" +#include "memory_utility.hpp" + +namespace nkg { + + class elf64_interpreter { + public: + using fo_t = uintptr_t; + using rva_t = uintptr_t; + using va_t = uintptr_t; + + class parse_error : public ::nkg::exception { + public: + parse_error(std::string_view file, int line, std::string_view message) noexcept : + ::nkg::exception(file, line, message) {} + }; + + class bad_fo_exception : public ::nkg::exception { + public: + bad_fo_exception(std::string_view file, int line, std::string_view message) noexcept : + ::nkg::exception(file, line, message) {} + }; + + class bad_va_exception : public ::nkg::exception { + public: + bad_va_exception(std::string_view file, int line, std::string_view message) noexcept : + ::nkg::exception(file, line, message) {} + }; + + private: + size_t m_elf_size; + Elf64_Ehdr* m_elf_header; + Elf64_Phdr* m_elf_program_headers; + Elf64_Shdr* m_elf_section_headers; + + std::map m_segment_va_lookup_table; + std::map m_segment_fo_lookup_table; + + std::map m_section_va_lookup_table; + std::map m_section_fo_lookup_table; + std::map m_section_name_lookup_table; + + Elf64_Dyn* m_dynamic_rela; + Elf64_Dyn* m_dynamic_relasz; + Elf64_Dyn* m_dynamic_rel; + Elf64_Dyn* m_dynamic_relsz; + Elf64_Dyn* m_dynamic_pltgot; + Elf64_Dyn* m_dynamic_jmprel; + Elf64_Dyn* m_dynamic_pltrel; + Elf64_Dyn* m_dynamic_pltrelsz; + Elf64_Dyn* m_dynamic_symtab; + Elf64_Dyn* m_dynamic_strtab; + + std::map m_relocation_distribute; + + elf64_interpreter(); + + public: + [[nodiscard]] + static elf64_interpreter parse(void* image_ptr, size_t image_size); + + [[nodiscard]] + size_t elf_size() const noexcept; + + template + [[nodiscard]] + return_t elf_base() const noexcept { + static_assert(std::is_pointer_v); + return reinterpret_cast(m_elf_header); + } + + template + [[nodiscard]] + return_t elf_offset(fo_t offset) const noexcept { + static_assert(std::is_pointer_v); + return address_offset_cast(m_elf_header, offset); + } + + [[nodiscard]] + Elf64_Ehdr* elf_header() const noexcept; + + [[nodiscard]] + Elf64_Phdr* elf_program_header(size_t n) const; + + [[nodiscard]] + Elf64_Phdr* elf_program_header_from_fo(fo_t file_offset) const; + + [[nodiscard]] + Elf64_Phdr* elf_program_header_from_rva(rva_t rva) const; + + [[nodiscard]] + Elf64_Phdr* elf_program_header_from_va(va_t va) const; + + [[nodiscard]] + size_t elf_program_headers_num() const noexcept; + + [[nodiscard]] + Elf64_Shdr* elf_section_header(size_t n) const; + + [[nodiscard]] + Elf64_Shdr* elf_section_header(std::string_view section_name) const; + + [[nodiscard]] + size_t elf_section_headers_num() const noexcept; + + template + [[nodiscard]] + return_t elf_section_view(size_t n, fo_t offset = 0) const { + return elf_offset(elf_section_header(n)->sh_offset + offset); + } + + template + [[nodiscard]] + return_t elf_section_view(std::string_view section_name, fo_t offset = 0) const { + return elf_offset(elf_section_header(section_name)->sh_offset + offset); + } + + template + [[nodiscard]] + fo_t convert_ptr_to_fo(ptr_t ptr) const { + return address_delta(ptr, m_elf_header); + } + + [[nodiscard]] + fo_t convert_rva_to_fo(rva_t rva) const; + + [[nodiscard]] + fo_t convert_va_to_fo(va_t va) const; + + [[nodiscard]] + rva_t convert_fo_to_rva(fo_t file_offset) const; + + template + [[nodiscard]] + rva_t convert_ptr_to_rva(ptr_t ptr) const { + return convert_fo_to_rva(convert_ptr_to_fo(ptr)); + } + + rva_t convert_va_to_rva(va_t va) const; + + [[nodiscard]] + va_t convert_fo_to_va(fo_t file_offset) const; + + [[nodiscard]] + va_t convert_rva_to_va(rva_t rva) const; + + template + va_t convert_ptr_to_va(ptr_t ptr) const { + return convert_fo_to_va(convert_ptr_to_fo(ptr)); + } + + template + [[nodiscard]] + ptr_t convert_fo_to_ptr(fo_t offset) const { + return elf_offset(offset); + } + + template + [[nodiscard]] + ptr_t convert_rva_to_ptr(rva_t rva) const { + return convert_fo_to_ptr(convert_rva_to_fo(rva)); + } + + template + [[nodiscard]] + ptr_t convert_va_to_ptr(va_t va) const { + return convert_fo_to_ptr(convert_va_to_fo(va)); + } + + template + [[nodiscard]] + return_t search_section(size_t n, callable_t&& pred_func) const noexcept { + auto sect_hdr = elf_section_header(n); + auto sect_view = elf_offset(sect_hdr->sh_offset); + for (size_t i = 0; i < sect_hdr->sh_size; ++i) { + if (pred_func(sect_view, i, sect_hdr->sh_size)) { + return reinterpret_cast(sect_view + i); + } + } + return nullptr; + } + + template + [[nodiscard]] + return_t search_section(std::string_view section_name, callable_t&& pred_func) const noexcept { + auto sect_hdr = elf_section_header(section_name); + auto sect_view = elf_offset(sect_hdr->sh_offset); + for (size_t i = 0; i < sect_hdr->sh_size; ++i) { + if (pred_func(sect_view, i, sect_hdr->sh_size)) { + return reinterpret_cast(sect_view + i); + } + } + return nullptr; + } + + [[nodiscard]] + std::optional elf_dynamic_rela() const; + + [[nodiscard]] + std::optional elf_dynamic_relasz() const; + + [[nodiscard]] + std::optional elf_dynamic_rel() const; + + [[nodiscard]] + std::optional elf_dynamic_relsz() const; + + [[nodiscard]] + std::optional elf_dynamic_pltgot() const; + + [[nodiscard]] + std::optional elf_dynamic_jmprel() const; + + [[nodiscard]] + std::optional elf_dynamic_pltrel() const; + + [[nodiscard]] + std::optional elf_dynamic_pltrelsz() const; + + [[nodiscard]] + std::optional elf_dynamic_symtab() const; + + [[nodiscard]] + std::optional elf_dynamic_strtab() const; + + [[nodiscard]] + const std::map& relocation_distribute() const; + }; + +} diff --git a/navicat-patcher/keystone_assembler.cpp b/navicat-patcher/keystone_assembler.cpp new file mode 100644 index 0000000..5c25223 --- /dev/null +++ b/navicat-patcher/keystone_assembler.cpp @@ -0,0 +1,40 @@ +#include "keystone_assembler.hpp" +#include "resource_traits/keystone/keystone_alloc.hpp" + +#define NKG_CURRENT_SOURCE_FILE() u8".\\navicat-patcher\\keystone_assembler.cpp" +#define NKG_CURRENT_SOURCE_LINE() __LINE__ + +namespace nkg { + + keystone_assembler::keystone_assembler(ks_arch architecture, ks_mode mode) { + auto err = ks_open(architecture, mode, m_keystone_engine.unsafe_addressof()); + if (err != KS_ERR_OK) { + throw backend_error(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), err, u8"ks_open failed."); + } + } + + void keystone_assembler::option(ks_opt_type option_type, size_t option_value) { + auto err = ks_option(m_keystone_engine.get(), option_type, option_value); + if (err != KS_ERR_OK) { + throw backend_error(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), err, u8"ks_option failed."); + } + } + + [[nodiscard]] + std::vector keystone_assembler::assemble(std::string_view asm_string, uint64_t asm_address) const { + resource_wrapper machine_code{ resource_traits::keystone::keystone_alloc{} }; + + size_t machine_code_size = 0; + size_t stat_count = 0; + if (ks_asm(m_keystone_engine.get(), asm_string.data(), asm_address, machine_code.unsafe_addressof(), &machine_code_size, &stat_count) < 0) { + auto err = ks_errno(m_keystone_engine.get()); + throw backend_error(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), err, u8"ks_asm failed."); + } + + return std::vector(machine_code.get(), machine_code.get() + machine_code_size); + } + +} + +#undef NKG_CURRENT_SOURCE_LINE +#undef NKG_CURRENT_SOURCE_FILE diff --git a/navicat-patcher/keystone_assembler.hpp b/navicat-patcher/keystone_assembler.hpp new file mode 100644 index 0000000..60a910c --- /dev/null +++ b/navicat-patcher/keystone_assembler.hpp @@ -0,0 +1,55 @@ +#pragma once +#include +#include +#include + +#include "resource_wrapper.hpp" +#include "resource_traits/keystone/keystone_handle.hpp" + +#include "exception.hpp" + +namespace nkg { + + class keystone_assembler { + public: + class backend_error : public ::nkg::exception { + public: + using error_code_t = ks_err; + + private: + error_code_t m_error_code; + std::string m_error_string; + + public: + backend_error(std::string_view file, int line, error_code_t keystone_err, std::string_view message) noexcept : + ::nkg::exception(file, line, message), m_error_code(keystone_err), m_error_string(ks_strerror(keystone_err)) {} + + [[nodiscard]] + virtual bool error_code_exists() const noexcept override { + return true; + } + + [[nodiscard]] + virtual intptr_t error_code() const noexcept override { + return m_error_code; + } + + [[nodiscard]] + virtual const std::string& error_string() const noexcept override { + return m_error_string; + } + }; + + private: + resource_wrapper m_keystone_engine; + + public: + keystone_assembler(ks_arch architecture, ks_mode mode); + + void option(ks_opt_type option_type, size_t option_value); + + [[nodiscard]] + std::vector assemble(std::string_view asm_string, uint64_t asm_address = 0) const; + }; + +} diff --git a/navicat-patcher/main.cpp b/navicat-patcher/main.cpp new file mode 100644 index 0000000..21f7fb0 --- /dev/null +++ b/navicat-patcher/main.cpp @@ -0,0 +1,265 @@ +#include +#include + +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include "resource_wrapper.hpp" +#include "resource_traits/cxx_object_traits.hpp" +#include "resource_traits/unix_os/file_descriptor.hpp" +#include "resource_traits/unix_os/map_view.hpp" + +#include "rsa_cipher.hpp" +#include "elf64_interpreter.hpp" +#include "patch_solution.hpp" +#include "patch_solution_since_16.0.7.0.hpp" + +#include "exception.hpp" +#include "exceptions/unix_exception.hpp" +#include "exceptions/operation_canceled_exception.hpp" + +#define NKG_CURRENT_SOURCE_FILE() u8".\\navicat-patcher\\main.cpp" +#define NKG_CURRENT_SOURCE_LINE() __LINE__ + +void welcome() { + puts("***************************************************"); + puts("* navicat-patcher by @DoubleLabyrinth *"); + puts("* version: 16.0.7.0 *"); + puts("***************************************************"); +} + +void select_patch_solutions(nkg::resource_wrapper>& solution0) { + return; +} + +void load_rsa_privkey(nkg::rsa_cipher& cipher, std::filesystem::path& rsa_key_file, nkg::patch_solution* solution0) { + if (!rsa_key_file.empty()) { + printf("[*] Import RSA-2048 private key from\n"); + printf(" %s\n", rsa_key_file.native().c_str()); + + cipher.import_private_key_file(rsa_key_file.native()); + + if (solution0 && !solution0->check_rsa_privkey(cipher)) { + throw nkg::exception(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), "The RSA private key you provide cannot be used."); + } + } else { + printf("[*] Generating new RSA private key, it may take a long time...\n"); + + do { + cipher.generate_key(2048); + } while (solution0 && !solution0->check_rsa_privkey(cipher)); // re-generate RSA key if one of `check_rsa_privkey` returns false + } + + printf("[*] Your RSA private key:\n%s\n", cipher.export_private_key_string().c_str()); +} + +template +bool all_patch_solutions_are_suppressed(args_t&&... args) { + return (!args.is_valid() && ...); +} + +void detect_backup(const std::filesystem::path& file_path) { + std::filesystem::path backup_path = file_path.native() + ".bak"; + if (std::filesystem::is_regular_file(backup_path)) { + while (true) { + printf("[*] Previous backup %s is detected. Delete? (y/n)", backup_path.native().c_str()); + + auto select = getchar(); + while (select != '\n' && getchar() != '\n') {} + + if (select == 'Y' || select == 'y') { + std::filesystem::remove(backup_path); + break; + } else if (select == 'N' || select == 'n') { + throw nkg::exceptions::operation_canceled_exception(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), u8"Backup file still exists. Patch abort!"); + } else { + continue; + } + } + } +} + +void make_backup(const std::filesystem::path& file_path) { + std::filesystem::path backup_path = file_path.native() + ".bak"; + if (std::filesystem::exists(backup_path)) { + throw nkg::exception(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), "Previous backup is detected.") + .push_hint(fmt::format("Please delete {} and try again.", backup_path.native())); + } else { + std::filesystem::copy_file(file_path, backup_path); + } +} + +int main(int argc, char* argv[]) { + welcome(); + + bool dry_run = false; + std::filesystem::path navicat_root; + std::filesystem::path rsa_key_file; + + cxxopts::Options cmd_parser{ "navicat-patcher" }; + cmd_parser.add_options() + ("dry-run", "Run patcher without applying any patches", cxxopts::value(dry_run)) + ("navicat-root", "Navicat root directory", cxxopts::value(navicat_root)) + ("rsa-key-file", "RSA-2048 private key file", cxxopts::value(rsa_key_file)) + ("h,help", "Print help"); + + cmd_parser.positional_help(" [RSA-2048 private key file]"); + cmd_parser.parse_positional({ "navicat-root", "rsa-key-file" }); + + try { + auto cmd_result = cmd_parser.parse(argc, argv); + + if (cmd_result.count("help")) { + puts(cmd_parser.help().c_str()); + return 0; + } + + if (cmd_result.count("navicat-root") == 0) { + puts(cmd_parser.help().c_str()); + return 0; + } + + puts(""); + + if (!std::filesystem::is_directory(navicat_root)) { + throw nkg::exception(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), "Navicat root path doesn't point to a directory.") + .push_hint("Are you sure the path you specified is correct?") + .push_hint(fmt::format("The path you specified: {}", navicat_root.native())); + } + + if (!rsa_key_file.empty() && !std::filesystem::is_regular_file(rsa_key_file)) { + throw nkg::exception(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), "RSA private key file path doesn't point to a file.") + .push_hint("Are you sure the path you specified is correct?") + .push_hint(fmt::format("The path you specified: {}", rsa_key_file.native())); + } + + nkg::rsa_cipher cipher; + + std::filesystem::path libcc_filepath = navicat_root / "usr" / "lib" / "libcc.so"; + nkg::resource_wrapper libcc_fd{ nkg::resource_traits::unix_os::file_descriptor{} }; + nkg::resource_wrapper libcc_stat{ nkg::resource_traits::cxx_object_traits{} }; + nkg::resource_wrapper libcc_map_view{ nkg::resource_traits::unix_os::map_view{}, [&libcc_stat](void* p) { munmap(p, libcc_stat->st_size); } }; + std::optional libcc_interpreter; + + nkg::resource_wrapper solution0{ nkg::resource_traits::cxx_object_traits{} }; + + // open libcc.dll + libcc_fd.set(open(libcc_filepath.native().c_str(), O_RDWR)); + if (libcc_fd.is_valid()) { + printf("[+] Try to open libcc.dll ... OK!\n"); + } else { + if (errno == ENOENT) { + printf("[-] Try to open libcc.dll ... NOT FOUND!\n"); + } else { + throw nkg::exceptions::unix_exception(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), errno, "open failed."); + } + } + + if (libcc_fd.is_valid()) { + libcc_stat.set(new struct stat()); + if (fstat(libcc_fd.get(), libcc_stat.get()) != 0) { + throw nkg::exceptions::unix_exception(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), errno, "fstat failed."); + } + + libcc_map_view.set(mmap(nullptr, libcc_stat->st_size, PROT_READ | PROT_WRITE, MAP_SHARED, libcc_fd.get(), 0)); + if (!libcc_map_view.is_valid()) { + throw nkg::exceptions::unix_exception(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), errno, "mmap failed."); + } + + libcc_interpreter = nkg::elf64_interpreter::parse(libcc_map_view.get(), libcc_stat->st_size); + + solution0.set(new nkg::patch_solution_since<16, 0, 7, 0>(libcc_interpreter.value())); + } + + puts(""); + + // find patch and decide which solution will be applied + if (solution0.is_valid()) { + auto patch_found = solution0->find_patch(); + puts(""); + + if (!patch_found) { + solution0.release(); + } + } + + select_patch_solutions(solution0); + + if (all_patch_solutions_are_suppressed(solution0)) { + throw nkg::exception(NKG_CURRENT_SOURCE_FILE(), NKG_CURRENT_SOURCE_LINE(), "All patch solutions are suppressed. Patch abort!") + .push_hint("Are you sure your navicat has not been patched/modified before?"); + } + + // load key + load_rsa_privkey(cipher, rsa_key_file, solution0.get()); + + // apply patch solutions + if (dry_run) { + puts("*******************************************************"); + puts("* DRY-RUN MODE ENABLE! *"); + puts("* NO PATCH WILL BE APPLIED! *"); + puts("*******************************************************"); + } else { + // save private key if not given + if (rsa_key_file.empty()) { + cipher.export_private_key_file(u8"RegPrivateKey.pem"); + } + + // detecting backups + if (solution0.is_valid()) { + detect_backup(libcc_filepath); + } + + // make backup + if (solution0.is_valid()) { + make_backup(libcc_filepath); + } + + // make patch + // no way to go back from here :-) + if (solution0.is_valid()) { + solution0->make_patch(cipher); + } + + // print new key file path + if (rsa_key_file.empty()) { + printf("[*] New RSA-2048 private key has been saved to\n"); + printf(" %s\n", (std::filesystem::current_path() / "RegPrivateKey.pem").c_str()); + } + + puts(""); + puts("*******************************************************"); + puts("* PATCH HAS BEEN DONE SUCCESSFULLY! *"); + puts("* HAVE FUN AND ENJOY~ *"); + puts("*******************************************************"); + } + + return 0; + } catch (cxxopts::OptionException&) { + puts(cmd_parser.help().c_str()); + return -1; + } catch (nkg::exception& e) { + printf("[-] %s:%d ->\n", e.source_file().c_str(), e.source_line()); + printf(" %s\n", e.custom_message().c_str()); + if (e.error_code_exists()) { + printf(" %s (0x%zx)\n", e.error_string().c_str(), e.error_code()); + } + + for (auto& hint : e.hints()) { + printf(" HINT: %s\n", hint.c_str()); + } + + return -1; + } +} + +#undef NKG_CURRENT_SOURCE_LINE +#undef NKG_CURRENT_SOURCE_FILE diff --git a/navicat-patcher/memory_utility.hpp b/navicat-patcher/memory_utility.hpp new file mode 100644 index 0000000..4eb3436 --- /dev/null +++ b/navicat-patcher/memory_utility.hpp @@ -0,0 +1,136 @@ +#pragma once +#include +#include +#include + +namespace nkg { + + template + [[nodiscard]] + inline ptrdiff_t address_delta(ptr1_t ptr1, ptr2_t ptr2) noexcept { + static_assert(std::is_pointer_v && std::is_pointer_v); + return reinterpret_cast(ptr1) - reinterpret_cast(ptr2); + } + + template + [[nodiscard]] + inline ptr_t address_offset(ptr_t ptr, ptrdiff_t off) noexcept { + static_assert(std::is_pointer_v); + return reinterpret_cast( + const_cast( + reinterpret_cast(ptr) + off + ) + ); + } + + template + [[nodiscard]] + inline return_ptr_t address_offset_cast(ptr_t ptr, ptrdiff_t off) noexcept { + static_assert(std::is_pointer_v && std::is_pointer_v); + return reinterpret_cast(address_offset(ptr, off)); + } + + template + [[nodiscard]] + inline bool is_address_in_range(ptr_t ptr, begin_ptr_t begin, end_ptr_t end) { + static_assert(std::is_pointer_v && std::is_pointer_v && std::is_pointer_v); + auto _ptr = reinterpret_cast(ptr); + auto _begin = reinterpret_cast(begin); + auto _end = reinterpret_cast(end); + return _begin <= _ptr && _ptr < _end; + } + + template + [[nodiscard]] + inline bool is_address_in_range(ptr_t ptr, base_ptr_t base, size_t size) { + static_assert(std::is_pointer_v && std::is_pointer_v); + return is_address_in_range(ptr, base, address_offset(base, size)); + } + + template + [[nodiscard]] + inline bool is_address_in_range(ptr1_t ptr1, ptr2_t ptr2, begin_ptr_t begin, end_ptr_t end) { + static_assert(std::is_pointer_v && std::is_pointer_v && std::is_pointer_v && std::is_pointer_v); + auto _ptr1 = reinterpret_cast(ptr1); + auto _ptr2 = reinterpret_cast(ptr2); + auto _begin = reinterpret_cast(begin); + auto _end = reinterpret_cast(end); + return _begin <= _ptr1 && _ptr1 <= _ptr2 && _ptr2 <= _end; + } + + template + [[nodiscard]] + inline bool is_address_in_range(ptr_t ptr, size_t size, begin_ptr_t begin, end_ptr_t end) { + static_assert(std::is_pointer_v && std::is_pointer_v && std::is_pointer_v); + return is_address_in_range(ptr, address_offset(ptr, size), begin, end); + } + + template + [[nodiscard]] + inline bool is_address_in_range(ptr1_t ptr1, ptr2_t ptr2, base_ptr_t base, size_t size) { + static_assert(std::is_pointer_v && std::is_pointer_v && std::is_pointer_v); + return is_address_in_range(ptr1, ptr2, base, address_offset(base, size)); + } + + template + [[nodiscard]] + inline bool is_address_in_range(ptr_t p, size_t s, base_ptr_t base, size_t size) { + static_assert(std::is_pointer_v); + static_assert(std::is_pointer_v); + return is_address_in_range(p, address_offset(p, s), base, address_offset(base, size)); + } + +// template +// [[nodiscard]] +// inline __ReadType AddressRead(__PtrType p) noexcept { +// static_assert(std::is_trivial_v<__ReadType> && std::is_standard_layout_v<__ReadType>); +// static_assert(std::is_pointer_v<__PtrType>); +// return *reinterpret_cast(p); +// } +// +// template +// [[nodiscard]] +// inline __ReadType AddressRead(__PtrType p, ptrdiff_t offset) noexcept { +// static_assert(std::is_trivial_v<__ReadType> && std::is_standard_layout_v<__ReadType>); +// static_assert(std::is_pointer_v<__PtrType>); +// return *reinterpret_cast( +// reinterpret_cast(p) + offset +// ); +// } +// +// template +// [[nodiscard]] +// inline __ReadType AddressRead(__PtrType p, size_t scale, ptrdiff_t index) noexcept { +// static_assert(std::is_trivial_v<__ReadType> && std::is_standard_layout_v<__ReadType>); +// static_assert(std::is_pointer_v<__PtrType>); +// return *reinterpret_cast( +// reinterpret_cast(p) + scale * index +// ); +// } +// +// template +// inline void AddressWrite(__PtrType p, const __WriteType& value) noexcept { +// static_assert(std::is_trivial_v<__WriteType> && std::is_standard_layout_v<__WriteType>); +// static_assert(std::is_pointer_v<__PtrType>); +// *reinterpret_cast(p) = value; +// } +// +// template +// inline void AddressWrite(__PtrType p, ptrdiff_t offset, const __WriteType& value) noexcept { +// static_assert(std::is_trivial_v<__WriteType> && std::is_standard_layout_v<__WriteType>); +// static_assert(std::is_pointer_v<__PtrType>); +// *reinterpret_cast( +// reinterpret_cast(p) + offset +// ) = value; +// } +// +// template +// inline void AddressWrite(__PtrType p, size_t scale, ptrdiff_t index, const __WriteType& value) noexcept { +// static_assert(std::is_trivial_v<__WriteType> && std::is_standard_layout_v<__WriteType>); +// static_assert(std::is_pointer_v<__PtrType>); +// *reinterpret_cast( +// reinterpret_cast(p) + scale * index +// ) = value; +// } + +} diff --git a/navicat-patcher/patch_solution.hpp b/navicat-patcher/patch_solution.hpp new file mode 100644 index 0000000..24fded6 --- /dev/null +++ b/navicat-patcher/patch_solution.hpp @@ -0,0 +1,19 @@ +#pragma once +#include "rsa_cipher.hpp" + +namespace nkg { + + class patch_solution { + public: + [[nodiscard]] + virtual bool find_patch() = 0; + + [[nodiscard]] + virtual bool check_rsa_privkey(const rsa_cipher& cipher) = 0; + + virtual void make_patch(const rsa_cipher& cipher) = 0; + + virtual ~patch_solution() = default; + }; + +} diff --git a/navicat-patcher/patch_solution_since.hpp b/navicat-patcher/patch_solution_since.hpp new file mode 100644 index 0000000..1e17cee --- /dev/null +++ b/navicat-patcher/patch_solution_since.hpp @@ -0,0 +1,9 @@ +#pragma once +#include "patch_solution.hpp" + +namespace nkg { + + template + class patch_solution_since; + +} diff --git a/navicat-patcher/patch_solution_since_16.0.7.0.cpp b/navicat-patcher/patch_solution_since_16.0.7.0.cpp new file mode 100644 index 0000000..3810f5d --- /dev/null +++ b/navicat-patcher/patch_solution_since_16.0.7.0.cpp @@ -0,0 +1,659 @@ +#include "patch_solution_since_16.0.7.0.hpp" +#include +#include "keystone_assembler.hpp" +#include + +namespace nkg { + + uint64_t patch_solution_since<16, 0, 7, 0>::_emulator_append_external_api_impl(amd64_emulator& x64_emulator, std::string_view api_name, const std::vector& api_impl) { + auto& external_api_impl = x64_emulator.context_get&>("external_api_impl"); + auto& external_api_impl_area_base = x64_emulator.context_get("external_api_impl_area_base"); + auto& external_api_impl_area_size = x64_emulator.context_get("external_api_impl_area_size"); + auto& external_api_impl_append_address = x64_emulator.context_get("external_api_impl_append_address"); + + auto p = external_api_impl_append_address; + if (p + api_impl.size() > external_api_impl_area_base + external_api_impl_area_size) { + auto expand_size = ((p + api_impl.size()) - (external_api_impl_area_base + external_api_impl_area_size) + 0xfff) / 0x1000 * 0x1000; + x64_emulator.mem_map(external_api_impl_area_base + external_api_impl_area_size, expand_size, UC_PROT_READ | UC_PROT_EXEC); + external_api_impl_area_size += expand_size; + } + + x64_emulator.mem_write(p, api_impl); + + external_api_impl[std::string(api_name)] = p; + external_api_impl_append_address = (p + api_impl.size() + 0xf) / 0x10 * 0x10; + return p; + } + + uint64_t patch_solution_since<16, 0, 7, 0>::_emulator_malloc(amd64_emulator& x64_emulator, size_t alloc_size) { + auto& heap_records = x64_emulator.context_get&>("heap_records"); + + auto predecessor_chunk = + std::adjacent_find(heap_records.begin(), heap_records.end(), [alloc_size](auto& chunk0, auto& chunk1) { return chunk1.first - (chunk0.first + chunk0.second) >= alloc_size; }); + + uint64_t alloc_p; + if (predecessor_chunk != heap_records.end()) { + alloc_p = predecessor_chunk->first + predecessor_chunk->second; + } else { + auto heap_base = x64_emulator.context_get("heap_base"); + auto heap_size = x64_emulator.context_get("heap_size"); + + auto free_space_base = heap_records.empty() ? heap_base : heap_records.rbegin()->first + heap_records.rbegin()->second; + auto free_space_size = heap_base + heap_size - free_space_base; + + if (free_space_size < alloc_size) { + auto heap_expand_base = heap_base + heap_size; + auto heap_expand_size = (alloc_size - free_space_size + 0xfff) / 0x1000 * 0x1000; + x64_emulator.mem_map(heap_expand_base, heap_expand_size, UC_PROT_READ | UC_PROT_WRITE); + } + + alloc_p = free_space_base; + } + + heap_records[alloc_p] = alloc_size; + + return alloc_p; + } + + void patch_solution_since<16, 0, 7, 0>::_emulator_free(amd64_emulator& x64_emulator, uint64_t alloc_p) { + auto& heap_records = x64_emulator.context_get&>("heap_records"); + + auto chunk = heap_records.find(alloc_p); + if (chunk != heap_records.end()) { + heap_records.erase(chunk); + } else { + printf("[-] patch_solution_since<16, 0, 7, 0>: emulator tries to free 0x%016lx which is not allocated by malloc.\n", alloc_p); + x64_emulator.emu_stop(); + } + } + + bool patch_solution_since<16, 0, 7, 0>::_emulator_page_fault_handler(amd64_emulator& x64_emulator, uc_mem_type access, uint64_t address, size_t size, int64_t value) { + try { + auto fault_segment = m_libcc_interpreter.elf_program_header_from_va(address); + + auto page_base = address / 0x1000 * 0x1000; + auto page_size = 0x1000; + uint32_t page_perms = UC_PROT_NONE; + + if (fault_segment->p_flags & PF_R) { + page_perms |= UC_PROT_READ; + } + if (fault_segment->p_flags & PF_W) { + page_perms |= UC_PROT_WRITE; + } + if (fault_segment->p_flags & PF_X) { + page_perms |= UC_PROT_EXEC; + } + + x64_emulator.mem_map(page_base, page_size, page_perms); + x64_emulator.mem_write(page_base, m_libcc_interpreter.convert_va_to_ptr(page_base), page_size); + + auto dynamic_pltgot = m_libcc_interpreter.elf_dynamic_pltgot(); + if (dynamic_pltgot.has_value() && page_base <= dynamic_pltgot.value() + 0x8 && dynamic_pltgot.value() + 0x10 <= page_base + page_size) { + uint64_t dead_address = x64_emulator.context_get("dead_address"); + x64_emulator.mem_write(dynamic_pltgot.value() + 0x8, &dead_address, sizeof(dead_address)); + } + + if (dynamic_pltgot.has_value() && page_base <= dynamic_pltgot.value() + 0x10 && dynamic_pltgot.value() + 0x18 <= page_base + page_size) { + uint64_t dl_runtime_resolve = x64_emulator.context_get&>("external_api_impl")["dl_runtime_resolve"]; + x64_emulator.mem_write(dynamic_pltgot.value() + 0x10, &dl_runtime_resolve, sizeof(dl_runtime_resolve)); + } + + return true; + } catch (::nkg::exception&) { + return false; + } + } + + void patch_solution_since<16, 0, 7, 0>::_emulator_dl_runtime_resolve_handler(amd64_emulator& x64_emulator, uint64_t address, size_t size) { + uint64_t rsp; + x64_emulator.reg_read(UC_X86_REG_RSP, &rsp); + + uint64_t reloc_idx; + x64_emulator.mem_read(rsp + 0x8, &reloc_idx, sizeof(reloc_idx)); + + uint64_t reloc_va; + Elf64_Sym* reloc_sym; + uint32_t reloc_type; + char* reloc_sym_name; + if (m_libcc_interpreter.elf_dynamic_pltrel().value() == DT_REL) { + auto jmp_reloc_table = m_libcc_interpreter.convert_va_to_ptr(m_libcc_interpreter.elf_dynamic_jmprel().value()); + auto symbol_table = m_libcc_interpreter.convert_va_to_ptr(m_libcc_interpreter.elf_dynamic_symtab().value()); + auto string_table = m_libcc_interpreter.convert_va_to_ptr(m_libcc_interpreter.elf_dynamic_strtab().value()); + reloc_va = jmp_reloc_table[reloc_idx].r_offset; + reloc_sym = &symbol_table[ELF64_R_SYM(jmp_reloc_table[reloc_idx].r_info)]; + reloc_type = ELF64_R_TYPE(jmp_reloc_table[reloc_idx].r_info); + reloc_sym_name = &string_table[reloc_sym->st_name]; + } else { // m_libcc_interpreter.elf_dynamic_pltrel().value() == DT_RELA + auto jmp_reloc_table = m_libcc_interpreter.convert_va_to_ptr(m_libcc_interpreter.elf_dynamic_jmprel().value()); + auto symbol_table = m_libcc_interpreter.convert_va_to_ptr(m_libcc_interpreter.elf_dynamic_symtab().value()); + auto string_table = m_libcc_interpreter.convert_va_to_ptr(m_libcc_interpreter.elf_dynamic_strtab().value()); + reloc_va = jmp_reloc_table[reloc_idx].r_offset; + reloc_sym = &symbol_table[ELF64_R_SYM(jmp_reloc_table[reloc_idx].r_info)]; + reloc_type = ELF64_R_TYPE(jmp_reloc_table[reloc_idx].r_info); + reloc_sym_name = &string_table[reloc_sym->st_name]; + } + + if (strcmp(reloc_sym_name, "_ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE9_M_appendEPKcm") == 0) { + // std::string::_M_append(char const*, unsigned long) + auto external_api_impl_va = + x64_emulator.context_get&>("external_api_impl")["_ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE9_M_appendEPKcm"]; + + x64_emulator.mem_write(reloc_va, &external_api_impl_va, sizeof(external_api_impl_va)); + + // external api address is resolved, set `qword ptr [rsp] = external_api_impl_va` in order to jump there + x64_emulator.mem_write(rsp, &external_api_impl_va, sizeof(external_api_impl_va)); + } else if (strcmp(reloc_sym_name, "_ZNKSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE7compareEPKc") == 0) { + // std::string::compare(char const*) const + auto external_api_impl_va = + x64_emulator.context_get&>("external_api_impl")["_ZNKSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE7compareEPKc"]; + + x64_emulator.mem_write(reloc_va, &external_api_impl_va, sizeof(external_api_impl_va)); + + // external api address is resolved, set `qword ptr [rsp] = external_api_impl_va` in order to jump there + x64_emulator.mem_write(rsp, &external_api_impl_va, sizeof(external_api_impl_va)); + } else if (strcmp(reloc_sym_name, "_ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE9push_backEc") == 0) { + // std::string::push_back(char) + auto external_api_impl_va = + x64_emulator.context_get&>("external_api_impl")["_ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE9push_backEc"]; + + x64_emulator.mem_write(reloc_va, &external_api_impl_va, sizeof(external_api_impl_va)); + + // external api address is resolved, set `qword ptr [rsp] = external_api_impl_va` in order to jump there + x64_emulator.mem_write(rsp, &external_api_impl_va, sizeof(external_api_impl_va)); + } else if (strcmp(reloc_sym_name, "_ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE6appendEPKc") == 0) { + // std::string::append(char const*) + m_va_pltgot_std_string_append = reloc_va; + + auto external_api_impl_va = + x64_emulator.context_get&>("external_api_impl")["_ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE6appendEPKc"]; + + x64_emulator.mem_write(reloc_va, &external_api_impl_va, sizeof(external_api_impl_va)); + + // external api address is resolved, set `qword ptr [rsp] = external_api_impl_va` in order to jump there + x64_emulator.mem_write(rsp, &external_api_impl_va, sizeof(external_api_impl_va)); + } else if (strcmp(reloc_sym_name, "_ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE6appendEPKcm") == 0) { + // std::string::append(char const*, unsigned long) + + // redirect to `std::string::_M_append(char const*, unsigned long)` + auto external_api_impl_va = + x64_emulator.context_get&>("external_api_impl")["_ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE9_M_appendEPKcm"]; + + x64_emulator.mem_write(reloc_va, &external_api_impl_va, sizeof(external_api_impl_va)); + + // external api address is resolved, set `qword ptr [rsp] = external_api_impl_va` in order to jump there + x64_emulator.mem_write(rsp, &external_api_impl_va, sizeof(external_api_impl_va)); + } else { + printf("[-] patch_solution_since<16, 0, 7, 0>: PLT GOT entry `%s` is not resolved.\n", reloc_sym_name); + x64_emulator.emu_stop(); + } + } + + void patch_solution_since<16, 0, 7, 0>::_emulator_malloc_handler(amd64_emulator& x64_emulator, uint64_t address, size_t size) { + uint64_t rdi; + x64_emulator.reg_read(UC_X86_REG_RDI, &rdi); + + uint64_t rax = _emulator_malloc(x64_emulator, rdi); + x64_emulator.reg_write(UC_X86_REG_RAX, &rax); + } + + void patch_solution_since<16, 0, 7, 0>::_emulator_free_handler(amd64_emulator& x64_emulator, uint64_t address, size_t size) { + uint64_t rdi; + x64_emulator.reg_read(UC_X86_REG_RDI, &rdi); + _emulator_free(x64_emulator, rdi); + } + + std::string patch_solution_since<16, 0, 7, 0>::_build_encoded_key(const rsa_cipher& cipher) { + auto encoded_key = cipher.export_public_key_string_pem(); + + encoded_key = std::regex_replace(encoded_key, std::regex("-----BEGIN PUBLIC KEY-----"), ""); + encoded_key = std::regex_replace(encoded_key, std::regex("-----END PUBLIC KEY-----"), ""); + encoded_key = std::regex_replace(encoded_key, std::regex("\n"), ""); + + return encoded_key; + } + + patch_solution_since<16, 0, 7, 0>::patch_solution_since(elf64_interpreter& libcc_interpreter) : + m_libcc_interpreter(libcc_interpreter), + m_va_CSRegistrationInfoFetcher_LINUX_vtable(0), + m_va_CSRegistrationInfoFetcher_LINUX_GenerateRegistrationKey(0), + m_va_pltgot_std_string_append(0) {} + + bool patch_solution_since<16, 0, 7, 0>::find_patch() { + if (m_libcc_interpreter.elf_header()->e_machine != EM_X86_64) { + printf("[-] patch_solution_since<16, 0, 7, 0>: Not x86-64 elf binary.\n"); + printf("[-] patch_solution_since<16, 0, 7, 0>: This patch solution will be suppressed.\n"); + return false; + } + + auto CSRegistrationInfoFetcher_LINUX_typeinfo_name = + m_libcc_interpreter.search_section( + ".rodata", + [](const uint8_t* base, size_t off, size_t size) { + return (size - off) >= sizeof("31CSRegistrationInfoFetcher_LINUX") && strcmp(address_offset_cast(base, off), "31CSRegistrationInfoFetcher_LINUX") == 0; + } + ); + + if (CSRegistrationInfoFetcher_LINUX_typeinfo_name == nullptr) { + printf("[-] patch_solution_since<16, 0, 7, 0>: RTTI info for CSRegistrationInfoFetcher_LINUX is not found. (failure label 0)\n"); + printf("[-] patch_solution_since<16, 0, 7, 0>: This patch solution will be suppressed.\n"); + return false; + } + + auto CSRegistrationInfoFetcher_LINUX_typeinfo_name_va = + m_libcc_interpreter.convert_ptr_to_va(CSRegistrationInfoFetcher_LINUX_typeinfo_name); + + auto CSRegistrationInfoFetcher_LINUX_typeinfo = + m_libcc_interpreter.search_section( + ".data.rel.ro", + [CSRegistrationInfoFetcher_LINUX_typeinfo_name_va](const uint8_t* base, size_t off, size_t size) { + return off % 8 == 0 && (size - (off + 8)) >= 8 && *address_offset_cast(base, off + 8) == CSRegistrationInfoFetcher_LINUX_typeinfo_name_va; + } + ); + + if (CSRegistrationInfoFetcher_LINUX_typeinfo == nullptr) { + printf("[-] patch_solution_since<16, 0, 7, 0>: RTTI info for CSRegistrationInfoFetcher_LINUX is not found. (failure label 1)\n"); + printf("[-] patch_solution_since<16, 0, 7, 0>: This patch solution will be suppressed.\n"); + return false; + } + + auto CSRegistrationInfoFetcher_LINUX_typeinfo_va = + m_libcc_interpreter.convert_ptr_to_va(CSRegistrationInfoFetcher_LINUX_typeinfo); + + auto CSRegistrationInfoFetcher_LINUX_vftable_before = + m_libcc_interpreter.search_section( + ".data.rel.ro", + [CSRegistrationInfoFetcher_LINUX_typeinfo_va](const uint8_t* base, size_t off, size_t size) { + return off % 8 == 0 && (size - off) >= 8 && *address_offset_cast(base, off) == CSRegistrationInfoFetcher_LINUX_typeinfo_va; + } + ); + + if (CSRegistrationInfoFetcher_LINUX_vftable_before == nullptr) { + printf("[-] patch_solution_since<16, 0, 7, 0>: Vftable for CSRegistrationInfoFetcher_LINUX is not found.\n"); + printf("[-] patch_solution_since<16, 0, 7, 0>: This patch solution will be suppressed.\n"); + } + + auto CSRegistrationInfoFetcher_LINUX_vftable = CSRegistrationInfoFetcher_LINUX_vftable_before + 1; + + m_va_CSRegistrationInfoFetcher_LINUX_vtable = m_libcc_interpreter.convert_ptr_to_va(CSRegistrationInfoFetcher_LINUX_vftable); + m_va_CSRegistrationInfoFetcher_LINUX_GenerateRegistrationKey = CSRegistrationInfoFetcher_LINUX_vftable[7]; + printf("[*] patch_solution_since<16, 0, 7, 0>: m_va_CSRegistrationInfoFetcher_LINUX_vtable = 0x%016lx\n", m_va_CSRegistrationInfoFetcher_LINUX_vtable); + printf("[*] patch_solution_since<16, 0, 7, 0>: m_va_CSRegistrationInfoFetcher_LINUX_GenerateRegistrationKey = 0x%016lx\n", m_va_CSRegistrationInfoFetcher_LINUX_GenerateRegistrationKey); + + amd64_emulator x64_emulator; + + x64_emulator.context_set("heap_base", uint64_t{ 0x00007fff00000000 }); + x64_emulator.context_set("heap_size", size_t{ 0x1000 * 32 }); + x64_emulator.context_set("heap_records", std::map{}); + + x64_emulator.context_set("stack_base", uint64_t{ 0x00007fffffff0000 }); + x64_emulator.context_set("stack_size", size_t{ 0x1000 * 32 }); + x64_emulator.context_set("stack_top", uint64_t{ x64_emulator.context_get("stack_base") - x64_emulator.context_get("stack_size") }); + + x64_emulator.context_set("dead_area_base", uint64_t{ 0xfffffffffffff000 }); + x64_emulator.context_set("dead_area_size", size_t{ 0x1000 }); + + x64_emulator.context_set("external_api_impl", std::map{}); + x64_emulator.context_set("external_api_impl_area_base", uint64_t{ 0xffff900000000000 }); + x64_emulator.context_set("external_api_impl_area_size", size_t{ 0 }); + x64_emulator.context_set("external_api_impl_append_address", x64_emulator.context_get("external_api_impl_area_base")); + + x64_emulator.context_set("start_address", static_cast(m_va_CSRegistrationInfoFetcher_LINUX_GenerateRegistrationKey)); + x64_emulator.context_set("dead_address", x64_emulator.context_get("dead_area_base")); + + // allocate heap + x64_emulator.mem_map(x64_emulator.context_get("heap_base"), x64_emulator.context_get("heap_size"), UC_PROT_READ | UC_PROT_WRITE); + + // allocate stack + x64_emulator.mem_map(x64_emulator.context_get("stack_top"), x64_emulator.context_get("stack_size"), UC_PROT_READ | UC_PROT_WRITE); + + // allocate dead area + x64_emulator.mem_map(x64_emulator.context_get("dead_area_base"), x64_emulator.context_get("dead_area_size"), UC_PROT_READ | UC_PROT_EXEC); + + // allocate and setup external api impl area + { + keystone_assembler x64_assembler{ KS_ARCH_X86, KS_MODE_64 }; + + auto& external_api_impl = x64_emulator.context_get&>("external_api_impl"); + auto& external_api_impl_append_address = x64_emulator.context_get("external_api_impl_append_address"); + + _emulator_append_external_api_impl + ( + x64_emulator, "dl_runtime_resolve", + x64_assembler.assemble + ( + " nop;" + " mov rax, qword ptr [rsp];" + " add rsp, 0x10;" + " cmp rax, 0;" + " je just_ret;" + " jmp rax;" + "just_ret:" + " ret;" + ) + ); + + _emulator_append_external_api_impl(x64_emulator, "malloc", x64_assembler.assemble("ret;")); + + _emulator_append_external_api_impl(x64_emulator, "free", x64_assembler.assemble("ret;")); + + _emulator_append_external_api_impl + ( + x64_emulator, "_ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE9_M_appendEPKcm", + x64_assembler.assemble + ( + fmt::format + ( + " push r12;" + " push r13;" + " push r14;" + " push r15;" + " mov r12, qword ptr [rdi];" + " mov r13, qword ptr [rdi + 0x8];" + " mov r14, 15;" + " lea rax, qword ptr [rdi + 0x10];" + " cmp r12, rax;" + " cmovne r14, qword ptr [rdi + 0x10];" + " xor r15, r15;" + " lea rax, qword ptr [r13 + rdx];" + " cmp rax, r14;" + " jbe append_string;" + "reallocate:" + " push rdi;" + " push rsi;" + " push rdx;" + " lea r14, qword ptr [r13 + rdx];" + " lea rdi, qword ptr [r14 + 0x1];" + " mov rax, {malloc:#016x};" + " call rax;" + " mov rdi, rax;" + " mov rsi, r12;" + " mov rcx, r13;" + " rep movs byte ptr [rdi], byte ptr [rsi];" + " mov r12, rax;" + " mov rdi, qword ptr [rsp + 0x10];" + " lea rax, qword ptr [rdi + 0x10];" + " mov rdi, qword ptr [rdi];" + " cmp rdi, rax;" + " je label_0;" + " mov rax, {free:#016x};" + " call rax;" + "label_0:" + " pop rdx;" + " pop rsi;" + " pop rdi;" + " mov r15b, 0x1;" + "append_string:" + " push rdi;" + " push rsi;" + " lea rdi, qword ptr [r12 + r13];" + " mov rcx, rdx;" + " rep movs byte ptr [rdi], byte ptr [rsi];" + " mov byte ptr [rdi], 0;" + " pop rsi;" + " pop rdi;" + " add r13, rdx;" + "update_string_struct:" + " mov qword ptr [rdi + 0x8], r13;" + " test r15, r15;" + " jz final;" + " mov qword ptr [rdi], r12;" + " mov qword ptr [rdi + 0x10], r14;" + "final:" + " mov rax, rdi;" + " pop r15;" + " pop r14;" + " pop r13;" + " pop r12;" + " ret;", + fmt::arg("malloc", external_api_impl["malloc"]), + fmt::arg("free", external_api_impl["free"]) + ) + ) + ); + + _emulator_append_external_api_impl + ( + x64_emulator, "_ZNKSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE7compareEPKc", + x64_assembler.assemble + ( + // rcx = strlen(rsi); + " push rsi;" + " xor rcx, rcx;" + " dec rcx;" + "strlen_loop:" + " inc rcx;" + " cmp byte ptr [rsi + rcx], 0x00;" + " jne strlen_loop;" + " pop rsi;" + // rdx = rcx; + // rcx = min(this->_M_string_length, rcx); + " mov rdx, rcx;" + " cmp qword ptr [rdi + 0x8], rcx;" + " cmovb rcx, qword ptr [rdi + 0x8];" + // if (rcx == 0 || memcmp(this->_M_dataplus, rsi, rcx) == 0) goto compare_length; + " test rcx, rcx;" + " jz compare_length;" + "memcmp:" + " push rdi;" + " push rsi;" + " mov rdi, qword ptr [rdi];" + " xchg rsi, rdi;" + " repe cmps byte ptr [rsi], byte ptr [rdi];" + " pop rsi;" + " pop rdi;" + " jl return_negative;" + " jg return_positive;" + "compare_length:" + " cmp qword ptr [rdi + 0x8], rdx;" + " ja return_positive;" + " jb return_negative;" + "return_zero:" + " xor eax, eax;" + " ret;" + "return_positive:" + " mov eax, 1;" + " ret;" + "return_negative:" + " mov eax, 0xffffffff;" + " ret;" + ) + ); + + _emulator_append_external_api_impl + ( + x64_emulator, "_ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE9push_backEc", + x64_assembler.assemble + ( + fmt::format + ( + " push rsi;" + " mov rsi, rsp;" + " mov rdx, 0x1;" + " mov rax, {_ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE9_M_appendEPKcm:#016x};" + " call rax;" + " pop rsi;" + " ret;", + fmt::arg("_ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE9_M_appendEPKcm", external_api_impl["_ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE9_M_appendEPKcm"]) + ) + ) + ); + + _emulator_append_external_api_impl + ( + x64_emulator, "_ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE6appendEPKc", + x64_assembler.assemble + ( + fmt::format + ( + " push rsi;" + " xor rdx, rdx;" + " dec rdx;" + "strlen_loop:" + " inc rdx;" + " cmp byte ptr [rsi + rdx], 0x00;" + " jne strlen_loop;" + " pop rsi;" + " mov rax, {_ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE9_M_appendEPKcm:#016x};" + " jmp rax;", + fmt::arg("_ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE9_M_appendEPKcm", external_api_impl["_ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE9_M_appendEPKcm"]) + ) + ) + ); + + x64_emulator.hook_add + (std::bind(&patch_solution_since::_emulator_dl_runtime_resolve_handler, this, std::ref(x64_emulator), std::placeholders::_1, std::placeholders::_2), external_api_impl["dl_runtime_resolve"], external_api_impl["dl_runtime_resolve"]); + + x64_emulator.hook_add + (std::bind(&patch_solution_since::_emulator_malloc_handler, this, std::ref(x64_emulator), std::placeholders::_1, std::placeholders::_2), external_api_impl["malloc"], external_api_impl["malloc"]); + + x64_emulator.hook_add + (std::bind(&patch_solution_since::_emulator_free_handler, this, std::ref(x64_emulator), std::placeholders::_1, std::placeholders::_2), external_api_impl["free"], external_api_impl["free"]); + } + + // set page fault handler + x64_emulator.hook_add + (std::bind(&patch_solution_since::_emulator_page_fault_handler, this, std::ref(x64_emulator), std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4)); + + // print all instructions' address + // x64_emulator.hook_add([](uint64_t address, size_t size) { printf("code_trace, address = 0x%016lx\n", address); }); + + // set rbp, rsp + uint64_t init_rbp = x64_emulator.context_get("stack_base") - x64_emulator.context_get("stack_size") / 4; + uint64_t init_rsp = x64_emulator.context_get("stack_base") - x64_emulator.context_get("stack_size") / 2; + + x64_emulator.reg_write(UC_X86_REG_RBP, &init_rbp); + x64_emulator.reg_write(UC_X86_REG_RSP, &init_rsp); + + // set return address + auto retaddr = x64_emulator.context_get("dead_address"); + x64_emulator.mem_write(init_rsp, &retaddr, sizeof(retaddr)); + + // set argument registers + uint64_t init_rdi = init_rsp + 0x40; // a pointer to stack memory which stores return value + x64_emulator.reg_write(UC_X86_REG_RDI, &init_rdi); + + // start emulate + try { + x64_emulator.emu_start(x64_emulator.context_get("start_address"), x64_emulator.context_get("dead_address")); + } catch (amd64_emulator::backend_error& e) { + printf("[-] patch_solution_since<16, 0, 7, 0>: Code emulation failed. %s\n", e.error_string().c_str()); + printf("[-] patch_solution_since<16, 0, 7, 0>: This patch solution will be suppressed.\n"); + return false; + } + + if (m_va_pltgot_std_string_append) { + printf("[*] patch_solution_since<16, 0, 7, 0>: m_va_pltgot_std_string_append = 0x%016lx\n", m_va_pltgot_std_string_append); + } else { + printf("[*] patch_solution_since<16, 0, 7, 0>: std::string::append(const char*) is not found.\n"); + printf("[-] patch_solution_since<16, 0, 7, 0>: This patch solution will be suppressed.\n"); + return false; + } + + // + // get result + // + // on AMD64 platform, `std::string` has follow memory layout: + // ------------------------------ + // | offset | size | + // ------------------------------ + // | +0 | 0x8 | char*: raw string ptr + // ------------------------------ + // | +0x8 | 0x8 | size_t: string length + // ------------------------------ + // | +0x10 | 0x8 | `char[16]: a small string buffer` OR `size_t: capacity` + // ------------------------------ + // + uint64_t encoded_key_length; + x64_emulator.mem_read(init_rdi + 0x8, &encoded_key_length, sizeof(encoded_key_length)); + if (encoded_key_length != official_encoded_key.length()) { + printf("[-] patch_solution_since<16, 0, 7, 0>: unexpected encoded key length(%lu).\n", encoded_key_length); + printf("[-] patch_solution_since<16, 0, 7, 0>: This patch solution will be suppressed.\n"); + return false; + } + + uint64_t encoded_key_ptr; + x64_emulator.mem_read(init_rdi, &encoded_key_ptr, sizeof(encoded_key_ptr)); + + auto encoded_key = x64_emulator.mem_read(encoded_key_ptr, encoded_key_length); + if (memcmp(encoded_key.data(), official_encoded_key.data(), encoded_key.size()) == 0) { + printf("[+] patch_solution_since<16, 0, 7, 0>: official encoded key is found.\n"); + return true; + } else { + printf("[-] patch_solution_since<16, 0, 7, 0>: official encoded key is not found.\n"); + printf("[-] patch_solution_since<16, 0, 7, 0>: This patch solution will be suppressed.\n"); + return false; + } + } + + bool patch_solution_since<16, 0, 7, 0>::check_rsa_privkey(const rsa_cipher& cipher) { + return cipher.bits() == 2048; + } + + void patch_solution_since<16, 0, 7, 0>::make_patch(const rsa_cipher& cipher) { + auto new_encoded_key = _build_encoded_key(cipher); + + auto CSRegistrationInfoFetcher_LINUX_GenerateRegistrationKey = + m_libcc_interpreter.convert_va_to_ptr(m_va_CSRegistrationInfoFetcher_LINUX_GenerateRegistrationKey); + + std::vector patch_code_chunks; + patch_code_chunks.emplace_back("push rbp;"); + patch_code_chunks.emplace_back("mov rbp, rsp;"); + patch_code_chunks.emplace_back("lea rax, qword ptr [rdi + 0x10];"); + patch_code_chunks.emplace_back("mov qword ptr [rdi], rax;"); + patch_code_chunks.emplace_back("mov qword ptr [rdi + 0x8], 0;"); + { + std::vector push_values((new_encoded_key.length() + 1 + 7) / 8, 0); + memcpy(push_values.data(), new_encoded_key.data(), new_encoded_key.length()); + + std::for_each + ( + push_values.crbegin(), + push_values.crend(), + [&patch_code_chunks](uint64_t x) { + patch_code_chunks.emplace_back(fmt::format("mov rax, {:#016x};", x)); + patch_code_chunks.emplace_back("push rax;"); + } + ); + } + patch_code_chunks.emplace_back("mov rsi, rsp;"); + patch_code_chunks.emplace_back(fmt::format("call qword ptr [{:#016x}];", m_va_pltgot_std_string_append)); + patch_code_chunks.emplace_back("leave;"); + patch_code_chunks.emplace_back("ret;"); + + std::vector assembled_patch_code; + { + keystone_assembler x86_assembler{ KS_ARCH_X86, KS_MODE_64 }; + + auto current_va = m_va_CSRegistrationInfoFetcher_LINUX_GenerateRegistrationKey; + auto next_reloc = m_libcc_interpreter.relocation_distribute().lower_bound(current_va); + for (const auto& patch_code_chunk : patch_code_chunks) { + auto assembled_patch_code_chunk = x86_assembler.assemble(patch_code_chunk, current_va); + + while (true) { + auto next_reloc_va = next_reloc != m_libcc_interpreter.relocation_distribute().end() ? next_reloc->first : 0xffffffffffffffff; + auto next_reloc_size = next_reloc != m_libcc_interpreter.relocation_distribute().end() ? next_reloc->second : 0; + + if (current_va + assembled_patch_code_chunk.size() + 2 <= next_reloc_va) { // 2 -> size of machine code "jmp rel8" + assembled_patch_code.insert(assembled_patch_code.end(), assembled_patch_code_chunk.begin(), assembled_patch_code_chunk.end()); + current_va += assembled_patch_code_chunk.size(); + break; + } else if (current_va + 2 <= next_reloc_va) { + auto next_va = next_reloc_va + next_reloc_size; + auto assembled_jmp = x86_assembler.assemble(fmt::format("jmp {:#016x};", next_va), current_va); + auto assembled_padding = std::vector(next_va - (current_va + assembled_jmp.size()), 0x90); // 0x90 -> nop + assembled_patch_code.insert(assembled_patch_code.end(), assembled_jmp.begin(), assembled_jmp.end()); + assembled_patch_code.insert(assembled_patch_code.end(), assembled_padding.begin(), assembled_padding.end()); + current_va = next_va; + ++next_reloc; + } else { + __builtin_unreachable(); // impossible to reach here + } + } + } + } + + memcpy(CSRegistrationInfoFetcher_LINUX_GenerateRegistrationKey, assembled_patch_code.data(), assembled_patch_code.size()); + printf("[*] patch_solution_since<16, 0, 7, 0>: Patch has been done.\n"); + } +} diff --git a/navicat-patcher/patch_solution_since_16.0.7.0.hpp b/navicat-patcher/patch_solution_since_16.0.7.0.hpp new file mode 100644 index 0000000..838b26b --- /dev/null +++ b/navicat-patcher/patch_solution_since_16.0.7.0.hpp @@ -0,0 +1,48 @@ +#pragma once +#include +#include "patch_solution_since.hpp" +#include "elf64_interpreter.hpp" +#include "amd64_emulator.hpp" + +namespace nkg { + + template<> + class patch_solution_since<16, 0, 7, 0> final : public patch_solution { + private: + static inline std::string_view official_encoded_key = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAw1dqF3SkCaAAmMzs889IqdW9M2dIdh3jG9yPcmLnmJiGpBF4E9VHSMGe8oPAy2kJDmdNt4BcEygvssEfginva5t5jm352UAoDosUJkTXGQhpAWMF4fBmBpO3EedG62rOsqMBgmSdAyxCSPBRJIOFR0QgZFbRnU0frj34fiVmgYiLuZSAmIbs8ZxiHPdp1oD4tUpvsFci4QJtYNjNnGU2WPH6rvChGl1IRKrxMtqLielsvajUjyrgOC6NmymYMvZNER3htFEtL1eQbCyTfDmtYyQ1Wt4Ot12lxf0wVIR5mcGN7XCXJRHOFHSf1gzXWabRSvmt1nrl7sW6cjxljuuQawIDAQAB"; + + elf64_interpreter& m_libcc_interpreter; + elf64_interpreter::va_t m_va_CSRegistrationInfoFetcher_LINUX_vtable; + elf64_interpreter::va_t m_va_CSRegistrationInfoFetcher_LINUX_GenerateRegistrationKey; + elf64_interpreter::va_t m_va_pltgot_std_string_append; + + uint64_t _emulator_append_external_api_impl(amd64_emulator& x64_emulator, std::string_view api_name, const std::vector& api_impl); + + uint64_t _emulator_malloc(amd64_emulator& x64_emulator, size_t alloc_size); + + void _emulator_free(amd64_emulator& x64_emulator, uint64_t alloc_p); + + bool _emulator_page_fault_handler(amd64_emulator& x64_emulator, uc_mem_type access, uint64_t address, size_t size, int64_t value); + + void _emulator_dl_runtime_resolve_handler(amd64_emulator& x64_emulator, uint64_t address, size_t size); + + void _emulator_malloc_handler(amd64_emulator& x64_emulator, uint64_t address, size_t size); + + void _emulator_free_handler(amd64_emulator& x64_emulator, uint64_t address, size_t size); + + static std::string _build_encoded_key(const rsa_cipher& cipher); + + public: + patch_solution_since(elf64_interpreter& libcc_interpreter); + + [[nodiscard]] + virtual bool find_patch() override; + + [[nodiscard]] + virtual bool check_rsa_privkey(const rsa_cipher& cipher) override; + + virtual void make_patch(const rsa_cipher& cipher) override; + + }; + +}