diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..1284b4b --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,126 @@ +name: build + +on: + push: + branches: [ "cmake-build" ] + tags: + - "v*" + pull_request: + branches: [ "cmake-build" ] + +permissions: read-all + +concurrency: + group: build-${{ github.ref }} + cancel-in-progress: true + +jobs: + build: + permissions: + # for upload to release + contents: write + name: IDA ${{ matrix.ida.version }} on ${{ matrix.os.name }} + runs-on: ${{ matrix.os.runner }} + strategy: + # Set fail-fast to false to ensure that feedback is delivered for all matrix combinations. Consider changing this to true when your workflow is stable. + fail-fast: false + matrix: + os: + - runner: ubuntu-latest + name: linux + mask: "*.so" + - runner: windows-latest + name: windows + mask: "Release/*.dll" + - runner: macos-latest + name: macos + mask: "*.dylib" + ida: + - version: "9.2" + ver: "92" + + steps: + - name: Setup MSBuild + if: matrix.os.name == 'windows' + uses: microsoft/setup-msbuild@v1.1 + + - name: Setup Visual Studio 2022 + if: matrix.os.name == 'windows' + uses: ilammy/msvc-dev-cmd@v1 + with: + vsversion: 2022 + + - name: Checkout + uses: actions/checkout@v5 + with: + fetch-depth: 0 + submodules: "recursive" + + - name: Download IDA SDK ${{ matrix.ida.version }} + shell: bash + run: | + git clone https://github.com/HexRaysSA/ida-sdk.git ida-sdk + cd ida-sdk + git checkout v${{ matrix.ida.version }} + cd .. + + - name: Setup CMake + uses: jwlawson/actions-setup-cmake@v2 + with: + cmake-version: '3.28.x' + + - name: Build + shell: bash + run: | + cmake -D IDASDK_VER=${{ matrix.ida.ver }} -D IDASDK_DIR="${{ github.workspace }}/ida-sdk/src" -DCMAKE_BUILD_TYPE=Release -DIdaSdk_ROOT_DIR="${{ github.workspace }}/ida-sdk/src" -DIDA_90_STABLE=1 -B build + cmake --build build --config Release + + - name: Collect artifacts + shell: bash + run: | + mkdir -p artifacts + cp -v ./build/${{ matrix.os.mask }} artifacts/ + cp -v ida-plugin.json artifacts/ + + - name: Upload artifacts + uses: actions/upload-artifact@v4 + with: + name: openlumina-ida${{ matrix.ida.version }}-${{ matrix.os.name }} + path: artifacts/* + if-no-files-found: error + + release: + if: startsWith(github.ref, 'refs/tags/v') + needs: build + runs-on: ubuntu-latest + permissions: + contents: write + name: release for IDA ${{ matrix.ida.version }} + strategy: + fail-fast: false + matrix: + ida: + - version: "9.2" + steps: + - name: Download all artifacts + uses: actions/download-artifact@v4 + with: + path: artifacts + pattern: openlumina-ida${{ matrix.ida.version }}-* + merge-multiple: true + + - name: Create archive + shell: bash + run: | + cd artifacts + zip -r ../openlumina-ida${{ matrix.ida.version }}.zip * + cd .. + mv openlumina-ida${{ matrix.ida.version }}.zip artifacts/ + + - name: Upload release assets + uses: softprops/action-gh-release@v2 + with: + files: artifacts/*.zip + fail_on_unmatched_files: false + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore index 8f7d771..5b65450 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .vs/* OpenLumina/x64/* OpenLumina/*.user +out/* x64/* diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..837ead2 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,95 @@ +cmake_minimum_required(VERSION 3.27) + +if (APPLE) + set(CMAKE_OSX_ARCHITECTURES "arm64;x86_64" CACHE STRING "" FORCE) +endif() + +project(openlumina C CXX) + +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + +if (WIN32) + #set(CMAKE_C_FLAGS ${CMAKE_C_FLAGS} "-fPIC") + #set(CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS} "-fPIC") +elseif (APPLE) + set(CMAKE_C_FLAGS ${CMAKE_C_FLAGS} "-fPIC") + set(CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS} "-fPIC") +elseif (UNIX) + set(CMAKE_C_FLAGS ${CMAKE_C_FLAGS} "-fPIC") + set(CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS} "-fPIC") +else() + message(STATUS "Unknown platform!") +endif() + +if (CMAKE_BUILD_TYPE MATCHES Release) + message(STATUS "Force CMAKE_INSTALL_DO_STRIP in Release") + set(CMAKE_INSTALL_DO_STRIP ON) +else() + set(CMAKE_INSTALL_DO_STRIP OFF) +endif() + +list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake) + +#if (UNIX OR APPLE) +# find_package(OpenSSL REQUIRED) +# if (OPENSSL_FOUND) +# include_directories(${OPENSSL_INCLUDE_DIR}) +# message(STATUS "Found OpenSSL ${OPENSSL_VERSION} at ${OPENSSL_INCLUDE_DIR}") +# else() +# message(STATUS "OpenSSL Not Found") +# endif() +#endif() +find_package(IdaSdk REQUIRED) + +include_directories(${PROJECT_SOURCE_DIR}) + +if (WIN32) + set(PLATFORM_SUFFIX "win32") +elseif (APPLE) + set(PLATFORM_SUFFIX "osx") +elseif (UNIX) + set(PLATFORM_SUFFIX "elf") +endif() + +set(plthooksrc + "OpenLumina/plthook/plthook_${PLATFORM_SUFFIX}.c" + "OpenLumina/plthook/plthook.h" +) + +add_library(PltHookLib STATIC ${plthooksrc}) +set_target_properties(PltHookLib PROPERTIES LINKER_LANGUAGE C) + +set(src + "OpenLumina/framework.h" + "OpenLumina/pch.cpp" + "OpenLumina/pch.h" + "OpenLumina/plugin_ctx.h" + "OpenLumina/OpenLumina.cpp" + "OpenLumina/OpenLumina.h" +) + +if (IDA_90_STABLE) + message(STATUS "Compiling for IDA 9.0") + add_ida_plugin(OpenLumina NOEA32 ${PROJECT_SOURCE_DIR}/OpenLumina/OpenLumina.cpp) +else() + add_ida_plugin(OpenLumina ${PROJECT_SOURCE_DIR}/OpenLumina/OpenLumina.cpp) +endif() + +set_ida_target_properties(OpenLumina PROPERTIES CXX_STANDARD 20) +if (UNIX OR APPLE) + set_ida_target_properties(OpenLumina PROPERTIES LINK_FLAGS_RELEASE -s) +endif() +ida_target_include_directories(OpenLumina PRIVATE ${IdaSdk_INCLUDE_DIRS}) + +add_ida_library(OpenLuminaLib ${src}) + +if (WIN32) + ida_target_link_libraries(OpenLumina crypt32.lib) +endif() +#if (UNIX OR APPLE) +# ida_target_link_libraries(OpenLumina OpenSSL::SSL) +#endif() +ida_target_link_libraries(OpenLumina PltHookLib) +ida_target_link_libraries(OpenLumina OpenLuminaLib) diff --git a/CMakeSettings.json b/CMakeSettings.json new file mode 100644 index 0000000..5b14c23 --- /dev/null +++ b/CMakeSettings.json @@ -0,0 +1,26 @@ +{ + "configurations": [ + { + "name": "x64-Debug", + "generator": "Ninja", + "configurationType": "Debug", + "inheritEnvironments": [ "msvc_x64_x64" ], + "buildRoot": "${projectDir}\\out\\build\\${name}", + "installRoot": "${projectDir}\\out\\install\\${name}", + "cmakeCommandArgs": "-DIdaSdk_ROOT_DIR=e:/ida90/idasdk90/ -DIDA_90_STABLE=1", + "buildCommandArgs": "", + "ctestCommandArgs": "" + }, + { + "name": "x64-Release", + "generator": "Ninja", + "configurationType": "Release", + "inheritEnvironments": [ "msvc_x64_x64" ], + "buildRoot": "${projectDir}\\out\\build\\${name}", + "installRoot": "${projectDir}\\out\\install\\${name}", + "cmakeCommandArgs": "-DIdaSdk_ROOT_DIR=e:/ida90/idasdk90/ -DIDA_90_STABLE=1", + "buildCommandArgs": "", + "ctestCommandArgs": "" + } + ] +} \ No newline at end of file diff --git a/OpenLumina/OpenLumina.cpp b/OpenLumina/OpenLumina.cpp index c9ee0a2..31211ba 100644 --- a/OpenLumina/OpenLumina.cpp +++ b/OpenLumina/OpenLumina.cpp @@ -3,125 +3,269 @@ #define PLUGIN_NAME "OpenLumina" #define PLUGIN_DESC "Allows IDA to connect to third party Lumina servers" #define PLUGIN_PREFIX "OpenLumina: " +#define PLUGIN_VER __DATE__ " " __TIME__ -bool load_and_decode_certificate(bytevec_t* buffer, const char* certFilePath) +static plugin_ctx_t* s_plugin_ctx = nullptr; + +bool load_certificate(qstring& buffer, const char* certFilePath) { auto certFile = fopenRT(certFilePath); if (certFile != nullptr) { - qstring cert; - qstring line; + uint64 certSize = qfsize(certFile); - if (qgetline(&line, certFile) >= 0) - { - do - { - if (strcmp(line.c_str(), "-----BEGIN CERTIFICATE-----")) - { - if (!strcmp(line.c_str(), "-----END CERTIFICATE-----")) - break; + buffer.resize(certSize, '\0'); - if (line.length()) - cert += line; - } - } while (qgetline(&line, certFile) >= 0); - } + qfread(certFile, &buffer[0], certSize); qfclose(certFile); if ((debug & IDA_DEBUG_LUMINA) != 0) - msg(PLUGIN_PREFIX "cert read: %s\n", cert.c_str()); + msg(PLUGIN_PREFIX "load_certificate:\n%s\nlength %lu\nsize %lu\n", buffer.c_str(), buffer.length(), buffer.size()); + + bool hasHeader = strstr(buffer.c_str(), "-----BEGIN CERTIFICATE-----") != nullptr; + bool hasFooter = strstr(buffer.c_str(), "-----END CERTIFICATE-----") != nullptr; - return base64_decode(buffer, cert.c_str(), cert.length()); + return hasHeader && hasFooter; } return false; } -static plugin_ctx_t* s_plugin_ctx = nullptr; +#if __NT__ +static BOOL(WINAPI* CertAddEncodedCertificateToStore_orig)(HCERTSTORE hCertStore, DWORD dwCertEncodingType, const BYTE* pbCertEncoded, DWORD cbCertEncoded, DWORD dwAddDisposition, PCCERT_CONTEXT* ppCertContext) = CertAddEncodedCertificateToStore; + +static BOOL WINAPI CertAddEncodedCertificateToStore_hook(HCERTSTORE hCertStore, DWORD dwCertEncodingType, const BYTE* pbCertEncoded, DWORD cbCertEncoded, DWORD dwAddDisposition, PCCERT_CONTEXT* ppCertContext) +{ + if ((debug & IDA_DEBUG_LUMINA) != 0) + msg(PLUGIN_PREFIX "CertAddEncodedCertificateToStore_hook called\n"); + + if (s_plugin_ctx != nullptr && s_plugin_ctx->certificates.size() != 0) + { + for (auto cert : s_plugin_ctx->certificates) + { + DWORD pubKeySize = 2048; + uint8_t pubKey[2048]; -static BOOL(WINAPI* TrueCertGetCertificateChain)(HCERTCHAINENGINE hChainEngine, PCCERT_CONTEXT pCertContext, LPFILETIME pTime, HCERTSTORE hAdditionalStore, PCERT_CHAIN_PARA pChainPara, DWORD dwFlags, LPVOID pvReserved, PCCERT_CHAIN_CONTEXT* ppChainContext) = CertGetCertificateChain; + if (CryptStringToBinaryA(cert.c_str(), (DWORD)cert.size(), CRYPT_STRING_BASE64HEADER, pubKey, &pubKeySize, NULL, NULL)) + { + // inject our root certificate to certificate store + if (CertAddEncodedCertificateToStore_orig(hCertStore, X509_ASN_ENCODING, pubKey, pubKeySize, CERT_STORE_ADD_USE_EXISTING, nullptr)) + { + if ((debug & IDA_DEBUG_LUMINA) != 0) + msg(PLUGIN_PREFIX "added our root certificate to certificate store\n"); + } + else + { + msg(PLUGIN_PREFIX "failed to add our root certificate to certificate store!\n"); + } + } + else + { + msg(PLUGIN_PREFIX "failed to decode our root certificate from string to binary!\n"); + } + } + } -static BOOL WINAPI HookedCertGetCertificateChain(HCERTCHAINENGINE hChainEngine, PCCERT_CONTEXT pCertContext, LPFILETIME pTime, HCERTSTORE hAdditionalStore, PCERT_CHAIN_PARA pChainPara, DWORD dwFlags, LPVOID pvReserved, PCCERT_CHAIN_CONTEXT* ppChainContext) + // continue adding official root certificate to certificate store + return CertAddEncodedCertificateToStore_orig(hCertStore, dwCertEncodingType, pbCertEncoded, cbCertEncoded, dwAddDisposition, ppCertContext); +} + +static BOOL(WINAPI* CertGetCertificateChain_orig)(HCERTCHAINENGINE hChainEngine, PCCERT_CONTEXT pCertContext, LPFILETIME pTime, HCERTSTORE hAdditionalStore, PCERT_CHAIN_PARA pChainPara, DWORD dwFlags, LPVOID pvReserved, PCCERT_CHAIN_CONTEXT* ppChainContext) = CertGetCertificateChain; + +static BOOL WINAPI CertGetCertificateChain_hook(HCERTCHAINENGINE hChainEngine, PCCERT_CONTEXT pCertContext, LPFILETIME pTime, HCERTSTORE hAdditionalStore, PCERT_CHAIN_PARA pChainPara, DWORD dwFlags, LPVOID pvReserved, PCCERT_CHAIN_CONTEXT* ppChainContext) { - static bool firstCall = true; + static bool firstCall = true; - if ((debug & IDA_DEBUG_LUMINA) != 0) - msg(PLUGIN_PREFIX "HookedCertGetCertificateChain called\n"); + if ((debug & IDA_DEBUG_LUMINA) != 0) + msg(PLUGIN_PREFIX "CertGetCertificateChain_hook called\n"); - BOOL result = TrueCertGetCertificateChain(hChainEngine, pCertContext, pTime, hAdditionalStore, pChainPara, dwFlags, pvReserved, ppChainContext); + BOOL result = CertGetCertificateChain_orig(hChainEngine, pCertContext, pTime, hAdditionalStore, pChainPara, dwFlags, pvReserved, ppChainContext); - if (firstCall && result && ppChainContext && *ppChainContext) - { - if ((debug & IDA_DEBUG_LUMINA) != 0) - msg(PLUGIN_PREFIX "Overriding dwErrorStatus to 0x10000\n"); + if (firstCall && result && ppChainContext && *ppChainContext) + { + if ((debug & IDA_DEBUG_LUMINA) != 0) + msg(PLUGIN_PREFIX "Overriding dwErrorStatus to 0x10000\n"); - PCERT_CHAIN_CONTEXT pChainContext = const_cast(*ppChainContext); - pChainContext->TrustStatus.dwErrorStatus = 0x10000; - } + PCERT_CHAIN_CONTEXT pChainContext = const_cast(*ppChainContext); + pChainContext->TrustStatus.dwErrorStatus = 0x10000; + } - firstCall = !firstCall; + firstCall = !firstCall; - return result; + return result; } +#endif -static BOOL(WINAPI* TrueCertAddEncodedCertificateToStore)(HCERTSTORE hCertStore, DWORD dwCertEncodingType, const BYTE* pbCertEncoded, DWORD cbCertEncoded, DWORD dwAddDisposition, PCCERT_CONTEXT* ppCertContext) = CertAddEncodedCertificateToStore; +#if __LINUX__ || __MAC__ +static openssl_ctx crypto; -static BOOL WINAPI HookedCertAddEncodedCertificateToStore(HCERTSTORE hCertStore, DWORD dwCertEncodingType, const BYTE* pbCertEncoded, DWORD cbCertEncoded, DWORD dwAddDisposition, PCCERT_CONTEXT* ppCertContext) +int X509_STORE_add_cert_hook(X509_STORE* ctx, X509* x) { if ((debug & IDA_DEBUG_LUMINA) != 0) - msg(PLUGIN_PREFIX "HookedCertAddEncodedCertificateToStore called\n"); + msg(PLUGIN_PREFIX "X509_STORE_add_cert_hook: %p %p\n", ctx, x); - if (s_plugin_ctx != nullptr && s_plugin_ctx->decodedCert.size() != 0) + if (s_plugin_ctx != nullptr && s_plugin_ctx->certificates.size() != 0) { - // inject our root certificate to certificate store - if (!TrueCertAddEncodedCertificateToStore(hCertStore, X509_ASN_ENCODING, &s_plugin_ctx->decodedCert[0], s_plugin_ctx->decodedCert.size(), CERT_STORE_ADD_USE_EXISTING, nullptr)) - { - msg(PLUGIN_PREFIX "failed to add our root certificate to certificate store!\n"); - } - else + for (auto certStr : s_plugin_ctx->certificates) { - if ((debug & IDA_DEBUG_LUMINA) != 0) - msg(PLUGIN_PREFIX "added our root certificate to certificate store\n"); + BIO* mem = crypto.BIO_new(crypto.BIO_s_mem());; + crypto.BIO_puts(mem, certStr.c_str()); + // may be use X509 *PEM_read_X509(FILE *fp, X509 **x, pem_password_cb *cb, void *u); instead? + X509* cert = crypto.PEM_read_bio_X509(mem, NULL, 0, NULL); + crypto.BIO_free(mem); + + // inject our root certificate to certificate store + if (crypto.X509_STORE_add_cert(ctx, cert)) + { + if ((debug & IDA_DEBUG_LUMINA) != 0) + msg(PLUGIN_PREFIX "added our root certificate to certificate store\n"); + } + else + { + msg(PLUGIN_PREFIX "failed to add our root certificate to certificate store!\n"); + } + + crypto.X509_free(cert); } } - // continue adding official root certificate to certificate store - return TrueCertAddEncodedCertificateToStore(hCertStore, dwCertEncodingType, pbCertEncoded, cbCertEncoded, dwAddDisposition, ppCertContext); + // continue adding official root certificate to certificate store + return crypto.X509_STORE_add_cert(ctx, x); } +static void* (*dlopen_orig)(const char* filename, int flags) = dlopen; + +void* dlopen_hook(const char* filename, int flags) +{ + if ((debug & IDA_DEBUG_LUMINA) != 0) + msg(PLUGIN_PREFIX "dlopen_hook: %s %u\n", filename, flags); + + return dlopen_orig(filename, flags); +} + +static void* (*dlsym_orig)(void* handle, const char* symbol) = dlsym; + +void* dlsym_hook(void* handle, const char* symbol) +{ + if ((debug & IDA_DEBUG_LUMINA) != 0) + msg(PLUGIN_PREFIX "dlsym_hook: %p %s\n", handle, symbol); + + void* addr = dlsym_orig(handle, symbol); + + if (addr != nullptr && symbol != nullptr && strcmp(symbol, "X509_STORE_add_cert") == 0) + { + crypto.BIO_s_mem = (BIO_s_mem_fptr)dlsym_orig(handle, "BIO_s_mem"); + crypto.BIO_new = (BIO_new_fptr)dlsym_orig(handle, "BIO_new"); + crypto.BIO_puts = (BIO_puts_fptr)dlsym_orig(handle, "BIO_puts"); + crypto.PEM_read_bio_X509 = (PEM_read_bio_X509_fptr)dlsym_orig(handle, "PEM_read_bio_X509"); + crypto.BIO_free = (BIO_free_fptr)dlsym_orig(handle, "BIO_free"); + crypto.X509_STORE_add_cert = (X509_STORE_add_cert_fptr)addr; + crypto.X509_free = (X509_free_fptr)dlsym_orig(handle, "X509_free"); + + if ((debug & IDA_DEBUG_LUMINA) != 0) + msg("openssl: BIO_s_mem %p BIO_new %p BIO_puts %p PEM_read_bio_X509 %p BIO_free %p X509_STORE_add_cert %p X509_free %p\n", + crypto.BIO_s_mem, crypto.BIO_new, crypto.BIO_puts, crypto.PEM_read_bio_X509, crypto.BIO_free, crypto.X509_STORE_add_cert, crypto.X509_free); + + if ((debug & IDA_DEBUG_LUMINA) != 0) + msg(PLUGIN_PREFIX "returned %p for X509_STORE_add_cert\n", (void*)X509_STORE_add_cert_hook); + + return (void*)X509_STORE_add_cert_hook; + } + + return addr; +} +#endif + bool idaapi plugin_ctx_t::run(size_t arg) { msg(PLUGIN_PREFIX "plugin run called\n"); return true; } +struct file_enumerator_impl : file_enumerator_t +{ + file_enumerator_impl(plugin_ctx_t* ctx) : pc(ctx) {} + + int visit_file(const char* file) + { + if ((debug & IDA_DEBUG_LUMINA) != 0) + msg(PLUGIN_PREFIX "loading certificate: %s\n", file); + + qstring cert; + + if (load_certificate(cert, file)) + pc->certificates.add(cert); + else + msg(PLUGIN_PREFIX "failed to load certificate file!\n"); + + if ((debug & IDA_DEBUG_LUMINA) != 0) + msg(PLUGIN_PREFIX "loaded certificate: %s\n", file); + + return 0; + } +private: + plugin_ctx_t* pc = nullptr; +}; + bool plugin_ctx_t::init_hook() { - char fileNameBuffer[MAX_PATH]; + const char* ida_dir = idadir(nullptr); + + char answer[QMAXPATH]; - auto certFileName = getsysfile(fileNameBuffer, sizeof(fileNameBuffer), "hexrays.crt", nullptr); + file_enumerator_impl fe(this); +#if IDA_SDK_VERSION >= 900 + enumerate_files(answer, sizeof(answer), ida_dir, "hexrays*.crt", fe); +#else + enumerate_files2(answer, sizeof(answer), ida_dir, "hexrays*.crt", fe); +#endif - if (certFileName == nullptr) + if (certificates.size() == 0) { - msg(PLUGIN_PREFIX "can't find hexrays.crt file in your IDA folder!\n"); + msg(PLUGIN_PREFIX "can't find any hexrays*.crt files in your IDA folder!\n"); return false; } + else + { + if ((debug & IDA_DEBUG_LUMINA) != 0) + msg(PLUGIN_PREFIX "loaded %lu certificates\n", certificates.size()); + } - if ((debug & IDA_DEBUG_LUMINA) != 0) - msg(PLUGIN_PREFIX "using certificate file \"%s\"\n", certFileName); + plthook_t* plthook; - if (!load_and_decode_certificate(&decodedCert, certFileName)) - { - msg(PLUGIN_PREFIX "failed to decode certificate file!\n"); + if (plthook_open(&plthook, IDA_LIB_NAME) != 0) { + msg(PLUGIN_PREFIX "plthook_open error: %s\n", plthook_error()); return false; } - DetourTransactionBegin(); - DetourUpdateThread(GetCurrentThread()); - DetourAttach(&(PVOID&)TrueCertGetCertificateChain, HookedCertGetCertificateChain); - DetourAttach(&(PVOID&)TrueCertAddEncodedCertificateToStore, HookedCertAddEncodedCertificateToStore); - DetourTransactionCommit(); +#if __NT__ + if (plthook_replace(plthook, "CertAddEncodedCertificateToStore", (void*)CertAddEncodedCertificateToStore_hook, NULL) != 0) { + msg(PLUGIN_PREFIX "plthook_replace CertAddEncodedCertificateToStore error: %s\n", plthook_error()); + plthook_close(plthook); + return false; + } + if (plthook_replace(plthook, "CertGetCertificateChain", (void*)CertGetCertificateChain_hook, NULL) != 0) { + msg(PLUGIN_PREFIX "plthook_replace CertGetCertificateChain error: %s\n", plthook_error()); + plthook_close(plthook); + return false; + } +#endif + +#if __LINUX__ || __MAC__ + if (plthook_replace(plthook, "dlopen", (void*)dlopen_hook, NULL) != 0) { + msg(PLUGIN_PREFIX "plthook_replace dlopen error: %s\n", plthook_error()); + plthook_close(plthook); + return false; + } + if (plthook_replace(plthook, "dlsym", (void*)dlsym_hook, NULL) != 0) { + msg(PLUGIN_PREFIX "plthook_replace dlsym error: %s\n", plthook_error()); + plthook_close(plthook); + return false; + } +#endif + + plthook_close(plthook); if ((debug & IDA_DEBUG_LUMINA) != 0) msg(PLUGIN_PREFIX "certificate hook applied\n"); @@ -131,15 +275,15 @@ bool plugin_ctx_t::init_hook() plugin_ctx_t::~plugin_ctx_t() { - DetourTransactionBegin(); - DetourUpdateThread(GetCurrentThread()); - DetourDetach(&(PVOID&)TrueCertGetCertificateChain, HookedCertGetCertificateChain); - DetourDetach(&(PVOID&)TrueCertAddEncodedCertificateToStore, HookedCertAddEncodedCertificateToStore); - DetourTransactionCommit(); + // TODO: remove hooks? + + s_plugin_ctx = nullptr; } static plugmod_t* idaapi init() { + msg(PLUGIN_PREFIX "init\n"); + auto ctx = new plugin_ctx_t; if (ctx == nullptr) @@ -157,6 +301,12 @@ static plugmod_t* idaapi init() s_plugin_ctx = ctx; +#if __EA64__ + msg(PLUGIN_PREFIX "initialized (Version: " PLUGIN_VER " 64-bit by TOM_RUS)\n"); +#else + msg(PLUGIN_PREFIX "initialized (Version: " PLUGIN_VER " 32-bit by TOM_RUS)\n"); +#endif + return ctx; } diff --git a/OpenLumina/OpenLumina.h b/OpenLumina/OpenLumina.h new file mode 100644 index 0000000..467d538 --- /dev/null +++ b/OpenLumina/OpenLumina.h @@ -0,0 +1,48 @@ +#pragma once + +#if __LINUX__ || __MAC__ +// these are just copy/paste from openssl so we don't have to link against it +struct X509_STORE; +struct X509; +struct BIO_METHOD; +struct BIO; +struct pem_password_cb; + +typedef const BIO_METHOD* (*BIO_s_mem_fptr)(void); +typedef BIO* (*BIO_new_fptr)(const BIO_METHOD* type); +typedef int (*BIO_puts_fptr)(BIO* bp, const char* buf); +typedef X509* (*PEM_read_bio_X509_fptr)(BIO* out, X509** x, pem_password_cb* cb, void* u); +typedef int (*BIO_free_fptr)(BIO* a); +typedef int (*X509_STORE_add_cert_fptr)(X509_STORE* ctx, X509* x); +typedef void (*X509_free_fptr)(X509* a); + +struct openssl_ctx +{ + BIO_s_mem_fptr BIO_s_mem; + BIO_new_fptr BIO_new; + BIO_puts_fptr BIO_puts; + PEM_read_bio_X509_fptr PEM_read_bio_X509; + BIO_free_fptr BIO_free; + X509_STORE_add_cert_fptr X509_STORE_add_cert; + X509_free_fptr X509_free; +}; + +#endif + +#if __EA64__ && IDA_SDK_VERSION < 900 +#define IDA_LIB_SUFF "64" +#else +#define IDA_LIB_SUFF +#endif + +#if __NT__ +constexpr auto IDA_LIB_NAME = "ida" IDA_LIB_SUFF ".dll"; +#endif + +#if __LINUX__ +constexpr auto IDA_LIB_NAME = "libida" IDA_LIB_SUFF ".so"; +#endif + +#if __MAC__ +constexpr auto IDA_LIB_NAME = "libida" IDA_LIB_SUFF ".dylib"; +#endif diff --git a/OpenLumina/OpenLumina.vcxproj b/OpenLumina/OpenLumina.vcxproj index 3da2ecb..e5e34a0 100644 --- a/OpenLumina/OpenLumina.vcxproj +++ b/OpenLumina/OpenLumina.vcxproj @@ -187,6 +187,7 @@ + @@ -198,6 +199,12 @@ Create Create + + NotUsing + NotUsing + NotUsing + NotUsing + diff --git a/OpenLumina/OpenLumina.vcxproj.filters b/OpenLumina/OpenLumina.vcxproj.filters index 2ee6598..14239fb 100644 --- a/OpenLumina/OpenLumina.vcxproj.filters +++ b/OpenLumina/OpenLumina.vcxproj.filters @@ -21,6 +21,12 @@ Header Files + + Header Files + + + Header Files + @@ -32,5 +38,8 @@ Source Files + + Source Files + \ No newline at end of file diff --git a/OpenLumina/PropertySheet.props b/OpenLumina/PropertySheet.props index 9a61bb5..d2dcd1e 100644 --- a/OpenLumina/PropertySheet.props +++ b/OpenLumina/PropertySheet.props @@ -3,7 +3,7 @@ ..\$(Platform)\$(Configuration) - e:\ida77\idasdk77 + e:\ida90\idasdk90 diff --git a/OpenLumina/framework.h b/OpenLumina/framework.h index d7beaec..4057bf1 100644 --- a/OpenLumina/framework.h +++ b/OpenLumina/framework.h @@ -1,6 +1,10 @@ #pragma once +#if __NT__ + #define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers // Windows Header Files #include #include + +#endif diff --git a/OpenLumina/pch.h b/OpenLumina/pch.h index fabf212..2509c33 100644 --- a/OpenLumina/pch.h +++ b/OpenLumina/pch.h @@ -7,19 +7,31 @@ #ifndef PCH_H #define PCH_H +//#undef __NT__ +//#define __LINUX__ 1 +//#define __MAC__ 1 + +//#define USE_STANDARD_FILE_FUNCTIONS + // add headers that you want to pre-compile here #include "framework.h" #include +#if __LINUX__ || __MAC__ +#include +#endif + #include #include #include #include #include -#include "detours/detours.h" +//#include "detours/detours.h" +#include "plthook/plthook.h" #include "plugin_ctx.h" +#include "OpenLumina.h" #endif //PCH_H diff --git a/OpenLumina/plthook/plthook.h b/OpenLumina/plthook/plthook.h new file mode 100644 index 0000000..11e137e --- /dev/null +++ b/OpenLumina/plthook/plthook.h @@ -0,0 +1,85 @@ +/* -*- indent-tabs-mode: nil -*- + * + * plthook.h -- the header file of plthook + * + * URL: https://github.com/kubo/plthook + * + * ------------------------------------------------------ + * + * Copyright 2013-2024 Kubo Takehiro + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ''AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of the authors. + * + */ +#ifndef PLTHOOK_H +#define PLTHOOK_H 1 + +#define PLTHOOK_SUCCESS 0 +#define PLTHOOK_FILE_NOT_FOUND 1 +#define PLTHOOK_INVALID_FILE_FORMAT 2 +#define PLTHOOK_FUNCTION_NOT_FOUND 3 +#define PLTHOOK_INVALID_ARGUMENT 4 +#define PLTHOOK_OUT_OF_MEMORY 5 +#define PLTHOOK_INTERNAL_ERROR 6 +#define PLTHOOK_NOT_IMPLEMENTED 7 + +typedef struct plthook plthook_t; + +#ifdef __cplusplus +extern "C" { +#endif + +int plthook_open(plthook_t **plthook_out, const char *filename); +int plthook_open_by_handle(plthook_t **plthook_out, void *handle); +int plthook_open_by_address(plthook_t **plthook_out, void *address); +int plthook_enum(plthook_t *plthook, unsigned int *pos, const char **name_out, void ***addr_out); +int plthook_replace(plthook_t *plthook, const char *funcname, void *funcaddr, void **oldfunc); +void plthook_close(plthook_t *plthook); +const char *plthook_error(void); + +/* enumerate entries with memory protection information (bitwise-OR of PROT_READ, PROT_WRITE and PROT_EXEC) + * + * source: plthook_elf.c and plthook_osx.c + */ +int plthook_enum_with_prot(plthook_t *plthook, unsigned int *pos, const char **name_out, void ***addr_out, int *prot); + +typedef struct { + const char *name; + void **addr; +#ifdef __APPLE__ + int addend; + // memory protection information. bitwise-OR of PROT_READ, PROT_WRITE and PROT_EXEC + int prot; + char weak; +#endif +} plthook_entry_t; + +int plthook_enum_entry(plthook_t *plthook, unsigned int *pos, plthook_entry_t *entry); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif diff --git a/OpenLumina/plthook/plthook_elf.c b/OpenLumina/plthook/plthook_elf.c new file mode 100644 index 0000000..0be2d8b --- /dev/null +++ b/OpenLumina/plthook/plthook_elf.c @@ -0,0 +1,927 @@ +/* -*- indent-tabs-mode: nil -*- + * + * plthook_elf.c -- implementation of plthook for ELF format + * + * URL: https://github.com/kubo/plthook + * + * ------------------------------------------------------ + * + * Copyright 2013-2019 Kubo Takehiro + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ''AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of the authors. + * + */ +#if defined(__sun) && defined(_XOPEN_SOURCE) && !defined(__EXTENSIONS__) +#define __EXTENSIONS__ +#endif +#if defined(__linux__) && !defined(_GNU_SOURCE) +#define _GNU_SOURCE +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef __sun +#include +#include +#define ELF_TARGET_ALL +#endif /* __sun */ +#ifdef __FreeBSD__ +#include +#include +#include +#endif +#include +#include +#include "plthook.h" + +#if defined __UCLIBC__ && !defined RTLD_NOLOAD +#define RTLD_NOLOAD 0 +#endif + +#ifndef __GNUC__ +#define __attribute__(arg) +#endif + +#if defined __FreeBSD__ && defined __i386__ && __ELF_WORD_SIZE == 64 +#error 32-bit application on 64-bit OS is not supported. +#endif + +#if !defined(R_X86_64_JUMP_SLOT) && defined(R_X86_64_JMP_SLOT) +#define R_X86_64_JUMP_SLOT R_X86_64_JMP_SLOT +#endif + +#if defined __x86_64__ || defined __x86_64 +#define R_JUMP_SLOT R_X86_64_JUMP_SLOT +#define R_GLOBAL_DATA R_X86_64_GLOB_DAT +#elif defined __i386__ || defined __i386 +#define R_JUMP_SLOT R_386_JMP_SLOT +#define R_GLOBAL_DATA R_386_GLOB_DAT +#define USE_REL +#elif defined __arm__ || defined __arm +#define R_JUMP_SLOT R_ARM_JUMP_SLOT +#define R_GLOBAL_DATA R_ARM_GLOB_DAT +#define USE_REL +#elif defined __aarch64__ || defined __aarch64 /* ARM64 */ +#define R_JUMP_SLOT R_AARCH64_JUMP_SLOT +#define R_GLOBAL_DATA R_AARCH64_GLOB_DAT +#elif defined __powerpc64__ +#define R_JUMP_SLOT R_PPC64_JMP_SLOT +#define R_GLOBAL_DATA R_PPC64_GLOB_DAT +#elif defined __powerpc__ +#define R_JUMP_SLOT R_PPC_JMP_SLOT +#define R_GLOBAL_DATA R_PPC_GLOB_DAT +#elif defined __riscv +#define R_JUMP_SLOT R_RISCV_JUMP_SLOT +#if __riscv_xlen == 32 +#define R_GLOBAL_DATA R_RISCV_32 +#elif __riscv_xlen == 64 +#define R_GLOBAL_DATA R_RISCV_64 +#else +#error unsupported RISCV implementation +#endif +#elif 0 /* disabled because not tested */ && (defined __sparcv9 || defined __sparc_v9__) +#define R_JUMP_SLOT R_SPARC_JMP_SLOT +#elif 0 /* disabled because not tested */ && (defined __sparc || defined __sparc__) +#define R_JUMP_SLOT R_SPARC_JMP_SLOT +#elif 0 /* disabled because not tested */ && (defined __ia64 || defined __ia64__) +#define R_JUMP_SLOT R_IA64_IPLTMSB +#else +#error unsupported OS +#endif + +#ifdef USE_REL +#define Elf_Plt_Rel Elf_Rel +#define PLT_DT_REL DT_REL +#define PLT_DT_RELSZ DT_RELSZ +#define PLT_DT_RELENT DT_RELENT +#else +#define Elf_Plt_Rel Elf_Rela +#define PLT_DT_REL DT_RELA +#define PLT_DT_RELSZ DT_RELASZ +#define PLT_DT_RELENT DT_RELAENT +#endif + +#if defined __LP64__ +#ifndef ELF_CLASS +#define ELF_CLASS ELFCLASS64 +#endif +#define SIZE_T_FMT "lu" +#define ELF_WORD_FMT "u" +#ifdef __ANDROID__ +#define ELF_XWORD_FMT "llu" +#else +#define ELF_XWORD_FMT "lu" +#endif +#define ELF_SXWORD_FMT "ld" +#define Elf_Half Elf64_Half +#define Elf_Xword Elf64_Xword +#define Elf_Sxword Elf64_Sxword +#define Elf_Ehdr Elf64_Ehdr +#define Elf_Phdr Elf64_Phdr +#define Elf_Sym Elf64_Sym +#define Elf_Dyn Elf64_Dyn +#define Elf_Rel Elf64_Rel +#define Elf_Rela Elf64_Rela +#ifndef ELF_R_SYM +#define ELF_R_SYM ELF64_R_SYM +#endif +#ifndef ELF_R_TYPE +#define ELF_R_TYPE ELF64_R_TYPE +#endif +#else /* __LP64__ */ +#ifndef ELF_CLASS +#define ELF_CLASS ELFCLASS32 +#endif +#define SIZE_T_FMT "u" +#ifdef __sun +#define ELF_WORD_FMT "lu" +#define ELF_XWORD_FMT "lu" +#define ELF_SXWORD_FMT "ld" +#else +#define ELF_WORD_FMT "u" +#define ELF_XWORD_FMT "u" +#define ELF_SXWORD_FMT "d" +#endif +#define Elf_Half Elf32_Half +#define Elf_Xword Elf32_Word +#define Elf_Sxword Elf32_Sword +#define Elf_Ehdr Elf32_Ehdr +#define Elf_Phdr Elf32_Phdr +#define Elf_Sym Elf32_Sym +#define Elf_Dyn Elf32_Dyn +#define Elf_Rel Elf32_Rel +#define Elf_Rela Elf32_Rela +#ifndef ELF_R_SYM +#define ELF_R_SYM ELF32_R_SYM +#endif +#ifndef ELF_R_TYPE +#define ELF_R_TYPE ELF32_R_TYPE +#endif +#endif /* __LP64__ */ + +typedef struct mem_prot { + size_t start; + size_t end; + int prot; +} mem_prot_t; + +#define NUM_MEM_PROT 20 + +struct plthook { + const Elf_Sym *dynsym; + const char *dynstr; + size_t dynstr_size; + const char *plt_addr_base; + const Elf_Plt_Rel *rela_plt; + size_t rela_plt_cnt; +#ifdef R_GLOBAL_DATA + const Elf_Plt_Rel *rela_dyn; + size_t rela_dyn_cnt; +#endif + mem_prot_t mem_prot[NUM_MEM_PROT]; +}; + +static char errmsg[512]; +static size_t page_size; +#define ALIGN_ADDR(addr) ((void*)((size_t)(addr) & ~(page_size - 1))) + +static int plthook_open_executable(plthook_t **plthook_out); +static int plthook_open_shared_library(plthook_t **plthook_out, const char *filename); +static const Elf_Dyn *find_dyn_by_tag(const Elf_Dyn *dyn, Elf_Sxword tag); + +typedef struct mem_prot_iter mem_prot_iter_t; +static int mem_prot_begin(mem_prot_iter_t *iter); +static int mem_prot_next(mem_prot_iter_t *iter, mem_prot_t *mem_prot); +static void mem_prot_end(mem_prot_iter_t *iter); + +static int plthook_open_real(plthook_t **plthook_out, struct link_map *lmap); +static int plthook_set_mem_prot(plthook_t *plthook); +static int plthook_get_mem_prot(plthook_t *plthook, void *addr); +#if defined __FreeBSD__ || defined __sun +static int check_elf_header(const Elf_Ehdr *ehdr); +#endif +static void set_errmsg(const char *fmt, ...) __attribute__((__format__ (__printf__, 1, 2))); + +#if defined __ANDROID__ || defined __UCLIBC__ +struct dl_iterate_data { + char* addr; + struct link_map lmap; +}; + +static int dl_iterate_cb(struct dl_phdr_info *info, size_t size, void *cb_data) +{ + struct dl_iterate_data *data = (struct dl_iterate_data*)cb_data; + Elf_Half idx = 0; + + for (idx = 0; idx < info->dlpi_phnum; ++idx) { + const Elf_Phdr *phdr = &info->dlpi_phdr[idx]; + char* base = (char*)info->dlpi_addr + phdr->p_vaddr; + if (base <= data->addr && data->addr < base + phdr->p_memsz) { + break; + } + } + if (idx == info->dlpi_phnum) { + return 0; + } + for (idx = 0; idx < info->dlpi_phnum; ++idx) { + const Elf_Phdr *phdr = &info->dlpi_phdr[idx]; + if (phdr->p_type == PT_DYNAMIC) { + data->lmap.l_addr = info->dlpi_addr; + data->lmap.l_ld = (Elf_Dyn*)(info->dlpi_addr + phdr->p_vaddr); + return 1; + } + } + return 0; +} +#endif + +int plthook_open(plthook_t **plthook_out, const char *filename) +{ + *plthook_out = NULL; + if (filename == NULL) { + return plthook_open_executable(plthook_out); + } else { + return plthook_open_shared_library(plthook_out, filename); + } +} + +int plthook_open_by_handle(plthook_t **plthook_out, void *hndl) +{ +#if defined __ANDROID__ || defined __UCLIBC__ + const static char *symbols[] = { + "__INIT_ARRAY__", + "_end", + "_start" + }; + size_t i; + + if (hndl == NULL) { + set_errmsg("NULL handle"); + return PLTHOOK_FILE_NOT_FOUND; + } + for (i = 0; i < sizeof(symbols)/sizeof(symbols[0]); i++) { + char *addr = dlsym(hndl, symbols[i]); + if (addr != NULL) { + return plthook_open_by_address(plthook_out, addr - 1); + } + } + set_errmsg("Could not find an address in the specified handle."); + return PLTHOOK_INTERNAL_ERROR; +#else + struct link_map *lmap = NULL; + + if (hndl == NULL) { + set_errmsg("NULL handle"); + return PLTHOOK_FILE_NOT_FOUND; + } + if (dlinfo(hndl, RTLD_DI_LINKMAP, &lmap) != 0) { + set_errmsg("dlinfo error"); + return PLTHOOK_FILE_NOT_FOUND; + } + return plthook_open_real(plthook_out, lmap); +#endif +} + +int plthook_open_by_address(plthook_t **plthook_out, void *address) +{ +#if defined __FreeBSD__ + return PLTHOOK_NOT_IMPLEMENTED; +#elif defined __ANDROID__ || defined __UCLIBC__ + struct dl_iterate_data data = {0,}; + data.addr = address; + dl_iterate_phdr(dl_iterate_cb, &data); + if (data.lmap.l_ld == NULL) { + set_errmsg("Could not find memory region containing address %p", address); + return PLTHOOK_INTERNAL_ERROR; + } + return plthook_open_real(plthook_out, &data.lmap); +#else + Dl_info info; + struct link_map *lmap = NULL; + + *plthook_out = NULL; + if (dladdr1(address, &info, (void**)&lmap, RTLD_DL_LINKMAP) == 0) { + set_errmsg("dladdr error"); + return PLTHOOK_FILE_NOT_FOUND; + } + return plthook_open_real(plthook_out, lmap); +#endif +} + +static int plthook_open_executable(plthook_t **plthook_out) +{ +#if defined __ANDROID__ || defined __UCLIBC__ + return plthook_open_shared_library(plthook_out, NULL); +#elif defined __linux__ + return plthook_open_real(plthook_out, _r_debug.r_map); +#elif defined __sun + const char *auxv_file = "/proc/self/auxv"; +#define NUM_AUXV_CNT 10 + FILE *fp = fopen(auxv_file, "r"); + auxv_t auxv; + struct r_debug *r_debug = NULL; + + if (fp == NULL) { + set_errmsg("Could not open %s: %s", auxv_file, + strerror(errno)); + return PLTHOOK_INTERNAL_ERROR; + } + while (fread(&auxv, sizeof(auxv_t), 1, fp) == 1) { + if (auxv.a_type == AT_SUN_LDDATA) { + r_debug = (struct r_debug *)auxv.a_un.a_ptr; + break; + } + } + fclose(fp); + if (r_debug == NULL) { + set_errmsg("Could not find r_debug"); + return PLTHOOK_INTERNAL_ERROR; + } + return plthook_open_real(plthook_out, r_debug->r_map); +#elif defined __FreeBSD__ + return plthook_open_shared_library(plthook_out, NULL); +#else + set_errmsg("Opening the main program is not supported on this platform."); + return PLTHOOK_NOT_IMPLEMENTED; +#endif +} + +static int plthook_open_shared_library(plthook_t **plthook_out, const char *filename) +{ + void *hndl = dlopen(filename, RTLD_LAZY | RTLD_NOLOAD); +#if defined __ANDROID__ || defined __UCLIBC__ + int rv; +#else + struct link_map *lmap = NULL; +#endif + + if (hndl == NULL) { + set_errmsg("dlopen error: %s", dlerror()); + return PLTHOOK_FILE_NOT_FOUND; + } +#if defined __ANDROID__ || defined __UCLIBC__ + rv = plthook_open_by_handle(plthook_out, hndl); + dlclose(hndl); + return rv; +#else + if (dlinfo(hndl, RTLD_DI_LINKMAP, &lmap) != 0) { + set_errmsg("dlinfo error"); + dlclose(hndl); + return PLTHOOK_FILE_NOT_FOUND; + } + dlclose(hndl); + return plthook_open_real(plthook_out, lmap); +#endif +} + +static const Elf_Dyn *find_dyn_by_tag(const Elf_Dyn *dyn, Elf_Sxword tag) +{ + while (dyn->d_tag != DT_NULL) { + if (dyn->d_tag == tag) { + return dyn; + } + dyn++; + } + return NULL; +} + +#ifdef __linux__ +struct mem_prot_iter { + FILE *fp; +}; + +static int mem_prot_begin(mem_prot_iter_t *iter) +{ + iter->fp = fopen("/proc/self/maps", "r"); + if (iter->fp == NULL) { + set_errmsg("failed to open /proc/self/maps"); + return -1; + } + return 0; +} + +static int mem_prot_next(mem_prot_iter_t *iter, mem_prot_t *mem_prot) +{ + char buf[PATH_MAX]; + char perms[5]; + int bol = 1; /* beginnng of line */ + + while (fgets(buf, PATH_MAX, iter->fp) != NULL) { + unsigned long start, end; + int eol = (strchr(buf, '\n') != NULL); /* end of line */ + if (bol) { + /* The fgets reads from the beginning of a line. */ + if (!eol) { + /* The next fgets reads from the middle of the same line. */ + bol = 0; + } + } else { + /* The fgets reads from the middle of a line. */ + if (eol) { + /* The next fgets reads from the beginning of a line. */ + bol = 1; + } + continue; + } + + if (sscanf(buf, "%lx-%lx %4s", &start, &end, perms) != 3) { + continue; + } + mem_prot->start = start; + mem_prot->end = end; + mem_prot->prot = 0; + if (perms[0] == 'r') { + mem_prot->prot |= PROT_READ; + } + if (perms[1] == 'w') { + mem_prot->prot |= PROT_WRITE; + } + if (perms[2] == 'x') { + mem_prot->prot |= PROT_EXEC; + } + return 0; + } + return -1; +} + +static void mem_prot_end(mem_prot_iter_t *iter) +{ + if (iter->fp != NULL) { + fclose(iter->fp); + } +} +#elif defined __FreeBSD__ +struct mem_prot_iter { + struct kinfo_vmentry *kve; + int idx; + int num; +}; + +static int mem_prot_begin(mem_prot_iter_t *iter) +{ + iter->kve = kinfo_getvmmap(getpid(), &iter->num); + if (iter->kve == NULL) { + set_errmsg("failed to call kinfo_getvmmap()\n"); + return -1; + } + iter->idx = 0; + return 0; +} + +static int mem_prot_next(mem_prot_iter_t *iter, mem_prot_t *mem_prot) +{ + if (iter->idx >= iter->num) { + return -1; + } + struct kinfo_vmentry *kve = &iter->kve[iter->idx++]; + mem_prot->start = kve->kve_start; + mem_prot->end = kve->kve_end; + mem_prot->prot = 0; + if (kve->kve_protection & KVME_PROT_READ) { + mem_prot->prot |= PROT_READ; + } + if (kve->kve_protection & KVME_PROT_WRITE) { + mem_prot->prot |= PROT_WRITE; + } + if (kve->kve_protection & KVME_PROT_EXEC) { + mem_prot->prot |= PROT_EXEC; + } + return 0; +} + +static void mem_prot_end(mem_prot_iter_t *iter) +{ + if (iter->kve != NULL) { + free(iter->kve); + } +} +#elif defined(__sun) +struct mem_prot_iter { + FILE *fp; + prmap_t maps[20]; + size_t idx; + size_t num; +}; + +static int mem_prot_begin(mem_prot_iter_t *iter) +{ + iter->fp = fopen("/proc/self/map", "r"); + if (iter->fp == NULL) { + set_errmsg("failed to open /proc/self/map"); + return -1; + } + iter->idx = iter->num = 0; + return 0; +} + +static int mem_prot_next(mem_prot_iter_t *iter, mem_prot_t *mem_prot) +{ + prmap_t *map; + + if (iter->idx == iter->num) { + iter->num = fread(iter->maps, sizeof(iter->maps[0]), sizeof(iter->maps) / sizeof(iter->maps[0]), iter->fp); + if (iter->num == 0) { + return -1; + } + iter->idx = 0; + } + map = &iter->maps[iter->idx++]; + mem_prot->start = map->pr_vaddr; + mem_prot->end = map->pr_vaddr + map->pr_size; + mem_prot->prot = 0; + if (map->pr_mflags & MA_READ) { + mem_prot->prot |= PROT_READ; + } + if (map->pr_mflags & MA_WRITE) { + mem_prot->prot |= PROT_WRITE; + } + if (map->pr_mflags & MA_EXEC) { + mem_prot->prot |= PROT_EXEC; + } + return 0; +} + +static void mem_prot_end(mem_prot_iter_t *iter) +{ + if (iter->fp != NULL) { + fclose(iter->fp); + } +} +#else +#error Unsupported platform +#endif + +static int plthook_open_real(plthook_t **plthook_out, struct link_map *lmap) +{ + plthook_t plthook = {NULL,}; + const Elf_Dyn *dyn; + const char *dyn_addr_base = NULL; + + if (page_size == 0) { + page_size = sysconf(_SC_PAGESIZE); + } + +#if defined __linux__ + plthook.plt_addr_base = (char*)lmap->l_addr; +#if defined __riscv + const Elf_Ehdr *ehdr = (const Elf_Ehdr*)lmap->l_addr; + if (ehdr->e_type == ET_DYN) { + dyn_addr_base = (const char*)lmap->l_addr; + } +#endif +#if defined __ANDROID__ || defined __UCLIBC__ + dyn_addr_base = (const char*)lmap->l_addr; +#endif +#elif defined __FreeBSD__ || defined __sun +#if __FreeBSD__ >= 13 + const Elf_Ehdr *ehdr = (const Elf_Ehdr*)lmap->l_base; +#else + const Elf_Ehdr *ehdr = (const Elf_Ehdr*)lmap->l_addr; +#endif + int rv_ = check_elf_header(ehdr); + if (rv_ != 0) { + return rv_; + } + if (ehdr->e_type == ET_DYN) { + dyn_addr_base = (const char*)lmap->l_addr; + plthook.plt_addr_base = (const char*)lmap->l_addr; + } +#else +#error unsupported OS +#endif + + /* get .dynsym section */ + dyn = find_dyn_by_tag(lmap->l_ld, DT_SYMTAB); + if (dyn == NULL) { + set_errmsg("failed to find DT_SYMTAB"); + return PLTHOOK_INTERNAL_ERROR; + } + plthook.dynsym = (const Elf_Sym*)(dyn_addr_base + dyn->d_un.d_ptr); + + /* Check sizeof(Elf_Sym) */ + dyn = find_dyn_by_tag(lmap->l_ld, DT_SYMENT); + if (dyn == NULL) { + set_errmsg("failed to find DT_SYMTAB"); + return PLTHOOK_INTERNAL_ERROR; + } + if (dyn->d_un.d_val != sizeof(Elf_Sym)) { + set_errmsg("DT_SYMENT size %" ELF_XWORD_FMT " != %" SIZE_T_FMT, dyn->d_un.d_val, sizeof(Elf_Sym)); + return PLTHOOK_INTERNAL_ERROR; + } + + /* get .dynstr section */ + dyn = find_dyn_by_tag(lmap->l_ld, DT_STRTAB); + if (dyn == NULL) { + set_errmsg("failed to find DT_STRTAB"); + return PLTHOOK_INTERNAL_ERROR; + } + plthook.dynstr = dyn_addr_base + dyn->d_un.d_ptr; + + /* get .dynstr size */ + dyn = find_dyn_by_tag(lmap->l_ld, DT_STRSZ); + if (dyn == NULL) { + set_errmsg("failed to find DT_STRSZ"); + return PLTHOOK_INTERNAL_ERROR; + } + plthook.dynstr_size = dyn->d_un.d_val; + + /* get .rela.plt or .rel.plt section */ + dyn = find_dyn_by_tag(lmap->l_ld, DT_JMPREL); + if (dyn != NULL) { + plthook.rela_plt = (const Elf_Plt_Rel *)(dyn_addr_base + dyn->d_un.d_ptr); + dyn = find_dyn_by_tag(lmap->l_ld, DT_PLTRELSZ); + if (dyn == NULL) { + set_errmsg("failed to find DT_PLTRELSZ"); + return PLTHOOK_INTERNAL_ERROR; + } + plthook.rela_plt_cnt = dyn->d_un.d_val / sizeof(Elf_Plt_Rel); + } +#ifdef R_GLOBAL_DATA + /* get .rela.dyn or .rel.dyn section */ + dyn = find_dyn_by_tag(lmap->l_ld, PLT_DT_REL); + if (dyn != NULL) { + size_t total_size, elem_size; + + plthook.rela_dyn = (const Elf_Plt_Rel *)(dyn_addr_base + dyn->d_un.d_ptr); + dyn = find_dyn_by_tag(lmap->l_ld, PLT_DT_RELSZ); + if (dyn == NULL) { + set_errmsg("failed to find PLT_DT_RELSZ"); + return PLTHOOK_INTERNAL_ERROR; + } + total_size = dyn->d_un.d_ptr; + + dyn = find_dyn_by_tag(lmap->l_ld, PLT_DT_RELENT); + if (dyn == NULL) { + set_errmsg("failed to find PLT_DT_RELENT"); + return PLTHOOK_INTERNAL_ERROR; + } + elem_size = dyn->d_un.d_ptr; + plthook.rela_dyn_cnt = total_size / elem_size; + } +#endif + +#ifdef R_GLOBAL_DATA + if (plthook.rela_plt == NULL && plthook.rela_dyn == NULL) { + set_errmsg("failed to find either of DT_JMPREL and DT_REL"); + return PLTHOOK_INTERNAL_ERROR; + } +#else + if (plthook.rela_plt == NULL) { + set_errmsg("failed to find DT_JMPREL"); + return PLTHOOK_INTERNAL_ERROR; + } +#endif + if (plthook_set_mem_prot(&plthook)) { + return PLTHOOK_INTERNAL_ERROR; + } + + *plthook_out = malloc(sizeof(plthook_t)); + if (*plthook_out == NULL) { + set_errmsg("failed to allocate memory: %" SIZE_T_FMT " bytes", sizeof(plthook_t)); + return PLTHOOK_OUT_OF_MEMORY; + } + **plthook_out = plthook; + return 0; +} + +static int plthook_set_mem_prot(plthook_t *plthook) +{ + unsigned int pos = 0; + const char *name; + void **addr; + size_t start = (size_t)-1; + size_t end = 0; + mem_prot_iter_t iter; + mem_prot_t mem_prot; + int idx = 0; + + while (plthook_enum(plthook, &pos, &name, &addr) == 0) { + if (start > (size_t)addr) { + start = (size_t)addr; + } + if (end < (size_t)addr) { + end = (size_t)addr; + } + } + end++; + + if (mem_prot_begin(&iter) != 0) { + return PLTHOOK_INTERNAL_ERROR; + } + while (mem_prot_next(&iter, &mem_prot) == 0 && idx < NUM_MEM_PROT) { + if (mem_prot.prot != 0 && mem_prot.start < end && start < mem_prot.end) { + plthook->mem_prot[idx++] = mem_prot; + } + } + mem_prot_end(&iter); + return 0; +} + +static int plthook_get_mem_prot(plthook_t *plthook, void *addr) +{ + mem_prot_t *ptr = plthook->mem_prot; + mem_prot_t *end = ptr + NUM_MEM_PROT; + + while (ptr < end && ptr->prot != 0) { + if (ptr->start <= (size_t)addr && (size_t)addr < ptr->end) { + return ptr->prot; + } + ++ptr; + } + return 0; +} + +#if defined __FreeBSD__ || defined __sun +static int check_elf_header(const Elf_Ehdr *ehdr) +{ + static const unsigned short s = 1; + /* Check endianness at runtime. */ + unsigned char elfdata = (*(const char*)&s) ? ELFDATA2LSB : ELFDATA2MSB; + + if (ehdr == NULL) { + set_errmsg("invalid elf header address: NULL"); + return PLTHOOK_INTERNAL_ERROR; + } + + if (memcmp(ehdr->e_ident, ELFMAG, SELFMAG) != 0) { + set_errmsg("invalid file signature: 0x%02x,0x%02x,0x%02x,0x%02x", + ehdr->e_ident[0], ehdr->e_ident[1], ehdr->e_ident[2], ehdr->e_ident[3]); + return PLTHOOK_INVALID_FILE_FORMAT; + } + if (ehdr->e_ident[EI_CLASS] != ELF_CLASS) { + set_errmsg("invalid elf class: 0x%02x", ehdr->e_ident[EI_CLASS]); + return PLTHOOK_INVALID_FILE_FORMAT; + } + if (ehdr->e_ident[EI_DATA] != elfdata) { + set_errmsg("invalid elf data: 0x%02x", ehdr->e_ident[EI_DATA]); + return PLTHOOK_INVALID_FILE_FORMAT; + } + if (ehdr->e_ident[EI_VERSION] != EV_CURRENT) { + set_errmsg("invalid elf version: 0x%02x", ehdr->e_ident[EI_VERSION]); + return PLTHOOK_INVALID_FILE_FORMAT; + } + if (ehdr->e_type != ET_EXEC && ehdr->e_type != ET_DYN) { + set_errmsg("invalid file type: 0x%04x", ehdr->e_type); + return PLTHOOK_INVALID_FILE_FORMAT; + } + if (ehdr->e_version != EV_CURRENT) { + set_errmsg("invalid object file version: %" ELF_WORD_FMT, ehdr->e_version); + return PLTHOOK_INVALID_FILE_FORMAT; + } + if (ehdr->e_ehsize != sizeof(Elf_Ehdr)) { + set_errmsg("invalid elf header size: %u", ehdr->e_ehsize); + return PLTHOOK_INVALID_FILE_FORMAT; + } + if (ehdr->e_phentsize != sizeof(Elf_Phdr)) { + set_errmsg("invalid program header table entry size: %u", ehdr->e_phentsize); + return PLTHOOK_INVALID_FILE_FORMAT; + } + return 0; +} +#endif + +static int check_rel(const plthook_t *plthook, const Elf_Plt_Rel *plt, Elf_Xword r_type, const char **name_out, void ***addr_out) +{ + if (ELF_R_TYPE(plt->r_info) == r_type) { + size_t idx = ELF_R_SYM(plt->r_info); + idx = plthook->dynsym[idx].st_name; + if (idx + 1 > plthook->dynstr_size) { + set_errmsg("too big section header string table index: %" SIZE_T_FMT, idx); + return PLTHOOK_INVALID_FILE_FORMAT; + } + *name_out = plthook->dynstr + idx; + *addr_out = (void**)(plthook->plt_addr_base + plt->r_offset); + return 0; + } + return -1; +} + +int plthook_enum(plthook_t *plthook, unsigned int *pos, const char **name_out, void ***addr_out) +{ + return plthook_enum_with_prot(plthook, pos, name_out, addr_out, NULL); +} + +int plthook_enum_with_prot(plthook_t *plthook, unsigned int *pos, const char **name_out, void ***addr_out, int *prot) +{ + while (*pos < plthook->rela_plt_cnt) { + const Elf_Plt_Rel *plt = plthook->rela_plt + *pos; + int rv = check_rel(plthook, plt, R_JUMP_SLOT, name_out, addr_out); + (*pos)++; + if (rv >= 0) { + if (rv == 0 && prot != NULL) { + *prot = plthook_get_mem_prot(plthook, *addr_out); + } + return rv; + } + } +#ifdef R_GLOBAL_DATA + while (*pos < plthook->rela_plt_cnt + plthook->rela_dyn_cnt) { + const Elf_Plt_Rel *plt = plthook->rela_dyn + (*pos - plthook->rela_plt_cnt); + int rv = check_rel(plthook, plt, R_GLOBAL_DATA, name_out, addr_out); + (*pos)++; + if (rv >= 0) { + if (rv == 0 && prot != NULL) { + *prot = plthook_get_mem_prot(plthook, *addr_out); + } + return rv; + } + } +#endif + *name_out = NULL; + *addr_out = NULL; + return EOF; +} + +int plthook_replace(plthook_t *plthook, const char *funcname, void *funcaddr, void **oldfunc) +{ + size_t funcnamelen = strlen(funcname); + unsigned int pos = 0; + const char *name; + void **addr; + int rv; + + if (plthook == NULL) { + set_errmsg("invalid argument: The first argument is null."); + return PLTHOOK_INVALID_ARGUMENT; + } + while ((rv = plthook_enum(plthook, &pos, &name, &addr)) == 0) { + if (strncmp(name, funcname, funcnamelen) == 0) { + if (name[funcnamelen] == '\0' || name[funcnamelen] == '@') { + int prot = plthook_get_mem_prot(plthook, addr); + if (prot == 0) { + set_errmsg("Could not get the process memory permission at %p", + ALIGN_ADDR(addr)); + return PLTHOOK_INTERNAL_ERROR; + } + if (!(prot & PROT_WRITE)) { + if (mprotect(ALIGN_ADDR(addr), page_size, PROT_READ | PROT_WRITE) != 0) { + set_errmsg("Could not change the process memory permission at %p: %s", + ALIGN_ADDR(addr), strerror(errno)); + return PLTHOOK_INTERNAL_ERROR; + } + } + if (oldfunc) { + *oldfunc = *addr; + } + *addr = funcaddr; + if (!(prot & PROT_WRITE)) { + mprotect(ALIGN_ADDR(addr), page_size, prot); + } + return 0; + } + } + } + if (rv == EOF) { + set_errmsg("no such function: %s", funcname); + rv = PLTHOOK_FUNCTION_NOT_FOUND; + } + return rv; +} + +void plthook_close(plthook_t *plthook) +{ + if (plthook != NULL) { + free(plthook); + } +} + +const char *plthook_error(void) +{ + return errmsg; +} + +static void set_errmsg(const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + vsnprintf(errmsg, sizeof(errmsg) - 1, fmt, ap); + va_end(ap); +} diff --git a/OpenLumina/plthook/plthook_osx.c b/OpenLumina/plthook/plthook_osx.c new file mode 100644 index 0000000..35c38a9 --- /dev/null +++ b/OpenLumina/plthook/plthook_osx.c @@ -0,0 +1,1173 @@ +/* -*- indent-tabs-mode: nil -*- + * + * plthook_osx.c -- implementation of plthook for OS X + * + * URL: https://github.com/kubo/plthook + * + * ------------------------------------------------------ + * + * Copyright 2014-2024 Kubo Takehiro + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ''AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of the authors. + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "plthook.h" + +// #define PLTHOOK_DEBUG_CMD 1 +// #define PLTHOOK_DEBUG_BIND 1 +// #define PLTHOOK_DEBUG_FIXUPS 1 +// #define PLTHOOK_DEBUG_ADDR 1 + +#ifdef PLTHOOK_DEBUG_CMD +#define DEBUG_CMD(...) fprintf(stderr, __VA_ARGS__) +#else +#define DEBUG_CMD(...) +#endif + +#ifdef PLTHOOK_DEBUG_FIXUPS +#define DEBUG_FIXUPS(...) fprintf(stderr, __VA_ARGS__) +#else +#define DEBUG_FIXUPS(...) +#endif + +#ifdef PLTHOOK_DEBUG_BIND +#define DEBUG_BIND(...) fprintf(stderr, __VA_ARGS__) +#define DEBUG_BIND_IF(cond, ...) if (cond) fprintf(stderr, __VA_ARGS__) +#else +#define DEBUG_BIND(...) +#define DEBUG_BIND_IF(cond, ...) +#endif + +#ifdef PLTHOOK_DEBUG_ADDR +#include + +#define INHERIT_MAX_SIZE 11 +static char *inherit_to_str(vm_inherit_t inherit, char *buf) +{ + switch (inherit) { + case VM_INHERIT_SHARE: return "share"; + case VM_INHERIT_COPY: return "copy"; + case VM_INHERIT_NONE: return "none"; + case VM_INHERIT_DONATE_COPY: return "donate_copy"; + default: + sprintf(buf, "%d", inherit); + return buf; + } +} + +#define BEHAVIOR_MAX_SIZE 16 +static char *behavior_to_str(vm_behavior_t behavior, char *buf) +{ + switch (behavior) { + case VM_BEHAVIOR_DEFAULT: return "default"; + case VM_BEHAVIOR_RANDOM: return "random"; + case VM_BEHAVIOR_SEQUENTIAL: return "sequential"; + case VM_BEHAVIOR_RSEQNTL: return "rseqntl"; + case VM_BEHAVIOR_WILLNEED: return "willneed"; + case VM_BEHAVIOR_DONTNEED: return "dontneed"; + case VM_BEHAVIOR_FREE: return "free"; + case VM_BEHAVIOR_ZERO_WIRED_PAGES: return "zero"; + case VM_BEHAVIOR_REUSABLE: return "reusable"; + case VM_BEHAVIOR_REUSE: return "reuse"; + case VM_BEHAVIOR_CAN_REUSE: return "can"; + case VM_BEHAVIOR_PAGEOUT: return "pageout"; + default: + sprintf(buf, "%d", behavior); + return buf; + } +} + +static void dump_maps(const char *image_name) +{ + mach_port_t task = mach_task_self(); + vm_region_basic_info_data_64_t info; + mach_msg_type_number_t info_count = VM_REGION_BASIC_INFO_COUNT_64; + memory_object_name_t object = 0; + vm_address_t addr = 0; + vm_size_t size; + char inherit_buf[INHERIT_MAX_SIZE + 1]; + char behavior_buf[BEHAVIOR_MAX_SIZE + 1]; + + fprintf(stderr, "MEMORY MAP(%s)\n", image_name); + fprintf(stderr, " start address end address protection max_protection inherit shared reserved offset behavior user_wired_count\n"); + while (vm_region_64(task, &addr, &size, VM_REGION_BASIC_INFO_64, (vm_region_info_t)&info, &info_count, &object) == KERN_SUCCESS) { + fprintf(stderr, " %016lx-%016lx %c%c%c(%08x) %c%c%c(%08x) %-*s %c %c %08llx %-*s %u\n", + addr, addr + size, + (info.protection & VM_PROT_READ) ? 'r' : '-', + (info.protection & VM_PROT_WRITE) ? 'w' : '-', + (info.protection & VM_PROT_EXECUTE) ? 'x' : '-', + info.protection, + (info.max_protection & VM_PROT_READ) ? 'r' : '-', + (info.max_protection & VM_PROT_WRITE) ? 'w' : '-', + (info.max_protection & VM_PROT_EXECUTE) ? 'x' : '-', + info.max_protection, + INHERIT_MAX_SIZE, inherit_to_str(info.inheritance, inherit_buf), + info.shared ? 'Y' : 'N', + info.reserved ? 'Y' : 'N', + info.offset, + BEHAVIOR_MAX_SIZE, behavior_to_str(info.behavior, behavior_buf), + info.user_wired_count); + addr += size; + } +} +#endif + +typedef struct { + const char *name; + int addend; + char weak; + void **addr; +} bind_address_t; + +typedef struct mem_prot { + size_t start; + size_t end; + int prot; +} mem_prot_t; + +#define NUM_MEM_PROT 100 + +struct plthook { + unsigned int num_entries; + mem_prot_t mem_prot[NUM_MEM_PROT]; + bind_address_t entries[1]; /* This must be the last. */ +}; + +#define MAX_SEGMENTS 8 +#define MAX_SECTIONS 30 + +typedef struct { + plthook_t *plthook; + intptr_t slide; + int num_segments; + int linkedit_segment_idx; + const struct segment_command_64 *segments[MAX_SEGMENTS]; +#ifdef PLTHOOK_DEBUG_FIXUPS + int num_sections; + const struct section_64 *sections[MAX_SECTIONS]; +#endif + const struct linkedit_data_command *chained_fixups; +} data_t; + +static int plthook_open_real(plthook_t **plthook_out, uint32_t image_idx, const struct mach_header *mh, const char *image_name); +static unsigned int set_bind_addrs(data_t *data, unsigned int idx, uint32_t bind_off, uint32_t bind_size, char weak); +static void set_bind_addr(data_t *d, unsigned int *idx, const char *sym_name, int seg_index, int seg_offset, int addend, char weak); +static int read_chained_fixups(data_t *d, const struct mach_header *mh, const char *image_name); +#ifdef PLTHOOK_DEBUG_FIXUPS +static const char *segment_name_from_addr(data_t *d, size_t addr); +static const char *section_name_from_addr(data_t *d, size_t addr); +#endif + +static int set_mem_prot(plthook_t *plthook); +static int get_mem_prot(plthook_t *plthook, void *addr); + +static inline uint8_t *fileoff_to_vmaddr_in_segment(data_t *d, int segment_index, size_t offset) +{ + const struct segment_command_64 *seg = d->segments[segment_index]; + return (uint8_t *)(seg->vmaddr - seg->fileoff + d->slide + offset); +} +static uint8_t *fileoff_to_vmaddr(data_t *data, size_t offset); + +static void set_errmsg(const char *fmt, ...) __attribute__((__format__ (__printf__, 1, 2))); + +static uint64_t uleb128(const uint8_t **p) +{ + uint64_t r = 0; + int s = 0; + do { + r |= (uint64_t)(**p & 0x7f) << s; + s += 7; + } while (*(*p)++ >= 0x80); + return r; +} + +static int64_t sleb128(const uint8_t** p) +{ + int64_t r = 0; + int s = 0; + for (;;) { + uint8_t b = *(*p)++; + if (b < 0x80) { + if (b & 0x40) { + r -= (0x80 - b) << s; + } else { + r |= (b & 0x3f) << s; + } + break; + } + r |= (b & 0x7f) << s; + s += 7; + } + return r; +} + +static char errmsg[512]; + +int plthook_open(plthook_t **plthook_out, const char *filename) +{ + size_t namelen; + uint32_t cnt; + uint32_t idx; + + if (filename == NULL) { + return plthook_open_real(plthook_out, 0, NULL, NULL); + } + cnt = _dyld_image_count(); + namelen = strlen(filename); + namelen = strlen(filename); + cnt = _dyld_image_count(); + + for (idx = 0; idx < cnt; idx++) { + const char *image_name = _dyld_get_image_name(idx); + size_t offset = 0; + + if (image_name == NULL) { + *plthook_out = NULL; + set_errmsg("Cannot find file at image index %u", idx); + return PLTHOOK_INTERNAL_ERROR; + } + if (*filename != '/') { + size_t image_name_len = strlen(image_name); + if (image_name_len > namelen) { + offset = image_name_len - namelen; + } + } + if (strcmp(image_name + offset, filename) == 0) { + return plthook_open_real(plthook_out, idx, NULL, image_name); + } + } + *plthook_out = NULL; + set_errmsg("Cannot find file: %s", filename); + return PLTHOOK_FILE_NOT_FOUND; +} + +int plthook_open_by_handle(plthook_t **plthook_out, void *hndl) +{ + int flags[] = { + RTLD_LAZY | RTLD_NOLOAD, + RTLD_LAZY | RTLD_NOLOAD | RTLD_FIRST, + }; + int flag_idx; + uint32_t cnt = _dyld_image_count(); +#define NUM_FLAGS (sizeof(flags) / sizeof(flags[0])) + + if (hndl == NULL) { + set_errmsg("NULL handle"); + return PLTHOOK_FILE_NOT_FOUND; + } + for (flag_idx = 0; flag_idx < (int)NUM_FLAGS; flag_idx++) { + uint32_t idx; + + for (idx = 0; idx < cnt; idx++) { + const char *image_name = idx ? _dyld_get_image_name(idx) : NULL; + void *handle = dlopen(image_name, flags[flag_idx]); + if (handle != NULL) { + dlclose(handle); + if (handle == hndl) { + return plthook_open_real(plthook_out, idx, NULL, image_name); + } + } + } + } + set_errmsg("Cannot find the image correspond to handle %p", hndl); + return PLTHOOK_FILE_NOT_FOUND; +} + +int plthook_open_by_address(plthook_t **plthook_out, void *address) +{ + Dl_info dlinfo; + uint32_t idx = 0; + uint32_t cnt = _dyld_image_count(); + + if (!dladdr(address, &dlinfo)) { + *plthook_out = NULL; + set_errmsg("Cannot find address: %p", address); + return PLTHOOK_FILE_NOT_FOUND; + } + for (idx = 0; idx < cnt; idx++) { + if (dlinfo.dli_fbase == _dyld_get_image_header(idx)) { + return plthook_open_real(plthook_out, idx, dlinfo.dli_fbase, dlinfo.dli_fname); + } + } + set_errmsg("Cannot find the image index for base address: %p", dlinfo.dli_fbase); + return PLTHOOK_FILE_NOT_FOUND; +} + +static int plthook_open_real(plthook_t **plthook_out, uint32_t image_idx, const struct mach_header *mh, const char *image_name) +{ + struct load_command *cmd; + const struct dyld_info_command *dyld_info = NULL; + unsigned int nbind; + data_t data = {NULL,}; + size_t size; + int i; + + data.linkedit_segment_idx = -1; + data.slide = _dyld_get_image_vmaddr_slide(image_idx); + if (mh == NULL) { + mh = _dyld_get_image_header(image_idx); + } + if (image_name == NULL) { + image_name = _dyld_get_image_name(image_idx); + } +#if defined(PLTHOOK_DEBUG_CMD) || defined(PLTHOOK_DEBUG_ADDR) + fprintf(stderr, "mh=%"PRIxPTR" slide=%"PRIxPTR"\n", (uintptr_t)mh, data.slide); +#endif +#ifdef PLTHOOK_DEBUG_ADDR + dump_maps(image_name); +#endif + + cmd = (struct load_command *)((size_t)mh + sizeof(struct mach_header_64)); + DEBUG_CMD("CMD START\n"); + for (i = 0; i < mh->ncmds; i++) { +#ifdef PLTHOOK_DEBUG_CMD + struct segment_command *segment; +#endif + struct segment_command_64 *segment64; + + switch (cmd->cmd) { + case LC_SEGMENT: /* 0x1 */ +#ifdef PLTHOOK_DEBUG_CMD + segment = (struct segment_command *)cmd; +#endif + DEBUG_CMD("LC_SEGMENT\n" + " segname %s\n" + " vmaddr %8x vmsize %8x\n" + " fileoff %8x filesize %8x\n" + " maxprot %8x initprot %8x\n" + " nsects %8d flags %8x\n", + segment->segname, + segment->vmaddr, segment->vmsize, + segment->fileoff, segment->filesize, + segment->maxprot, segment->initprot, + segment->nsects, segment->flags); + break; + case LC_SEGMENT_64: /* 0x19 */ + segment64 = (struct segment_command_64 *)cmd; + DEBUG_CMD("LC_SEGMENT_64\n" + " segname %s\n" + " vmaddr %8llx vmsize %8llx\n" + " fileoff %8llx filesize %8llx\n" + " maxprot %8x initprot %8x\n" + " nsects %8d flags %8x\n", + segment64->segname, + segment64->vmaddr, segment64->vmsize, + segment64->fileoff, segment64->filesize, + segment64->maxprot, segment64->initprot, + segment64->nsects, segment64->flags); + if (strcmp(segment64->segname, "__LINKEDIT") == 0) { + data.linkedit_segment_idx = data.num_segments; + } +#ifdef PLTHOOK_DEBUG_FIXUPS + struct section_64 *sec = (struct section_64 *)(segment64 + 1); + uint32_t i; + for (i = 0; i < segment64->nsects; i++) { + DEBUG_CMD(" section_64 (%u)\n" + " sectname %s\n" + " segname %s\n" + " addr 0x%llx\n" + " size 0x%llx\n" + " offset 0x%x\n" + " align 0x%x\n" + " reloff 0x%x\n" + " nreloc %d\n" + " flags 0x%x\n" + " reserved1 %d\n" + " reserved2 %d\n" + " reserved3 %d\n", + i, + sec->sectname, + sec->segname, + sec->addr, + sec->size, + sec->offset, + sec->align, + sec->reloff, + sec->nreloc, + sec->flags, + sec->reserved1, + sec->reserved2, + sec->reserved3); + sec++; + } +#endif + if (data.num_segments == MAX_SEGMENTS) { + set_errmsg("Too many segments: %s", image_name); + return PLTHOOK_INTERNAL_ERROR; + } + data.segments[data.num_segments++] = segment64; +#ifdef PLTHOOK_DEBUG_FIXUPS + { + struct section_64 *sec = (struct section_64 *)(segment64 + 1); + struct section_64 *sec_end = sec + segment64->nsects; + while (sec < sec_end) { + if (data.num_sections == MAX_SECTIONS) { + set_errmsg("Too many sections: %s", image_name); + return PLTHOOK_INTERNAL_ERROR; + } + data.sections[data.num_sections++] = sec; + sec++; + } + } +#endif + break; + case LC_DYLD_INFO_ONLY: /* (0x22|LC_REQ_DYLD) */ + dyld_info= (struct dyld_info_command *)cmd; + DEBUG_CMD("LC_DYLD_INFO_ONLY\n" + " offset size\n" + " rebase %8x %8x\n" + " bind %8x %8x\n" + " weak_bind %8x %8x\n" + " lazy_bind %8x %8x\n" + " export_bind %8x %8x\n", + dyld_info->rebase_off, dyld_info->rebase_size, + dyld_info->bind_off, dyld_info->bind_size, + dyld_info->weak_bind_off, dyld_info->weak_bind_size, + dyld_info->lazy_bind_off, dyld_info->lazy_bind_size, + dyld_info->export_off, dyld_info->export_size); + break; + case LC_SYMTAB: /* 0x2 */ + DEBUG_CMD("LC_SYMTAB\n"); + break; + case LC_DYSYMTAB: /* 0xb */ + DEBUG_CMD("LC_DYSYMTAB\n"); + break; + case LC_LOAD_DYLIB: /* 0xc */ + DEBUG_CMD("LC_LOAD_DYLIB\n"); + break; + case LC_ID_DYLIB: /* 0xd */ + DEBUG_CMD("LC_ID_DYLIB\n"); + break; + case LC_LOAD_DYLINKER: /* 0xe */ + DEBUG_CMD("LC_LOAD_DYLINKER\n"); + break; + case LC_ROUTINES_64: /* 0x1a */ + DEBUG_CMD("LC_ROUTINES_64\n"); + break; + case LC_UUID: /* 0x1b */ + DEBUG_CMD("LC_UUID\n"); + break; + case LC_RPATH: /* (0x1c|LC_REQ_DYLD) */ + DEBUG_CMD("LC_RPATH\n"); + break; + case LC_CODE_SIGNATURE: /* 0x1d */ + DEBUG_CMD("LC_CODE_SIGNATURE\n"); + break; + case LC_VERSION_MIN_MACOSX: /* 0x24 */ + DEBUG_CMD("LC_VERSION_MIN_MACOSX\n"); + break; + case LC_FUNCTION_STARTS: /* 0x26 */ + DEBUG_CMD("LC_FUNCTION_STARTS\n"); + break; + case LC_MAIN: /* 0x28|LC_REQ_DYLD */ + DEBUG_CMD("LC_MAIN\n"); + break; + case LC_DATA_IN_CODE: /* 0x29 */ + DEBUG_CMD("LC_DATA_IN_CODE\n"); + break; + case LC_SOURCE_VERSION: /* 0x2A */ + DEBUG_CMD("LC_SOURCE_VERSION\n"); + break; + case LC_DYLIB_CODE_SIGN_DRS: /* 0x2B */ + DEBUG_CMD("LC_DYLIB_CODE_SIGN_DRS\n"); + break; + case LC_BUILD_VERSION: /* 0x32 */ + DEBUG_CMD("LC_BUILD_VERSION\n"); + break; + case LC_DYLD_EXPORTS_TRIE: /* (0x33|LC_REQ_DYLD) */ + DEBUG_CMD("LC_DYLD_EXPORTS_TRIE\n"); + break; + case LC_DYLD_CHAINED_FIXUPS: /* (0x34|LC_REQ_DYLD) */ + data.chained_fixups = (struct linkedit_data_command *)cmd; + DEBUG_CMD("LC_DYLD_CHAINED_FIXUPS\n" + " cmdsize %u\n" + " dataoff %u (0x%x)\n" + " datasize %u\n", + data.chained_fixups->cmdsize, + data.chained_fixups->dataoff, + data.chained_fixups->dataoff, + data.chained_fixups->datasize); + break; + default: + DEBUG_CMD("LC_? (0x%x)\n", cmd->cmd); + } + cmd = (struct load_command *)((size_t)cmd + cmd->cmdsize); + } + DEBUG_CMD("CMD END\n"); + if (data.linkedit_segment_idx == -1) { + set_errmsg("Cannot find the linkedit segment: %s", image_name); + return PLTHOOK_INVALID_FILE_FORMAT; + } + if (data.chained_fixups != NULL) { + int rv = read_chained_fixups(&data, mh, image_name); + if (rv != 0) { + return rv; + } + } else { + nbind = 0; + nbind = set_bind_addrs(&data, nbind, dyld_info->bind_off, dyld_info->bind_size, 0); + nbind = set_bind_addrs(&data, nbind, dyld_info->weak_bind_off, dyld_info->weak_bind_size, 1); + nbind = set_bind_addrs(&data, nbind, dyld_info->lazy_bind_off, dyld_info->lazy_bind_size, 0); + size = offsetof(plthook_t, entries) + sizeof(bind_address_t) * nbind; + data.plthook = (plthook_t*)calloc(1, size); + if (data.plthook == NULL) { + set_errmsg("failed to allocate memory: %" PRIuPTR " bytes", size); + return PLTHOOK_OUT_OF_MEMORY; + } + data.plthook->num_entries = nbind; + nbind = 0; + nbind = set_bind_addrs(&data, nbind, dyld_info->bind_off, dyld_info->bind_size, 0); + nbind = set_bind_addrs(&data, nbind, dyld_info->weak_bind_off, dyld_info->weak_bind_size, 1); + nbind = set_bind_addrs(&data, nbind, dyld_info->lazy_bind_off, dyld_info->lazy_bind_size, 0); + } + set_mem_prot(data.plthook); + + *plthook_out = data.plthook; + return 0; +} + +static unsigned int set_bind_addrs(data_t *data, unsigned int idx, uint32_t bind_off, uint32_t bind_size, char weak) +{ + const uint8_t *ptr = fileoff_to_vmaddr_in_segment(data, data->linkedit_segment_idx, bind_off); + const uint8_t *end = ptr + bind_size; + const char *sym_name; + int seg_index = 0; + uint64_t seg_offset = 0; + int addend = 0; + int count, skip; +#ifdef PLTHOOK_DEBUG_BIND + int cond = data->plthook != NULL; +#endif + + while (ptr < end) { + uint8_t op = *ptr & BIND_OPCODE_MASK; + uint8_t imm = *ptr & BIND_IMMEDIATE_MASK; + int i; + + DEBUG_BIND_IF(cond, "0x%02x: ", *ptr); + ptr++; + switch (op) { + case BIND_OPCODE_DONE: + DEBUG_BIND_IF(cond, "BIND_OPCODE_DONE\n"); + break; + case BIND_OPCODE_SET_DYLIB_ORDINAL_IMM: + DEBUG_BIND_IF(cond, "BIND_OPCODE_SET_DYLIB_ORDINAL_IMM: ordinal = %u\n", imm); + break; + case BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB: +#ifdef PLTHOOK_DEBUG_BIND + DEBUG_BIND_IF(cond, "BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB: ordinal = %llu\n", uleb128(&ptr)); +#else + uleb128(&ptr); +#endif + break; + case BIND_OPCODE_SET_DYLIB_SPECIAL_IMM: + if (imm == 0) { + DEBUG_BIND_IF(cond, "BIND_OPCODE_SET_DYLIB_SPECIAL_IMM: ordinal = 0\n"); + } else { + DEBUG_BIND_IF(cond, "BIND_OPCODE_SET_DYLIB_SPECIAL_IMM: ordinal = %u\n", BIND_OPCODE_MASK | imm); + } + break; + case BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM: + sym_name = (const char*)ptr; + ptr += strlen(sym_name) + 1; + DEBUG_BIND_IF(cond, "BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM: sym_name = %s\n", sym_name); + break; + case BIND_OPCODE_SET_TYPE_IMM: + DEBUG_BIND_IF(cond, "BIND_OPCODE_SET_TYPE_IMM: type = %u\n", imm); + break; + case BIND_OPCODE_SET_ADDEND_SLEB: + addend = sleb128(&ptr); + DEBUG_BIND_IF(cond, "BIND_OPCODE_SET_ADDEND_SLEB: ordinal = %lld\n", addend); + break; + case BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB: + seg_index = imm; + seg_offset = uleb128(&ptr); + DEBUG_BIND_IF(cond, "BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB: seg_index = %u, seg_offset = 0x%llx\n", seg_index, seg_offset); + break; + case BIND_OPCODE_ADD_ADDR_ULEB: + seg_offset += uleb128(&ptr); + DEBUG_BIND_IF(cond, "BIND_OPCODE_ADD_ADDR_ULEB: seg_offset = 0x%llx\n", seg_offset); + break; + case BIND_OPCODE_DO_BIND: + set_bind_addr(data, &idx, sym_name, seg_index, seg_offset, addend, weak); + seg_offset += sizeof(void*); + DEBUG_BIND_IF(cond, "BIND_OPCODE_DO_BIND\n"); + break; + case BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB: + set_bind_addr(data, &idx, sym_name, seg_index, seg_offset, addend, weak); + seg_offset += uleb128(&ptr) + sizeof(void*); + DEBUG_BIND_IF(cond, "BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB: seg_offset = 0x%llx\n", seg_offset); + break; + case BIND_OPCODE_DO_BIND_ADD_ADDR_IMM_SCALED: + set_bind_addr(data, &idx, sym_name, seg_index, seg_offset, addend, weak); + seg_offset += imm * sizeof(void *) + sizeof(void*); + DEBUG_BIND_IF(cond, "BIND_OPCODE_DO_BIND_ADD_ADDR_IMM_SCALED\n"); + break; + case BIND_OPCODE_DO_BIND_ULEB_TIMES_SKIPPING_ULEB: + count = uleb128(&ptr); + skip = uleb128(&ptr); + for (i = 0; i < count; i++) { + set_bind_addr(data, &idx, sym_name, seg_index, seg_offset, addend, weak); + seg_offset += skip + sizeof(void*); + } + DEBUG_BIND_IF(cond, "BIND_OPCODE_DO_BIND_ULEB_TIMES_SKIPPING_ULEB\n"); + break; + default: + DEBUG_BIND_IF(cond, "op: 0x%x, imm: 0x%x\n", op, imm); + } + } + return idx; +} + +static void set_bind_addr(data_t *data, unsigned int *idx, const char *sym_name, int seg_index, int seg_offset, int addend, char weak) +{ + if (data->plthook != NULL) { + size_t vmaddr = data->segments[seg_index]->vmaddr; + bind_address_t *bind_addr = &data->plthook->entries[*idx]; + bind_addr->name = sym_name; + bind_addr->addend = addend; + bind_addr->weak = weak; + bind_addr->addr = (void**)(vmaddr + data->slide + seg_offset); + DEBUG_BIND("bind_address[%u]: %s, %d, %d, %p, %p, %p\n", *idx, sym_name, seg_index, seg_offset, (void*)vmaddr, (void*)data->slide, bind_addr->addr); + } + (*idx)++; +} + +typedef struct { + const char *image_name; + FILE *fp; + const struct dyld_chained_starts_in_image *starts; + uint32_t seg_index; // i + uint16_t page_index; // j + off_t offset; +} chained_fixups_iter_t; + +typedef struct { + uint16_t ptr_format; + union { + uint64_t raw; + struct dyld_chained_ptr_64_rebase rebase; + struct dyld_chained_ptr_64_bind bind; + struct dyld_chained_ptr_arm64e_rebase arm64e_rebase; + struct dyld_chained_ptr_arm64e_bind arm64e_bind; + struct dyld_chained_ptr_arm64e_bind24 arm64e_bind24; + struct dyld_chained_ptr_arm64e_auth_rebase arm64e_auth_rebase; + struct dyld_chained_ptr_arm64e_auth_bind arm64e_auth_bind; + struct dyld_chained_ptr_arm64e_auth_bind24 arm64e_auth_bind24; + } ptr; + off_t offset; +} chianed_fixups_entry_t; + +static int chained_fixups_iter_init(chained_fixups_iter_t *iter, const char *image_name, const struct dyld_chained_starts_in_image *starts_offset); +static void chained_fixups_iter_deinit(chained_fixups_iter_t *iter); +static int chained_fixups_iter_rewind(chained_fixups_iter_t *iter); +static int chained_fixups_iter_next(chained_fixups_iter_t *iter, chianed_fixups_entry_t *entry); + +static int chained_fixups_iter_init(chained_fixups_iter_t *iter, const char *image_name, const struct dyld_chained_starts_in_image *starts) +{ + memset(iter, 0, sizeof(*iter)); + iter->fp = fopen(image_name, "r"); + if (iter->fp == NULL) { + set_errmsg("failed to open file %s (error: %s)", image_name, strerror(errno)); + return PLTHOOK_FILE_NOT_FOUND; + } + iter->image_name = image_name; + iter->starts = starts; + return 0; +} + +static void chained_fixups_iter_deinit(chained_fixups_iter_t *iter) +{ + if (iter->fp != NULL) { + fclose(iter->fp); + iter->fp = NULL; + } +} + +static int chained_fixups_iter_rewind(chained_fixups_iter_t *iter) +{ + iter->seg_index = 0; + iter->page_index = 0; + iter->offset = 0; + return 0; +} + +static int chained_fixups_iter_next(chained_fixups_iter_t *iter, chianed_fixups_entry_t *entry) +{ + const struct dyld_chained_starts_in_image *starts = iter->starts; + uint32_t i = iter->seg_index; + uint16_t j = iter->page_index; + off_t offset = iter->offset; + +next_segment: + if (i == starts->seg_count) { + return -1; + } + if (j == 0 && offset == 0) { + DEBUG_FIXUPS(" seg_info_offset[%u] %u\n", + i, starts->seg_info_offset[i]); + } + if (starts->seg_info_offset[i] == 0) { + i++; + j = 0; + offset = 0; + goto next_segment; + } + const struct dyld_chained_starts_in_segment* seg = (const struct dyld_chained_starts_in_segment*)((char*)starts + starts->seg_info_offset[i]); + if (j == 0 && offset == 0) { + DEBUG_FIXUPS(" dyld_chained_starts_in_segment\n" + " size %u\n" + " page_size 0x%x\n" + " pointer_format %u\n" + " segment_offset %llu (0x%llx)\n" + " max_valid_pointer %u\n" + " page_count %u\n", + seg->size, seg->page_size, seg->pointer_format, seg->segment_offset, seg->segment_offset, seg->max_valid_pointer, seg->page_count); + } +next_page: + if (j == seg->page_count) { + i++; + j = 0; + offset = 0; + goto next_segment; + } + + if (seg->page_start[j] == DYLD_CHAINED_PTR_START_NONE) { + DEBUG_FIXUPS(" page_start[%u] DYLD_CHAINED_PTR_START_NONE\n", j); + j++; + offset = 0; + goto next_page; + } + if (offset == 0) { + DEBUG_FIXUPS(" page_start[%u] %u\n", j, seg->page_start[j]); + } + if (offset == 0) { + offset = seg->segment_offset + j * seg->page_size + seg->page_start[j]; + } + if (fseeko(iter->fp, offset, SEEK_SET) != 0) { + set_errmsg("failed to seek to %lld in %s", offset, iter->image_name); + return PLTHOOK_INVALID_FILE_FORMAT; + } + entry->ptr_format = seg->pointer_format; + if (fread(&entry->ptr, sizeof(entry->ptr), 1, iter->fp) != 1) { + set_errmsg("failed to read fixup chain from %s", iter->image_name); + return PLTHOOK_INVALID_FILE_FORMAT; + } + entry->offset = offset; + switch (seg->pointer_format) { + case DYLD_CHAINED_PTR_64_OFFSET: + if (entry->ptr.bind.next) { + offset += entry->ptr.bind.next * 4; + } else { + j++; + offset = 0; + } + break; + default: + set_errmsg("unsupported pointer format %u in %s", seg->pointer_format, iter->image_name); + return PLTHOOK_INTERNAL_ERROR; + } + iter->seg_index = i; + iter->page_index = j; + iter->offset = offset; + return 0; +} + +static int read_chained_fixups(data_t *d, const struct mach_header *mh, const char *image_name) +{ + const uint8_t *ptr = fileoff_to_vmaddr_in_segment(d, d->linkedit_segment_idx, d->chained_fixups->dataoff); + const struct dyld_chained_fixups_header *header = (const struct dyld_chained_fixups_header *)ptr; + const char *symbol_pool = (const char*)ptr + header->symbols_offset; + int rv; + unsigned int num_binds; + size_t size; + const struct dyld_chained_starts_in_image *starts = (const struct dyld_chained_starts_in_image *)(ptr + header->starts_offset); + const struct dyld_chained_import *import = (const struct dyld_chained_import *)(ptr + header->imports_offset); + chained_fixups_iter_t iter = {NULL, }; + chianed_fixups_entry_t entry; + + rv = chained_fixups_iter_init(&iter, image_name, starts); + if (rv != 0) { + return rv; + } + + DEBUG_FIXUPS("dyld_chained_fixups_header\n" + " fixups_version %u\n" + " starts_offset %u\n" + " imports_offset %u\n" + " symbols_offset %u\n" + " imports_count %u\n" + " imports_format %u\n" + " symbols_format %u\n", + header->fixups_version, + header->starts_offset, + header->imports_offset, + header->symbols_offset, + header->imports_count, + header->imports_format, + header->symbols_format); + if (header->fixups_version != 0) { + set_errmsg("unknown chained fixups version %u", header->fixups_version); + rv = PLTHOOK_INVALID_FILE_FORMAT; + goto cleanup; + } + + DEBUG_FIXUPS("dyld_chained_starts_in_image\n" + " seg_count %u\n", + starts->seg_count); + num_binds = 0; + while ((rv = chained_fixups_iter_next(&iter, &entry)) == 0) { + if (entry.ptr_format == DYLD_CHAINED_PTR_64_OFFSET && entry.ptr.bind.bind) { + num_binds++; + } +#if 0 + if (entry.ptr.rebase.bind) { + DEBUG_FIXUPS(" 0x%08lX: raw: 0x%016lX bind: (next: %03u, ordinal: %06X, addend: %d)\n", + entry.offset, entry.ptr.raw, entry.ptr.bind.next, entry.ptr.bind.ordinal, entry.ptr.bind.addend); + } else { + DEBUG_FIXUPS(" 0x%08lX: raw: 0x%016lX rebase: (next: %03u, target: 0x%011lX, high8: 0x%02X)\n", + entry.offset, entry.ptr.raw, entry.ptr.rebase.next, entry.ptr.rebase.target, entry.ptr.rebase.high8); + } +#endif + } + if (rv > 0) { + goto cleanup; + } + + size = offsetof(plthook_t, entries) + sizeof(bind_address_t) * num_binds; + d->plthook = (plthook_t*)calloc(1, size); + if (d->plthook == NULL) { + set_errmsg("failed to allocate memory: %" PRIuPTR " bytes", size); + rv = PLTHOOK_OUT_OF_MEMORY; + goto cleanup; + } + d->plthook->num_entries = num_binds; + + chained_fixups_iter_rewind(&iter); + num_binds = 0; + while ((rv = chained_fixups_iter_next(&iter, &entry)) == 0) { + if (entry.ptr_format == DYLD_CHAINED_PTR_64_OFFSET && entry.ptr.bind.bind) { + uint16_t ordinal = entry.ptr.bind.ordinal; + uint32_t name_offset; + char weak = 0; + bind_address_t *bind_addr = &d->plthook->entries[num_binds]; +#ifdef PLTHOOK_DEBUG_FIXUPS + int32_t lib_ordinal; + const char *libname; +#endif + switch (header->imports_format) { + case DYLD_CHAINED_IMPORT: + name_offset = import[ordinal].name_offset; + weak = import[ordinal].weak_import; +#ifdef PLTHOOK_DEBUG_FIXUPS + if (import[ordinal].lib_ordinal >= (uint8_t)BIND_SPECIAL_DYLIB_WEAK_LOOKUP) { + lib_ordinal = (int8_t)import[ordinal].lib_ordinal; + } else { + lib_ordinal = (uint8_t)import[ordinal].lib_ordinal; + } +#endif + break; + default: + DEBUG_FIXUPS("imports_format: %u\n", header->imports_format); + set_errmsg("unsupported imports format %u", header->imports_format); + rv = PLTHOOK_INTERNAL_ERROR; + goto cleanup; + } + bind_addr->name = symbol_pool + name_offset; + bind_addr->addr = (void**)fileoff_to_vmaddr(d, entry.offset); + bind_addr->addend = entry.ptr.bind.addend; + bind_addr->weak = weak; +#ifdef PLTHOOK_DEBUG_FIXUPS + switch (lib_ordinal) { + case BIND_SPECIAL_DYLIB_SELF: + libname = "this-image"; + break; + case BIND_SPECIAL_DYLIB_MAIN_EXECUTABLE: + libname = "main-executable"; + break; + case BIND_SPECIAL_DYLIB_FLAT_LOOKUP: + libname = "flat-namespace"; + break; + case BIND_SPECIAL_DYLIB_WEAK_LOOKUP: + libname = "weak"; + break; + default: + libname = "?"; + } +#endif + DEBUG_FIXUPS(" %-12s %-16s 0x%08llX bind %s/%s", + segment_name_from_addr(d, entry.offset), section_name_from_addr(d, entry.offset), entry.offset, libname, symbol_pool + name_offset); + if (entry.ptr.bind.addend != 0) { + DEBUG_FIXUPS(" + 0x%X", entry.ptr.bind.addend); + } + if (weak) { + DEBUG_FIXUPS(" [weak-import]"); + } + DEBUG_FIXUPS("\n"); + num_binds++; + } else if (entry.ptr_format == DYLD_CHAINED_PTR_64_OFFSET && !entry.ptr.bind.bind) { + DEBUG_FIXUPS(" %-12s %-16s 0x%08llX rebase 0x%08llX\n", + segment_name_from_addr(d, entry.offset), section_name_from_addr(d, entry.offset), entry.offset, entry.ptr.rebase.target); + } + } + chained_fixups_iter_deinit(&iter); + rv = 0; +cleanup: + chained_fixups_iter_deinit(&iter); + if (rv != 0 && d->plthook) { + free(d->plthook); + d->plthook = NULL; + } + return rv; +} + +#ifdef PLTHOOK_DEBUG_FIXUPS +static const char *segment_name_from_addr(data_t *d, size_t addr) +{ + int i; + for (i = 0; i < d->num_segments; i++) { + const struct segment_command_64 *seg = d->segments[i]; + if (seg->fileoff <= addr && addr < seg->fileoff + seg->filesize) { + return seg->segname; + } + } + return "?"; +} + +static const char *section_name_from_addr(data_t *d, size_t addr) +{ + int i; + for (i = 0; i < d->num_sections; i++) { + const struct section_64 *sec = d->sections[i]; + if (sec->offset <= addr && addr < sec->offset + sec->size) { + return sec->sectname; + } + } + return "?"; +} +#endif + +static int set_mem_prot(plthook_t *plthook) +{ + unsigned int pos = 0; + const char *name; + void **addr; + size_t start = (size_t)-1; + size_t end = 0; + mach_port_t task = mach_task_self(); + vm_address_t vm_addr = 0; + vm_size_t vm_size; + vm_region_basic_info_data_64_t info; + mach_msg_type_number_t info_count = VM_REGION_BASIC_INFO_COUNT_64; + memory_object_name_t object = 0; + int idx = 0; + + while (plthook_enum(plthook, &pos, &name, &addr) == 0) { + if (start > (size_t)addr) { + start = (size_t)addr; + } + if (end < (size_t)addr) { + end = (size_t)addr; + } + } + end++; + + while (vm_region_64(task, &vm_addr, &vm_size, VM_REGION_BASIC_INFO_64, (vm_region_info_t)&info, &info_count, &object) == KERN_SUCCESS) { + mem_prot_t mem_prot = {vm_addr, vm_addr + vm_size, info.protection & (PROT_READ | PROT_WRITE | PROT_EXEC)}; + if (mem_prot.prot != 0 && mem_prot.start < end && start < mem_prot.end) { + plthook->mem_prot[idx++] = mem_prot; + if (idx == NUM_MEM_PROT) { + break; + } + } + vm_addr += vm_size; + } + return 0; +} + +static int get_mem_prot(plthook_t *plthook, void *addr) +{ + mem_prot_t *ptr = plthook->mem_prot; + mem_prot_t *end = ptr + NUM_MEM_PROT; + + while (ptr < end && ptr->prot != 0) { + if (ptr->start <= (size_t)addr && (size_t)addr < ptr->end) { + return ptr->prot; + } + ++ptr; + } + return 0; +} + +static uint8_t *fileoff_to_vmaddr(data_t *d, size_t offset) +{ + int i; + for (i = 0; i < d->num_segments; i++) { + const struct segment_command_64 *seg = d->segments[i]; + if (seg->fileoff <= offset && offset < seg->fileoff + seg->filesize) { + return fileoff_to_vmaddr_in_segment(d, i, offset); + } + } + return NULL; +} + +int plthook_enum(plthook_t *plthook, unsigned int *pos, const char **name_out, void ***addr_out) +{ + plthook_entry_t entry; + int rv = plthook_enum_entry(plthook, pos, &entry); + if (rv == 0) { + *name_out = entry.name; + *addr_out = entry.addr; + } + return rv; +} + +int plthook_enum_with_prot(plthook_t *plthook, unsigned int *pos, const char **name_out, void ***addr_out, int *prot) +{ + plthook_entry_t entry; + int rv = plthook_enum_entry(plthook, pos, &entry); + if (rv == 0) { + *name_out = entry.name; + *addr_out = entry.addr; + if (prot) { + *prot = entry.prot; + } + } + return rv; +} + +int plthook_enum_entry(plthook_t *plthook, unsigned int *pos, plthook_entry_t *entry) +{ + memset(entry, 0, sizeof(*entry)); + while (*pos < plthook->num_entries) { + if (strcmp(plthook->entries[*pos].name, "__tlv_bootstrap") == 0) { + (*pos)++; + continue; + } + entry->name = plthook->entries[*pos].name; + entry->addr = plthook->entries[*pos].addr; + entry->addend = plthook->entries[*pos].addend; + entry->prot = get_mem_prot(plthook, entry->addr); + entry->weak = plthook->entries[*pos].weak; + (*pos)++; + return 0; + } + return EOF; +} + +int plthook_replace(plthook_t *plthook, const char *funcname, void *funcaddr, void **oldfunc) +{ + size_t funcnamelen = strlen(funcname); + unsigned int pos = 0; + plthook_entry_t entry; + int rv; + + if (plthook == NULL) { + set_errmsg("invalid argument: The first argument is null."); + return PLTHOOK_INVALID_ARGUMENT; + } + while ((rv = plthook_enum_entry(plthook, &pos, &entry)) == 0) { + const char *name = entry.name; + void **addr = entry.addr; + if (strncmp(name, funcname, funcnamelen) == 0) { + if (name[funcnamelen] == '\0' || name[funcnamelen] == '$') { + goto matched; + } + } + if (name[0] == '@') { + /* I doubt this code... */ + name++; + if (strncmp(name, funcname, funcnamelen) == 0) { + if (name[funcnamelen] == '\0' || name[funcnamelen] == '$') { + goto matched; + } + } + } + if (name[0] == '_') { + name++; + if (strncmp(name, funcname, funcnamelen) == 0) { + if (name[funcnamelen] == '\0' || name[funcnamelen] == '$') { + goto matched; + } + } + } + continue; +matched: + if (oldfunc) { + *oldfunc = *addr; + } + if (!(entry.prot & PROT_WRITE)) { + size_t page_size = sysconf(_SC_PAGESIZE); + void *base = (void*)((size_t)addr & ~(page_size - 1)); + if (mprotect(base, page_size, PROT_READ | PROT_WRITE) != 0) { + set_errmsg("Cannot change memory protection at address %p", base); + return PLTHOOK_INTERNAL_ERROR; + } + *addr = funcaddr; + mprotect(base, page_size, entry.prot); + } else { + *addr = funcaddr; + } + return 0; + } + if (rv == EOF) { + set_errmsg("no such function: %s", funcname); + rv = PLTHOOK_FUNCTION_NOT_FOUND; + } + return rv; +} + +void plthook_close(plthook_t *plthook) +{ + if (plthook != NULL) { + free(plthook); + } + return; +} + +const char *plthook_error(void) +{ + return errmsg; +} + +static void set_errmsg(const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + vsnprintf(errmsg, sizeof(errmsg) - 1, fmt, ap); + va_end(ap); +} diff --git a/OpenLumina/plthook/plthook_win32.c b/OpenLumina/plthook/plthook_win32.c new file mode 100644 index 0000000..206bd8a --- /dev/null +++ b/OpenLumina/plthook/plthook_win32.c @@ -0,0 +1,304 @@ +/* -*- indent-tabs-mode: nil -*- + * + * plthook_win32.c -- implementation of plthook for PE format + * + * URL: https://github.com/kubo/plthook + * + * ------------------------------------------------------ + * + * Copyright 2013-2014 Kubo Takehiro + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ''AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of the authors. + * + */ +#include +#include +#include +#include +#include +#include "plthook.h" + +#ifdef _MSC_VER +#pragma comment(lib, "dbghelp.lib") +#endif + +#ifndef _Printf_format_string_ +#define _Printf_format_string_ +#endif +#ifndef __GNUC__ +#define __attribute__(arg) +#endif + +#if defined _LP64 /* data model: I32/LP64 */ +#define SIZE_T_FMT "lu" +#elif defined _WIN64 /* data model: IL32/P64 */ +#define SIZE_T_FMT "I64u" +#else +#define SIZE_T_FMT "u" +#endif + +#ifdef __CYGWIN__ +#define stricmp strcasecmp +#endif + +typedef struct { + const char *mod_name; + const char *name; + void **addr; +} import_address_entry_t; + +struct plthook { + HMODULE hMod; + unsigned int num_entries; + import_address_entry_t entries[1]; +}; + +static char errbuf[512]; +static int plthook_open_real(plthook_t **plthook_out, HMODULE hMod); +static void set_errmsg(_Printf_format_string_ const char *fmt, ...) __attribute__((__format__ (__printf__, 1, 2))); +static void set_errmsg2(_Printf_format_string_ const char *fmt, ...) __attribute__((__format__ (__printf__, 1, 2))); + +int plthook_open(plthook_t **plthook_out, const char *filename) +{ + HMODULE hMod; + + *plthook_out = NULL; + if (!GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, filename, &hMod)) { + set_errmsg2("Cannot get module %s: ", filename); + return PLTHOOK_FILE_NOT_FOUND; + } + return plthook_open_real(plthook_out, hMod); +} + +int plthook_open_by_handle(plthook_t **plthook_out, void *hndl) +{ + if (hndl == NULL) { + set_errmsg("NULL handle"); + return PLTHOOK_FILE_NOT_FOUND; + } + return plthook_open_real(plthook_out, (HMODULE)hndl); +} + +int plthook_open_by_address(plthook_t **plthook_out, void *address) +{ + HMODULE hMod; + + *plthook_out = NULL; + if (!GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT | GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, (LPCSTR)address, &hMod)) { + set_errmsg2("Cannot get module at address %p: ", address); + return PLTHOOK_FILE_NOT_FOUND; + } + return plthook_open_real(plthook_out, hMod); +} + +static int plthook_open_real(plthook_t **plthook_out, HMODULE hMod) +{ + plthook_t *plthook; + ULONG ulSize; + IMAGE_IMPORT_DESCRIPTOR *desc_head, *desc; + size_t num_entries = 0; + size_t ordinal_name_buflen = 0; + size_t idx; + char *ordinal_name_buf; + + desc_head = (IMAGE_IMPORT_DESCRIPTOR*)ImageDirectoryEntryToData(hMod, TRUE, IMAGE_DIRECTORY_ENTRY_IMPORT, &ulSize); + if (desc_head == NULL) { + set_errmsg2("ImageDirectoryEntryToData error: "); + return PLTHOOK_INTERNAL_ERROR; + } + + /* Calculate size to allocate memory. */ + for (desc = desc_head; desc->Name != 0; desc++) { + IMAGE_THUNK_DATA *name_thunk = (IMAGE_THUNK_DATA*)((char*)hMod + desc->OriginalFirstThunk); + IMAGE_THUNK_DATA *addr_thunk = (IMAGE_THUNK_DATA*)((char*)hMod + desc->FirstThunk); + const char *module_name = (char *)hMod + desc->Name; + int is_winsock2_dll = (stricmp(module_name, "WS2_32.DLL") == 0); + + while (addr_thunk->u1.Function != 0) { + if (IMAGE_SNAP_BY_ORDINAL(name_thunk->u1.Ordinal)) { + int ordinal = IMAGE_ORDINAL(name_thunk->u1.Ordinal); +#ifdef __CYGWIN__ + ordinal_name_buflen += snprintf(NULL, 0, "%s:@%d", module_name, ordinal) + 1; +#else + ordinal_name_buflen += _scprintf("%s:@%d", module_name, ordinal) + 1; +#endif + } + num_entries++; + name_thunk++; + addr_thunk++; + } + } + + plthook = calloc(1, offsetof(plthook_t, entries) + sizeof(import_address_entry_t) * num_entries + ordinal_name_buflen); + if (plthook == NULL) { + set_errmsg("failed to allocate memory: %" SIZE_T_FMT " bytes", sizeof(plthook_t)); + return PLTHOOK_OUT_OF_MEMORY; + } + plthook->hMod = hMod; + plthook->num_entries = num_entries; + + ordinal_name_buf = (char*)plthook + offsetof(plthook_t, entries) + sizeof(import_address_entry_t) * num_entries; + idx = 0; + for (desc = desc_head; desc->Name != 0; desc++) { + IMAGE_THUNK_DATA *name_thunk = (IMAGE_THUNK_DATA*)((char*)hMod + desc->OriginalFirstThunk); + IMAGE_THUNK_DATA *addr_thunk = (IMAGE_THUNK_DATA*)((char*)hMod + desc->FirstThunk); + const char *module_name = (char *)hMod + desc->Name; + + while (addr_thunk->u1.Function != 0) { + const char *name = NULL; + + if (IMAGE_SNAP_BY_ORDINAL(name_thunk->u1.Ordinal)) { + int ordinal = IMAGE_ORDINAL(name_thunk->u1.Ordinal); + name = ordinal_name_buf; + ordinal_name_buf += sprintf(ordinal_name_buf, "%s:@%d", module_name, ordinal) + 1; + } else { + name = (char*)((PIMAGE_IMPORT_BY_NAME)((char*)hMod + name_thunk->u1.AddressOfData))->Name; + } + plthook->entries[idx].mod_name = module_name; + plthook->entries[idx].name = name; + plthook->entries[idx].addr = (void**)&addr_thunk->u1.Function; + idx++; + name_thunk++; + addr_thunk++; + } + } + + *plthook_out = plthook; + return 0; +} + +int plthook_enum(plthook_t *plthook, unsigned int *pos, const char **name_out, void ***addr_out) +{ + if (*pos >= plthook->num_entries) { + *name_out = NULL; + *addr_out = NULL; + return EOF; + } + *name_out = plthook->entries[*pos].name; + *addr_out = plthook->entries[*pos].addr; + (*pos)++; + return 0; +} + +static void replace_funcaddr(void **addr, void *newfunc, void **oldfunc) +{ + DWORD dwOld; + DWORD dwDummy; + + if (oldfunc != NULL) { + *oldfunc = *addr; + } + VirtualProtect(addr, sizeof(void *), PAGE_EXECUTE_READWRITE, &dwOld); + *addr = newfunc; + VirtualProtect(addr, sizeof(void *), dwOld, &dwDummy); +} + +int plthook_replace(plthook_t *plthook, const char *funcname, void *funcaddr, void **oldfunc) +{ +#ifndef _WIN64 + size_t funcnamelen = strlen(funcname); +#endif + unsigned int pos = 0; + const char *name; + void **addr; + int rv; + BOOL import_by_ordinal = funcname[0] != '?' && strstr(funcname, ":@") != NULL; + + if (plthook == NULL) { + set_errmsg("invalid argument: The first argument is null."); + return PLTHOOK_INVALID_ARGUMENT; + } + while ((rv = plthook_enum(plthook, &pos, &name, &addr)) == 0) { + if (import_by_ordinal) { + if (_stricmp(name, funcname) == 0) { + goto found; + } + } else { + /* import by name */ +#ifdef _WIN64 + if (strcmp(name, funcname) == 0) { + goto found; + } +#else + /* Function names may be decorated in Windows 32-bit applications. */ + if (strncmp(name, funcname, funcnamelen) == 0) { + if (name[funcnamelen] == '\0' || name[funcnamelen] == '@') { + goto found; + } + } + if (name[0] == '_' || name[0] == '@') { + name++; + if (strncmp(name, funcname, funcnamelen) == 0) { + if (name[funcnamelen] == '\0' || name[funcnamelen] == '@') { + goto found; + } + } + } +#endif + } + } + if (rv == EOF) { + set_errmsg("no such function: %s", funcname); + rv = PLTHOOK_FUNCTION_NOT_FOUND; + } + return rv; +found: + replace_funcaddr(addr, funcaddr, oldfunc); + return 0; +} + +void plthook_close(plthook_t *plthook) +{ + if (plthook != NULL) { + free(plthook); + } +} + +const char *plthook_error(void) +{ + return errbuf; +} + +static void set_errmsg(const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + vsnprintf(errbuf, sizeof(errbuf) - 1, fmt, ap); + va_end(ap); +} + +static void set_errmsg2(const char *fmt, ...) +{ + va_list ap; + size_t len; + + va_start(ap, fmt); + vsnprintf(errbuf, sizeof(errbuf) - 1, fmt, ap); + va_end(ap); + len = strlen(errbuf); + FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM |FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + errbuf + len, sizeof(errbuf) - len - 1, NULL); +} diff --git a/OpenLumina/plugin_ctx.h b/OpenLumina/plugin_ctx.h index 15f68a3..7266a9c 100644 --- a/OpenLumina/plugin_ctx.h +++ b/OpenLumina/plugin_ctx.h @@ -4,7 +4,7 @@ struct plugin_ctx_t : public plugmod_t { - bytevec_t decodedCert; + qvector certificates; bool idaapi run(size_t arg) override; bool init_hook(); ~plugin_ctx_t() override; diff --git a/README.md b/README.md index 6b0ced0..e724b49 100644 --- a/README.md +++ b/README.md @@ -5,16 +5,14 @@ IDA plugin that allows connecting to third party Lumina servers. ## Getting started 1. Build or download precompiled version of the plugin and put it into your IDA\plugins directory -2. Copy hexrays.crt certificate file provided by Lumina server owner to your IDA install directory +2. Copy your hexrays*.crt certificate file(s) provided by Lumina server owner to your IDA install directory ## Building plugin -1. Visual Studio 2022 is required for building plugin -2. vcpkg package manager required if you don't want to configure dependencies yourself manually -3. Install Microsoft Detours package through vcpkg `vcpkg install detours` -4. Configure paths to your extracted IDA SDK directory and optionally your IDA install directory in PropertySheet.props file -5. Open OpenLumina.sln in Visual Studio 2022 and build the plugin -6. Copy compiled plugin binaries to your IDA\plugins directory if you haven't configured your IDA install directory in step 4 above +1. CMake is required for building plugin. You can use CMake version bundled with Visual Studio 2022 (CMake 3.29.5 as of 02.09.2024) +2. Configure path to your extracted IDA SDK directory in build_win.cmd/build_linux.sh/build_mac.sh file +3. Run build_win.cmd/build_linux.sh/build_mac.sh (on Windows it must be run from VS Developer Command Prompt for VS2022) +3. Copy compiled plugin binaries to your \plugins directory ## Generating TLS certificates for your own Lumina server diff --git a/build_linux.sh b/build_linux.sh new file mode 100644 index 0000000..8b91964 --- /dev/null +++ b/build_linux.sh @@ -0,0 +1,5 @@ +rm -r out +cmake -DCMAKE_BUILD_TYPE=Release -DIdaSdk_ROOT_DIR=/home/tom_rus/idasdk91 -DIDA_90_STABLE=1 -B out +cmake --build out +zip -j openlumina_linux.zip out/OpenLumina*.so +#cp out/OpenLumina64.so /home/tom_rus/idafree-8.4/plugins/ diff --git a/build_mac.sh b/build_mac.sh new file mode 100644 index 0000000..aa09333 --- /dev/null +++ b/build_mac.sh @@ -0,0 +1,5 @@ +rm -r out +cmake -DCMAKE_BUILD_TYPE=Release -DIdaSdk_ROOT_DIR=/Users/tom_rus/Desktop/idasdk91 -DIDA_90_STABLE=1 -B out +cmake --build out +zip -j openlumina_mac.zip out/OpenLumina*.dylib +#cp out/OpenLumina64.dylib "/Applications/IDA Freeware 8.4/ida64.app/Contents/MacOS/plugins/" diff --git a/build_win.cmd b/build_win.cmd new file mode 100644 index 0000000..3a066a8 --- /dev/null +++ b/build_win.cmd @@ -0,0 +1,5 @@ +@echo off +rmdir /S /Q out +cmake -DCMAKE_BUILD_TYPE=Release -DIdaSdk_ROOT_DIR=e:\ida84\idasdk_pro84 -B out +cmake --build out --config Release +rem cp out/OpenLumina64.so /home/tom_rus/idafree-8.4/plugins/ diff --git a/build_win_90.cmd b/build_win_90.cmd new file mode 100644 index 0000000..4d5c9be --- /dev/null +++ b/build_win_90.cmd @@ -0,0 +1,5 @@ +@echo off +rmdir /S /Q out +cmake -DCMAKE_BUILD_TYPE=Release -DIdaSdk_ROOT_DIR=e:\ida92\ida-sdk-ida-v9.2\src -DIDA_90_STABLE=1 -B out +cmake --build out --config Release +rem cp out/OpenLumina64.so /home/tom_rus/idafree-8.4/plugins/ diff --git a/cmake/FindIdaSdk.cmake b/cmake/FindIdaSdk.cmake new file mode 100644 index 0000000..a81375b --- /dev/null +++ b/cmake/FindIdaSdk.cmake @@ -0,0 +1,308 @@ +# Copyright 2011-2021 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. + +# FindIdaSdk +# ---------- +# +# Locates and configures the IDA Pro SDK. Supports version 7.0 or higher. +# +# Use this module by invoking find_package with the form: +# +# find_package(IdaSdk [REQUIRED] # Fail with an error if IDA SDK is not found ) +# +# Defines the following variables: +# +# IdaSdk_INCLUDE_DIRS - Include directories for the IDA Pro SDK. IdaSdk_PLATFORM +# - IDA SDK platform, one of __LINUX__, __NT__ or __MAC__. +# +# This module reads hints about search locations from variables: +# +# IdaSdk_ROOT_DIR - Preferred installation prefix +# +# Example (this assumes Windows): +# +# find_package(IdaSdk REQUIRED) +# +# # Builds targets plugin.dll and plugin64.dll add_ida_plugin(plugin +# myplugin.cc) # Builds target plugin64.dll add_ida_plugin(plugin NOEA32 +# myplugin.cc) # Builds target plugin.dll add_ida_plugin(plugin NOEA64 +# myplugin.cc) +# +# Builds targets ldr.dll and ldr64.dll add_ida_loader(ldr myloader.cc) +# +# For platform-agnostic build files, the variables _so, and _so64 are available +# (and map to .dll, .so, .dylib as necessary): +# +# add_ida_plugin(plugin myplugin.cc) target_link_libraries(plugin${_so} ssl) +# target_link_libraries(plugin${_so64} ssl) +# +# To avoid the duplication above, these functions, which mimic the built-in +# ones, are also defined: +# +# add_ida_library( NOEA64|NOEA64 ...) <=> add_libary() +# ida_target_link_libraries(...) <=> target_link_libraries() +# ida_target_include_directories(...) <=> target_include_directories() +# set_ida_target_properties(...) <=> set_target_properties() +# ida_install(...) <=> install() + +include(CMakeParseArguments) +include(FindPackageHandleStandardArgs) + +find_path( + IdaSdk_DIR + NAMES include/pro.h + HINTS ${IdaSdk_ROOT_DIR} ENV IDASDK_ROOT + PATHS ${CMAKE_CURRENT_LIST_DIR}/../third_party/idasdk + PATH_SUFFIXES idasdk + DOC "Location of the IDA SDK" + NO_DEFAULT_PATH) +set(IdaSdk_INCLUDE_DIRS ${IdaSdk_DIR}/include) + +find_package_handle_standard_args( + IdaSdk + FOUND_VAR IdaSdk_FOUND + REQUIRED_VARS IdaSdk_DIR IdaSdk_INCLUDE_DIRS + FAIL_MESSAGE "IDA SDK not found, try setting IdaSdk_ROOT_DIR") + +# Define some platform specific variables for later use. +set(_so ${CMAKE_SHARED_LIBRARY_SUFFIX}) +set(_so64 64${CMAKE_SHARED_LIBRARY_SUFFIX}) # An additional "64" +# _plx, _plx64, _llx, _llx64 are kept to stay compatible with older +# CMakeLists.txt files. +set(_plx ${CMAKE_SHARED_LIBRARY_SUFFIX}) +set(_plx64 64${CMAKE_SHARED_LIBRARY_SUFFIX}) # An additional "64" +set(_llx ${CMAKE_SHARED_LIBRARY_SUFFIX}) +set(_llx64 64${CMAKE_SHARED_LIBRARY_SUFFIX}) # An additional "64" +if(APPLE) + set(IdaSdk_PLATFORM __MAC__) +elseif(UNIX) + set(IdaSdk_PLATFORM __LINUX__) +elseif(WIN32) + set(IdaSdk_PLATFORM __NT__) +else() + message(FATAL_ERROR "Unsupported system type: ${CMAKE_SYSTEM_NAME}") +endif() + +function(_ida_common_target_settings t ea64) + if(ea64) # Support for 64-bit addresses. + target_compile_definitions(${t} PUBLIC __EA64__) + endif() + # Add the necessary __IDP__ define and allow to use "dangerous" and standard + # file functions. + target_compile_definitions( + ${t} PUBLIC ${IdaSdk_PLATFORM} __X64__ __IDP__ USE_DANGEROUS_FUNCTIONS + USE_STANDARD_FILE_FUNCTIONS) + target_include_directories(${t} PUBLIC ${IdaSdk_INCLUDE_DIRS}) +endfunction() + +function(_ida_win_link_library) + if(ea64) + set(target_bits 64) + else() + set(target_bits 32) + endif() + + if(EXISTS ${IdaSdk_DIR}/lib/x64_win_vc_${target_bits}_pro) + set(ida_lib_dir ${IdaSdk_DIR}/lib/x64_win_vc_${target_bits}_pro) + elseif(EXISTS ${IdaSdk_DIR}/lib/x64_win_vc_${target_bits}) + set(ida_lib_dir ${IdaSdk_DIR}/lib/x64_win_vc_${target_bits}) + else() + message(FATAL_ERROR "IDA SDK lib directory not found") + endif() + + target_link_libraries(${t} ${ida_lib_dir}/ida.lib) +endfunction() + +function(_ida_plugin name ea64 link_script) # ARGN contains sources + if(ea64) + set(t ${name}${_so64}) + else() + set(t ${name}${_so}) + endif() + + # Define a module with the specified sources. + add_library(${t} MODULE ${ARGN}) + _ida_common_target_settings(${t} ${ea64}) + + set_target_properties(${t} PROPERTIES PREFIX "" SUFFIX "") + if(UNIX) + target_compile_options(${t} PUBLIC ${_ida_compile_options}) + if(APPLE) + target_link_libraries(${t} ${_ida_compile_options} -Wl,-flat_namespace -Wl,-undefined,dynamic_lookup -Wl,-exported_symbol,_PLUGIN) + else() + # Always use the linker script needed for IDA. + target_link_libraries(${t} ${_ida_compile_options} -Wl,--version-script ${IdaSdk_DIR}/${link_script}) + endif() + + # For qrefcnt_obj_t in ida.hpp + # TODO(cblichmann): This belongs in an interface library instead. + target_compile_options(${t} PUBLIC -Wno-non-virtual-dtor -Wno-varargs) + elseif(WIN32) + _ida_win_link_library() + endif() +endfunction() + +function(_ida_loader name ea64 link_script) + if(ea64) + set(t ${name}${_so64}) + else() + set(t ${name}${_so}) + endif() + + # Define a module with the specified sources. + add_library(${t} MODULE ${ARGN}) + _ida_common_target_settings(${t} ${ea64}) + + set_target_properties(${t} PROPERTIES PREFIX "" SUFFIX "") + if(UNIX) + target_compile_options(${t} PUBLIC ${_ida_compile_options}) + if(APPLE) + target_link_libraries(${t} ${_ida_compile_options} -Wl,-flat_namespace -Wl,-undefined,dynamic_lookup -Wl,-exported_symbol,_LDSC) + else() + # Always use the linker script needed for IDA. + target_link_libraries(${t} ${_ida_compile_options} -Wl,--version-script ${IdaSdk_DIR}/${link_script}) + endif() + + # For qrefcnt_obj_t in ida.hpp + # TODO(cblichmann): This belongs in an interface library instead. + target_compile_options(${t} PUBLIC -Wno-non-virtual-dtor -Wno-varargs) + elseif(WIN32) + _ida_win_link_library() + target_link_options(${t} PUBLIC "/EXPORT:LDSC") + endif() +endfunction() + +macro(_ida_check_bitness) + if(opt_NOEA32 AND opt_NOEA64) + message(FATAL_ERROR "NOEA32 and NOEA64 cannot be used at the same time") + endif() +endmacro() + +function(_ida_library name ea64) + if(ea64) + set(t ${name}_ea64) + else() + set(t ${name}_ea32) + endif() + + # Define the actual library. + add_library(${t} ${ARGN}) + _ida_common_target_settings(${t} ${ea64}) +endfunction() + +function(add_ida_library name) + cmake_parse_arguments(PARSE_ARGV 1 opt "NOEA32;NOEA64" "" "") + _ida_check_bitness(opt_NOEA32 opt_NOEA64) + + if(NOT DEFINED (opt_NOEA32)) + _ida_library(${name} FALSE ${opt_UNPARSED_ARGUMENTS}) + endif() + if(NOT DEFINED (opt_NOEA64)) + _ida_library(${name} TRUE ${opt_UNPARSED_ARGUMENTS}) + endif() +endfunction() + +function(add_ida_plugin name) + cmake_parse_arguments(PARSE_ARGV 1 opt "NOEA32;NOEA64" "" "") + _ida_check_bitness(opt_NOEA32 opt_NOEA64) + + if(NOT opt_NOEA32) + _ida_plugin(${name} FALSE plugins/exports.def ${opt_UNPARSED_ARGUMENTS}) + endif() + if(NOT opt_NOEA64) + _ida_plugin(${name} TRUE plugins/exports.def ${opt_UNPARSED_ARGUMENTS}) + endif() +endfunction() + +function(add_ida_loader name) + cmake_parse_arguments(PARSE_ARGV 1 opt "NOEA32;NOEA64" "" "") + _ida_check_bitness(opt_NOEA32 opt_NOEA64) + + if(NOT opt_NOEA32) + _ida_loader(${name} FALSE ldr/exports.def ${opt_UNPARSED_ARGUMENTS}) + endif() + if(NOT opt_NOEA64) + _ida_loader(${name} TRUE ldr/exports.def ${opt_UNPARSED_ARGUMENTS}) + endif() +endfunction() + +function(ida_target_link_libraries name) + foreach(item IN LISTS ARGN) + if(TARGET ${item}_ea32 OR TARGET ${item}_ea64) + if(TARGET ${item}_ea32) + list(APPEND args32 ${item}_ea32) + endif() + if(TARGET ${item}_ea64) + list(APPEND args64 ${item}_ea64) + endif() + else() + list(APPEND args ${item}) + endif() + endforeach() + foreach(target ${name}${_so} ${name}_ea32) + if(TARGET ${target}) + target_link_libraries(${target} ${args32} ${args}) + set(added TRUE) + endif() + endforeach() + foreach(target ${name}${_so64} ${name}_ea64) + if(TARGET ${target}) + target_link_libraries(${target} ${args64} ${args}) + set(added TRUE) + endif() + endforeach() + if(NOT added) + message(FATAL_ERROR "No such target: ${name}") + endif() +endfunction() + +function(ida_target_include_directories name) + foreach(target ${name}${_so} ${name}${_so64} ${name}_ea32 ${name}_ea64) + if(TARGET ${target}) + target_include_directories(${target} ${ARGN}) + set(added TRUE) + endif() + endforeach() + if(NOT added) + message(FATAL_ERROR "No such target: ${name}") + endif() +endfunction() + +function(set_ida_target_properties name) + foreach(target ${name}${_so} ${name}${_so64} ${name}_ea32 ${name}_ea64) + if(TARGET ${target}) + set_target_properties(${target} ${ARGN}) + set(added TRUE) + endif() + endforeach() + if(NOT added) + message(FATAL_ERROR "No such target: ${name}") + endif() +endfunction() + +function(ida_install) + foreach(item IN LISTS ARGN) + if(TARGET ${item}${_so} OR TARGET ${item}${_so64}) + if(TARGET ${item}${_so}) + list(APPEND args ${item}${_so}) + endif() + if(TARGET ${item}${_so64}) + list(APPEND args ${item}${_so64}) + endif() + else() + list(APPEND args ${item}) + endif() + endforeach() + install(${args}) +endfunction() diff --git a/ida-plugin.json b/ida-plugin.json new file mode 100644 index 0000000..0dbb817 --- /dev/null +++ b/ida-plugin.json @@ -0,0 +1,16 @@ +{ + "IDAMetadataDescriptorVersion": 1, + "plugin": { + "name": "OpenLumina", + "entryPoint": "OpenLumina", + "version": "9.2", + "idaVersions": ">=9.2", + "categories": ["collaboration-and-productivity", "other"], + "description" : "IDA Pro plugin that allows connecting to third party Lumina servers", + "license": "MIT", + "logoPath": "logo.png", + "urls": { + "repository": "https://github.com/tomrus88/OpenLumina" + } + } +} diff --git a/logo.png b/logo.png new file mode 100644 index 0000000..429af79 Binary files /dev/null and b/logo.png differ