diff --git a/Syscaller/crt.hpp b/Syscaller/crt.hpp new file mode 100644 index 0000000..b0cc036 --- /dev/null +++ b/Syscaller/crt.hpp @@ -0,0 +1,131 @@ +#ifndef SYSCALL_CRT_HPP +#define SYSCALL_CRT_HPP + +#include +#include + +namespace syscall::crt +{ + template + [[nodiscard]] constexpr size_t getCountOf(T(&)[N]) noexcept + { + return N; + } + + namespace string + { + constexpr char toLower(char c) noexcept + { + return (c >= 'A' && c <= 'Z') ? (c + ('a' - 'A')) : c; + } + constexpr wchar_t toLower(wchar_t c) noexcept + { + return (c >= L'A' && c <= L'Z') ? (c + (L'a' - L'A')) : c; + } + + constexpr size_t getLength(const char* szStr) noexcept + { + const char* s = szStr; + while (*s) + ++s; + + return s - szStr; + } + + constexpr size_t getLength(const wchar_t* wzStr) noexcept + { + const wchar_t* s = wzStr; + while (*s) + ++s; + + return s - wzStr; + } + + [[nodiscard]] constexpr int compare(const char* szFirst, const char* szSecond) noexcept + { + while (*szFirst && (*szFirst == *szSecond)) + { + szFirst++; + szSecond++; + } + + return *(const unsigned char*)szFirst - *(const unsigned char*)szSecond; + } + + [[nodiscard]] constexpr int compareIgnoreCase(const wchar_t* szFirst, const wchar_t* szSecond) noexcept + { + wchar_t c1, c2; + do { + c1 = toLower(*szFirst++); + c2 = toLower(*szSecond++); + + if (c1 == L'\0') + return c1 - c2; + } while (c1 == c2); + + return c1 - c2; + } + + [[nodiscard]] constexpr const char* findChar(const char* str, int character) noexcept + { + while (*str != '\0') + { + if (*str == static_cast(character)) + return str; + + str++; + } + return nullptr; + } + + [[nodiscard]] inline char* findChar(char* str, int character) noexcept + { + return const_cast(findChar(static_cast(str), character)); + } + + inline void copy(char* szDest, size_t uDestLength, const char* src) noexcept + { + if (!szDest || !uDestLength) + return; + + const size_t uSourceLength = getLength(src); + + const size_t uCount = (uSourceLength < uDestLength) ? uSourceLength : (uDestLength - 1); + std::copy_n(src, uCount, szDest); + szDest[uCount] = '\0'; + } + + inline void concat(wchar_t* pDest, size_t uSizeInElements, const wchar_t* pSource) noexcept + { + if (!pDest || !uSizeInElements) + return; + + const size_t uDestLength = getLength(pDest); + if (uDestLength >= uSizeInElements - 1) + return; + + + const size_t uSourceLength = getLength(pSource); + const size_t uRemainingSpace = uSizeInElements - uDestLength - 1; + const size_t uCount = (uSourceLength < uRemainingSpace) ? uSourceLength : uRemainingSpace; + + std::copy_n(pSource, uCount * sizeof(wchar_t), pDest + uDestLength); + pDest[uDestLength + uCount] = L'\0'; + } + + inline void mbToWcs(wchar_t* pDest, size_t uSizeInElements, const char* pSource) noexcept + { + if (!pDest || !uSizeInElements) + return; + + const size_t uSourceLength = getLength(pSource); + const size_t uCount = (uSourceLength < uSizeInElements) ? uSourceLength : (uSizeInElements - 1); + for (size_t i = 0; i < uCount; ++i) + pDest[i] = static_cast(static_cast(pSource[i])); + + pDest[uCount] = L'\0'; + } + } +} + +#endif \ No newline at end of file diff --git a/Syscaller/hash.hpp b/Syscaller/hash.hpp new file mode 100644 index 0000000..85c0440 --- /dev/null +++ b/Syscaller/hash.hpp @@ -0,0 +1,86 @@ +#ifndef HASHING_HPP +#define HASHING_HPP + +#include +#include +#include + +namespace syscall::hashing +{ + using Hash_t = uint64_t; + + constexpr Hash_t getCompileTimeSeed() + { + Hash_t seed = 0; + const char* szCurrentTime = __TIME__; + const char* szCurrentDate = __DATE__; + + for (int i = 0; szCurrentTime[i] != '\0'; ++i) + seed = std::rotr(seed, 3) + szCurrentTime[i]; + + for (int i = 0; szCurrentDate[i] != '\0'; ++i) + seed = std::rotr(seed, 5) + szCurrentDate[i]; + + return seed; + } + + constexpr Hash_t currentSeed = getCompileTimeSeed(); + constexpr Hash_t polyKey1 = 0xAF6F01BD5B2D7583ULL ^ currentSeed; + constexpr Hash_t polyKey2 = 0xB4F281729182741DULL ^ std::rotr(currentSeed, 7); + + consteval Hash_t calculateHash(const char* szData) + { + Hash_t hash = polyKey1; + while (*szData) + { + hash ^= static_cast(*szData++); + hash += std::rotr(hash, 11) + polyKey2; + } + return hash; + } + + consteval Hash_t calculateHash(const char* szData, size_t uLength) + { + Hash_t hash = polyKey1; + for (size_t i = 0; i < uLength && szData[i]; ++i) + { + hash ^= static_cast(szData[i]); + hash += std::rotr(hash, 11) + polyKey2; + } + return hash; + } + + + inline Hash_t calculateHashRuntime(const char* szData) + { + Hash_t hash = polyKey1; + while (*szData) + { + hash ^= static_cast(*szData++); + hash += std::rotr(hash, 11) + polyKey2; + } + return hash; + } + + inline Hash_t calculateHashRuntime(const char* szData, size_t uLength) + { + Hash_t hash = polyKey1; + for (size_t i = 0; i < uLength && szData[i]; ++i) + { + hash ^= static_cast(szData[i]); + hash += std::rotr(hash, 11) + polyKey2; + } + return hash; + } +} + +#ifdef SYSCALLS_NO_HASH +#define SYSCALL_ID(str) (str) +#define SYSCALL_ID_RT(str) (str) +#else +#define SYSCALL_ID(str) (syscall::hashing::calculateHash(str)) +#define SYSCALL_ID_RT(str) (syscall::hashing::calculateHashRuntime(str)) + +#endif + +#endif \ No newline at end of file diff --git a/Syscaller/native_api.hpp b/Syscaller/native_api.hpp new file mode 100644 index 0000000..8f284e6 --- /dev/null +++ b/Syscaller/native_api.hpp @@ -0,0 +1,236 @@ +#ifndef NATIVE_API_HPP +#define NATIVE_API_HPP + +#include "shared.hpp" +#include "crt.hpp" +#include "hash.hpp" +#include +#include + +namespace syscall::native +{ + inline PPEB getCurrentPEB() + { +#if SYSCALL_PLATFORM_WINDOWS_64 + return (PEB*)(__readgsqword(0x60)); +#elif SYSCALL_PLATFORM_WINDOWS_32 + return (PEB*)(__readfsdword(0x30)); +#endif + } + + inline hashing::Hash_t calculateHashRuntimeCi(const wchar_t* wzData) + { + if (!wzData) + return 0; + + hashing::Hash_t hash = hashing::polyKey1; + wchar_t wcCurrent = 0; + + while ((wcCurrent = *wzData++)) + { + char cAnsiChar = static_cast(crt::string::toLower(wcCurrent)); + hash ^= static_cast(cAnsiChar); + hash += std::rotr(hash, 11) + hashing::polyKey2; + } + return hash; + } + + inline HMODULE getModuleBase(const wchar_t* wzModuleName) + { + auto pPeb = getCurrentPEB(); + if (!pPeb || !pPeb->Ldr) + return nullptr; + + auto pLdrData = pPeb->Ldr; + auto pListHead = &pLdrData->InMemoryOrderModuleList; + auto pCurrentEntry = pListHead->Flink; + + while (pCurrentEntry != pListHead) + { + auto pEntry = CONTAINING_RECORD(pCurrentEntry, native::LDR_DATA_TABLE_ENTRY, InMemoryOrderLinks); + if (pEntry->BaseDllName.Buffer && crt::string::compareIgnoreCase(pEntry->BaseDllName.Buffer, wzModuleName) == 0) + return reinterpret_cast(pEntry->DllBase); + + pCurrentEntry = pCurrentEntry->Flink; + } + return nullptr; + } + + inline HMODULE getModuleBase(hashing::Hash_t uModuleHash) + { + auto pPeb = getCurrentPEB(); + if (!pPeb || !pPeb->Ldr) + return nullptr; + + auto pLdrData = pPeb->Ldr; + auto pListHead = &pLdrData->InMemoryOrderModuleList; + auto pCurrentEntry = pListHead->Flink; + + while (pCurrentEntry != pListHead) + { + auto pEntry = CONTAINING_RECORD(pCurrentEntry, native::LDR_DATA_TABLE_ENTRY, InMemoryOrderLinks); + if (pEntry->BaseDllName.Buffer && calculateHashRuntimeCi(pEntry->BaseDllName.Buffer) == uModuleHash) + return reinterpret_cast(pEntry->DllBase); + + pCurrentEntry = pCurrentEntry->Flink; + } + return nullptr; + } + + inline void* getExportAddress(HMODULE hModuleBase, const char* szExportName) + { + if (!hModuleBase || !szExportName) + return nullptr; + + auto pBase = reinterpret_cast(hModuleBase); + auto pDosHeader = reinterpret_cast(pBase); + if (pDosHeader->e_magic != IMAGE_DOS_SIGNATURE) + return nullptr; + + auto pNtHeaders = reinterpret_cast(pBase + pDosHeader->e_lfanew); + if (pNtHeaders->Signature != IMAGE_NT_SIGNATURE) + return nullptr; + + auto uExportDirRva = pNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress; + if (!uExportDirRva) return nullptr; + + auto pExportDir = reinterpret_cast(pBase + uExportDirRva); + auto pNamesRVA = reinterpret_cast(pBase + pExportDir->AddressOfNames); + auto pOrdinalsRVA = reinterpret_cast(pBase + pExportDir->AddressOfNameOrdinals); + auto pFunctionsRVA = reinterpret_cast(pBase + pExportDir->AddressOfFunctions); + + for (uint32_t i = 0; i < pExportDir->NumberOfNames; ++i) + { + const char* szCurrentProcName = reinterpret_cast(pBase + pNamesRVA[i]); + + if (crt::string::compare(szCurrentProcName, szExportName) == 0) + { + uint16_t usOrdinal = pOrdinalsRVA[i]; + uint32_t uFunctionRva = pFunctionsRVA[usOrdinal]; + auto uExportSectionStart = uExportDirRva; + auto uExportSectionEnd = uExportSectionStart + pNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size; + + if (uFunctionRva >= uExportSectionStart && uFunctionRva < uExportSectionEnd) + { + char szForwarderString[256]; + crt::string::copy(szForwarderString, sizeof(szForwarderString), reinterpret_cast(pBase + uFunctionRva)); + + char* szSeparator = crt::string::findChar(szForwarderString, '.'); + if (!szSeparator) + return nullptr; + + *szSeparator = '\0'; + char* szForwarderFuncName = szSeparator + 1; + char* szForwarderDllName = szForwarderString; + + wchar_t wzWideDllName[260]; + + crt::string::mbToWcs(wzWideDllName, crt::getCountOf(wzWideDllName), szForwarderDllName); + crt::string::concat(wzWideDllName, crt::getCountOf(wzWideDllName), L".dll"); + + HMODULE hForwarderModuleBase = getModuleBase(wzWideDllName); + if (!hForwarderModuleBase) + return nullptr; + + return getExportAddress(hForwarderModuleBase, szForwarderFuncName); + } + else + return pBase + uFunctionRva; + } + } + return nullptr; + } + + inline void* getExportAddress(HMODULE hModuleBase, hashing::Hash_t uExportHash) + { + if (!hModuleBase) + return nullptr; + + auto pBase = reinterpret_cast(hModuleBase); + auto pDosHeader = reinterpret_cast(pBase); + if (pDosHeader->e_magic != IMAGE_DOS_SIGNATURE) + return nullptr; + + auto pNtHeaders = reinterpret_cast(pBase + pDosHeader->e_lfanew); + if (pNtHeaders->Signature != IMAGE_NT_SIGNATURE) + return nullptr; + + auto uExportDirRva = pNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress; + if (!uExportDirRva) + return nullptr; + + auto pExportDir = reinterpret_cast(pBase + uExportDirRva); + auto pNamesRVA = reinterpret_cast(pBase + pExportDir->AddressOfNames); + auto pOrdinalsRVA = reinterpret_cast(pBase + pExportDir->AddressOfNameOrdinals); + auto pFunctionsRVA = reinterpret_cast(pBase + pExportDir->AddressOfFunctions); + + for (uint32_t i = 0; i < pExportDir->NumberOfNames; ++i) + { + const char* szCurrentProcName = reinterpret_cast(pBase + pNamesRVA[i]); + + if (hashing::calculateHashRuntime(szCurrentProcName) == uExportHash) + { + uint16_t usOrdinal = pOrdinalsRVA[i]; + uint32_t uFunctionRva = pFunctionsRVA[usOrdinal]; + auto uExportSectionStart = uExportDirRva; + auto uExportSectionEnd = uExportSectionStart + pNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size; + + if (uFunctionRva >= uExportSectionStart && uFunctionRva < uExportSectionEnd) + { + char szForwarderString[256]; + crt::string::copy(szForwarderString, sizeof(szForwarderString), reinterpret_cast(pBase + uFunctionRva)); + + char* szSeparator = crt::string::findChar(szForwarderString, '.'); + if (!szSeparator) + return nullptr; + + *szSeparator = '\0'; + char* szForwarderFuncName = szSeparator + 1; + char* szForwarderDllName = szForwarderString; + + wchar_t wzWideDllName[260]; + crt::string::mbToWcs(wzWideDllName, crt::getCountOf(wzWideDllName), szForwarderDllName); + hashing::Hash_t uForwarderDllHash = calculateHashRuntimeCi(wzWideDllName); + if (!uForwarderDllHash) + return nullptr; + + uForwarderDllHash ^= static_cast('.'); + uForwarderDllHash += std::rotr(uForwarderDllHash, 11) + hashing::polyKey2; + + uForwarderDllHash ^= static_cast('d'); + uForwarderDllHash += std::rotr(uForwarderDllHash, 11) + hashing::polyKey2; + + uForwarderDllHash ^= static_cast('l'); + uForwarderDllHash += std::rotr(uForwarderDllHash, 11) + hashing::polyKey2; + + uForwarderDllHash ^= static_cast('l'); + uForwarderDllHash += std::rotr(uForwarderDllHash, 11) + hashing::polyKey2; + + HMODULE hForwarderModuleBase = getModuleBase(uForwarderDllHash); + if (!hForwarderModuleBase) + return nullptr; + + hashing::Hash_t uForwarderFuncHash = hashing::calculateHashRuntime(szForwarderFuncName); + return getExportAddress(hForwarderModuleBase, uForwarderFuncHash); + } + else + return pBase + uFunctionRva; + } + } + return nullptr; + } + + SYSCALL_FORCE_INLINE uint64_t rdtscp() + { + unsigned int uProcessorId; +#if SYSCALL_COMPILER_MSVC + return __rdtscp(&uProcessorId); +#elif SYSCALL_COMPILER_GCC || SYSCALL_COMPILER_CLANG + return __builtin_ia32_rdtscp(&uProcessorId); +#else +#error "Compiler not supported for RDTSCP intrinsic" +#endif + } +} + +#endif \ No newline at end of file diff --git a/Syscaller/platform.hpp b/Syscaller/platform.hpp new file mode 100644 index 0000000..ded9d4f --- /dev/null +++ b/Syscaller/platform.hpp @@ -0,0 +1,69 @@ +#ifndef _SYSCALL_PLATFORM_HPP_ +#define _SYSCALL_PLATFORM_HPP_ + +#if defined(_WIN64) +#define SYSCALL_PLATFORM_WINDOWS 1 +#define SYSCALL_PLATFORM_WINDOWS_64 1 +#define SYSCALL_PLATFORM_WINDOWS_32 0 +#elif defined(_WIN32) +#define SYSCALL_PLATFORM_WINDOWS 1 +#define SYSCALL_PLATFORM_WINDOWS_64 0 +#define SYSCALL_PLATFORM_WINDOWS_32 1 +#else +#define SYSCALL_PLATFORM_WINDOWS 0 +#endif + +#if defined(__linux__) +#define SYSCALL_PLATFORM_LINUX 1 +#else +#define SYSCALL_PLATFORM_LINUX 0 +#endif + +#if defined(_MSC_VER) +#define SYSCALL_COMPILER_MSVC 1 +#else +#define SYSCALL_COMPILER_MSVC 0 +#endif + +#if defined(__clang__) +#define SYSCALL_COMPILER_CLANG 1 +#else +#define SYSCALL_COMPILER_CLANG 0 +#endif + +#if defined(__GNUC__) && !SYSCALL_COMPILER_CLANG +#define SYSCALL_COMPILER_GCC 1 +#else +#define SYSCALL_COMPILER_GCC 0 +#endif + +#if SYSCALL_PLATFORM_WINDOWS_64 +#define SYSCALL_API __stdcall +#else +#define SYSCALL_API __cdecl +#endif + +#if SYSCALL_COMPILER_MSVC +#define SYSCALL_FORCE_INLINE __forceinline +#elif SYSCALL_COMPILER_GCC || SYSCALL_COMPILER_CLANG +#define SYSCALL_FORCE_INLINE inline __attribute__((always_inline)) +#else +#define SYSCALL_FORCE_INLINE inline +#endif + +namespace syscall::platform +{ + constexpr bool isWindows = (SYSCALL_PLATFORM_WINDOWS == 1); + constexpr bool isWindows64 = (SYSCALL_PLATFORM_WINDOWS_64 == 1); + constexpr bool isWindows32 = (SYSCALL_PLATFORM_WINDOWS_32 == 1); + constexpr bool isLinux = (SYSCALL_PLATFORM_LINUX == 1); + + constexpr bool isMSVC = (SYSCALL_COMPILER_MSVC == 1); + constexpr bool IsClang = (SYSCALL_COMPILER_CLANG == 1); + constexpr bool IsGCC = (SYSCALL_COMPILER_GCC == 1); + + static_assert(isWindows, "Unsupported OS"); + static_assert(isMSVC || IsClang || IsGCC, "Unsupported compiler"); +} + +#endif \ No newline at end of file diff --git a/Syscaller/shared.hpp b/Syscaller/shared.hpp new file mode 100644 index 0000000..1f69d20 --- /dev/null +++ b/Syscaller/shared.hpp @@ -0,0 +1,111 @@ +#ifndef _SYSCALL_SHARED_HPP_ +#define _SYSCALL_SHARED_HPP_ + +#define WIN32_LEAN_AND_MEAN +#define NOMINMAX +#include +#include + +#include "platform.hpp" + +namespace syscall::native +{ + + [[nodiscard]] inline constexpr bool isSuccess(NTSTATUS status) noexcept + { + return status >= 0; + } + constexpr NTSTATUS STATUS_SUCCESS = 0x00000000L; + constexpr NTSTATUS STATUS_UNSUCCESSFUL = 0xC0000001L; + constexpr NTSTATUS STATUS_PROCEDURE_NOT_FOUND = 0xC000007A; + + inline constexpr HANDLE getCurrentProcess() noexcept + { + return reinterpret_cast(-1); + } + + enum class ESectionInherit : DWORD + { + VIEW_SHARE = 1, + VIEW_UNMAP = 2 + }; + + enum class ESectionAllocAttributes : ULONG + { + SECTION_COMMIT = SEC_COMMIT, + SECTION_IMAGE = SEC_IMAGE, + SECTION_IMAGE_NO_EXECUTE = SEC_IMAGE_NO_EXECUTE, + SECTION_LARGE_PAGES = SEC_LARGE_PAGES, + SECTION_NO_CHANGE = 0x00400000, + SECTION_RESERVE = SEC_RESERVE, + }; + + struct LDR_DATA_TABLE_ENTRY + { + LIST_ENTRY InLoadOrderLinks; + LIST_ENTRY InMemoryOrderLinks; + LIST_ENTRY InInitializationOrderLinks; + PVOID DllBase; + PVOID EntryPoint; + ULONG SizeOfImage; + UNICODE_STRING FullDllName; + UNICODE_STRING BaseDllName; + ULONG Flags; + USHORT LoadCount; + USHORT TlsIndex; + union { + LIST_ENTRY HashLinks; + struct { + PVOID SectionPointer; + ULONG CheckSum; + }; + }; + union { + ULONG TimeDateStamp; + PVOID LoadedImports; + }; + _ACTIVATION_CONTEXT* EntryPointActivationContext; + PVOID PatchInformation; + LIST_ENTRY ForwarderLinks; + LIST_ENTRY ServiceTagLinks; + LIST_ENTRY StaticLinks; + PVOID ContextInformation; + ULONG_PTR OriginalBase; + LARGE_INTEGER LoadTime; + }; + + using NtCreateSection_t = NTSTATUS(NTAPI*)( + PHANDLE SectionHandle, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes, + PLARGE_INTEGER MaximumSize, ULONG SectionPageProtection, ULONG AllocationAttributes, HANDLE FileHandle + ); + + using NtMapViewOfSection_t = NTSTATUS(NTAPI*)( + HANDLE SectionHandle, HANDLE ProcessHandle, PVOID* BaseAddress, + ULONG_PTR ZeroBits, SIZE_T CommitSize, PLARGE_INTEGER SectionOffset, + PSIZE_T ViewSize, ESectionInherit InheritDisposition, ULONG AllocationType, ULONG Win32Protect + ); + + using NtUnmapViewOfSection_t = NTSTATUS(NTAPI*)(HANDLE ProcessHandle, PVOID BaseAddress); + + using NtAllocateVirtualMemory_t = NTSTATUS(NTAPI*)( + HANDLE ProcessHandle, PVOID* BaseAddress, ULONG_PTR ZeroBits, + PSIZE_T RegionSize, ULONG AllocationType, ULONG Protect + ); + + using NtProtectVirtualMemory_t = NTSTATUS(NTAPI*)( + HANDLE ProcessHandle, PVOID* BaseAddress, PSIZE_T RegionSize, + ULONG NewProtect, PULONG OldProtect + ); + + using NtFreeVirtualMemory_t = NTSTATUS(NTAPI*)( + HANDLE ProcessHandle, PVOID* BaseAddress, PSIZE_T RegionSize, ULONG FreeType + ); + + using NtClose_t = NTSTATUS(NTAPI*)(HANDLE Handle); + using RtlCreateHeap_t = PVOID(NTAPI*)(ULONG Flags, PVOID HeapBase, SIZE_T ReserveSize, SIZE_T CommitSize, PVOID Lock, PVOID Parameters); + using RtlAllocateHeap_t = PVOID(NTAPI*)(PVOID HeapHandle, ULONG Flags, SIZE_T Size); + using RtlDestroyHeap_t = PVOID(NTAPI*)(PVOID HeapHandle); + +} // syscall::native + +#endif \ No newline at end of file diff --git a/Syscaller/syscall.hpp b/Syscaller/syscall.hpp new file mode 100644 index 0000000..43b96ab --- /dev/null +++ b/Syscaller/syscall.hpp @@ -0,0 +1,952 @@ +#ifndef SYSCALL_HPP +#define SYSCALL_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "shared.hpp" +#include "hash.hpp" +#include "native_api.hpp" + + +namespace syscall +{ + + struct ModuleInfo_t + { + uint8_t* m_pModuleBase = nullptr; + IMAGE_NT_HEADERS* m_pNtHeaders = nullptr; + IMAGE_EXPORT_DIRECTORY* m_pExportDir = nullptr; + }; + +#ifdef SYSCALLS_NO_HASH + using SyscallKey_t = std::string; +#else + using SyscallKey_t = hashing::Hash_t; +#endif + + struct SyscallEntry_t + { + SyscallKey_t m_key; + uint32_t m_uSyscallNumber; + uint32_t m_uOffset; + }; + + inline thread_local struct ExceptionContext_t + { + bool m_bShouldHandle = false; + const void* m_pExpectedExceptionAddress = nullptr; + void* m_pSyscallGadget = nullptr; + uint32_t m_uSyscallNumber = 0; + } pExceptionContext; + + class CExceptionContextGuard + { + public: + CExceptionContextGuard(const void* pExpectedAddress, void* pSyscallGadget, uint32_t uSyscallNumber) + { + pExceptionContext.m_bShouldHandle = true; + pExceptionContext.m_pExpectedExceptionAddress = pExpectedAddress; + pExceptionContext.m_pSyscallGadget = pSyscallGadget; + pExceptionContext.m_uSyscallNumber = uSyscallNumber; + } + + ~CExceptionContextGuard() + { + pExceptionContext.m_bShouldHandle = false; + } + + CExceptionContextGuard(const CExceptionContextGuard&) = delete; + CExceptionContextGuard& operator=(const CExceptionContextGuard&) = delete; + }; + + inline LONG NTAPI VectoredExceptionHandler(PEXCEPTION_POINTERS pExceptionInfo) + { + if (!pExceptionContext.m_bShouldHandle) + return EXCEPTION_CONTINUE_SEARCH; + + if (pExceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_ILLEGAL_INSTRUCTION && + pExceptionInfo->ExceptionRecord->ExceptionAddress == pExceptionContext.m_pExpectedExceptionAddress) + { + pExceptionContext.m_bShouldHandle = false; +#if SYSCALL_PLATFORM_WINDOWS_64 + pExceptionInfo->ContextRecord->R10 = pExceptionInfo->ContextRecord->Rcx; + pExceptionInfo->ContextRecord->Rax = pExceptionContext.m_uSyscallNumber; + pExceptionInfo->ContextRecord->Rip = reinterpret_cast(pExceptionContext.m_pSyscallGadget); +#else + uintptr_t uReturnAddressAfterSyscall = reinterpret_cast(pExceptionInfo->ExceptionRecord->ExceptionAddress) + 2; + + pExceptionInfo->ContextRecord->Edx = pExceptionInfo->ContextRecord->Esp; + + pExceptionInfo->ContextRecord->Esp -= sizeof(uintptr_t); + + *reinterpret_cast(pExceptionInfo->ContextRecord->Esp) = uReturnAddressAfterSyscall; + + pExceptionInfo->ContextRecord->Eip = reinterpret_cast(pExceptionContext.m_pSyscallGadget); + pExceptionInfo->ContextRecord->Eax = pExceptionContext.m_uSyscallNumber; + +#endif + + + return EXCEPTION_CONTINUE_EXECUTION; + } + + return EXCEPTION_CONTINUE_SEARCH; + } + + namespace policies + { + namespace allocator + { + struct section + { + static bool allocate(size_t uRegionSize, const std::span vecBuffer, void*& pOutRegion, HANDLE& /*unused*/) + { + HMODULE hNtDll = native::getModuleBase(hashing::calculateHash("ntdll.dll")); + + auto fNtCreateSection = reinterpret_cast(native::getExportAddress(hNtDll, SYSCALL_ID("NtCreateSection"))); + auto fNtMapView = reinterpret_cast(native::getExportAddress(hNtDll, SYSCALL_ID("NtMapViewOfSection"))); + auto fNtUnmapView = reinterpret_cast(native::getExportAddress(hNtDll, SYSCALL_ID("NtUnmapViewOfSection"))); + auto fNtClose = reinterpret_cast(native::getExportAddress(hNtDll, SYSCALL_ID("NtClose"))); + if (!fNtCreateSection || !fNtMapView || !fNtUnmapView || !fNtClose) + return false; + + HANDLE hSectionHandle = nullptr; + LARGE_INTEGER sectionSize; + sectionSize.QuadPart = uRegionSize; + + using enum native::ESectionInherit; + using enum native::ESectionAllocAttributes; + + NTSTATUS status = fNtCreateSection(&hSectionHandle, SECTION_ALL_ACCESS, nullptr, §ionSize, PAGE_EXECUTE_READWRITE, SEC_COMMIT | static_cast(SECTION_NO_CHANGE), nullptr); + if (!NT_SUCCESS(status)) + return false; + + void* pTempView = nullptr; + SIZE_T uViewSize = uRegionSize; + status = fNtMapView(hSectionHandle, native::getCurrentProcess(), &pTempView, 0, 0, nullptr, &uViewSize, VIEW_SHARE, 0, PAGE_READWRITE); + if (!NT_SUCCESS(status)) + { + fNtClose(hSectionHandle); + return false; + } + + std::copy_n(vecBuffer.data(), uRegionSize, static_cast(pTempView)); + fNtUnmapView(native::getCurrentProcess(), pTempView); + uViewSize = uRegionSize; + status = fNtMapView(hSectionHandle, native::getCurrentProcess(), &pOutRegion, 0, 0, nullptr, &uViewSize, native::ESectionInherit::VIEW_SHARE, 0, PAGE_EXECUTE_READ); + fNtClose(hSectionHandle); + return NT_SUCCESS(status) && pOutRegion; + } + static void release(void* pRegion, HANDLE /*hHeapHandle*/) + { + HMODULE hNtDll = native::getModuleBase(hashing::calculateHash("ntdll.dll")); + if (pRegion) + { + auto fNtUnmapView = reinterpret_cast(native::getExportAddress(hNtDll, SYSCALL_ID("NtUnmapViewOfSection"))); + if (fNtUnmapView) + fNtUnmapView(native::getCurrentProcess(), pRegion); + } + } + }; + + struct heap + { + static bool allocate(size_t uRegionSize, const std::span vecBuffer, void*& pOutRegion, HANDLE& hOutHeapHandle) + { + using RtlGetLastNtStatus_t = NTSTATUS(NTAPI*)(); + HMODULE hNtdll = native::getModuleBase(hashing::calculateHash("ntdll.dll")); + if (!hNtdll) + return false; + + auto fRtlCreateHeap = reinterpret_cast(native::getExportAddress(hNtdll, SYSCALL_ID("RtlCreateHeap"))); + auto fRtlAllocateHeap = reinterpret_cast(native::getExportAddress(hNtdll, SYSCALL_ID("RtlAllocateHeap"))); + auto fRtlGetLastNtStatus = reinterpret_cast(native::getExportAddress(hNtdll, SYSCALL_ID("RtlGetLastNtStatus"))); + if (!fRtlCreateHeap || !fRtlAllocateHeap) + return false; + + hOutHeapHandle = fRtlCreateHeap(HEAP_CREATE_ENABLE_EXECUTE | HEAP_GROWABLE, nullptr, 0, 0, nullptr, nullptr); + if (!hOutHeapHandle) + return false; + + pOutRegion = fRtlAllocateHeap(hOutHeapHandle, 0, uRegionSize); + if (!pOutRegion) + { + release(nullptr, hOutHeapHandle); + hOutHeapHandle = nullptr; + return false; + } + + std::copy_n(vecBuffer.data(), uRegionSize, static_cast(pOutRegion)); + return true; + } + + static void release(void* /*region*/, HANDLE hHeapHandle) + { + if (hHeapHandle) + { + HMODULE hNtdll = native::getModuleBase(hashing::calculateHash("ntdll.dll")); + if (!hNtdll) + return; + + auto fRtlDestroyHeap = reinterpret_cast(native::getExportAddress(hNtdll, SYSCALL_ID("RtlDestroyHeap"))); + if (fRtlDestroyHeap) + fRtlDestroyHeap(hHeapHandle); + } + } + }; + + struct memory + { + static bool allocate(size_t uRegionSize, const std::span vecBuffer, void*& pOutRegion, HANDLE& /*unused*/) + { + HMODULE hNtDll = native::getModuleBase(hashing::calculateHash("ntdll.dll")); + + auto fNtAllocate = reinterpret_cast(native::getExportAddress(hNtDll, SYSCALL_ID("NtAllocateVirtualMemory"))); + auto fNtProtect = reinterpret_cast(native::getExportAddress(hNtDll, SYSCALL_ID("NtProtectVirtualMemory"))); + if (!fNtAllocate || !fNtProtect) + return false; + + pOutRegion = nullptr; + SIZE_T uSize = uRegionSize; + NTSTATUS status = fNtAllocate(native::getCurrentProcess(), &pOutRegion, 0, &uSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); + + if (!NT_SUCCESS(status) || !pOutRegion) + return false; + + std::copy_n(vecBuffer.data(), uRegionSize, static_cast(pOutRegion)); + ULONG oldProtection = 0; + uSize = uRegionSize; + status = fNtProtect(native::getCurrentProcess(), &pOutRegion, &uSize, PAGE_EXECUTE_READ, &oldProtection); + + if (!NT_SUCCESS(status)) + { + uSize = 0; + fNtAllocate(native::getCurrentProcess(), &pOutRegion, 0, &uSize, MEM_RELEASE, 0); + pOutRegion = nullptr; + return false; + } + + return true; + } + + static void release(void* pRegion, HANDLE /*heapHandle*/) + { + HMODULE hNtDll = native::getModuleBase(hashing::calculateHash("ntdll.dll")); + + if (pRegion) + { + auto fNtFree = reinterpret_cast(native::getExportAddress(hNtDll, SYSCALL_ID("NtFreeVirtualMemory"))); + if (fNtFree) + { + SIZE_T uSize = 0; + fNtFree(native::getCurrentProcess(), &pRegion, &uSize, MEM_RELEASE); + } + } + } + }; + } // allocator + namespace generator + { +#if SYSCALL_PLATFORM_WINDOWS_64 + // @note / SapDragon: supports only on x64 now + struct gadget + { + static constexpr bool bRequiresGadget = true; + static constexpr size_t getStubSize() { return 32; } + static void generate(uint8_t* pBuffer, uint32_t uSyscallNumber, void* pGadgetAddress) + { + // @note / SapDragon: mov r10, rcx + pBuffer[0] = 0x49; + pBuffer[1] = 0x89; + pBuffer[2] = 0xCA; + + // @note / SapDragon: mov eax, syscallNumber + pBuffer[3] = 0xB8; + *reinterpret_cast(&pBuffer[4]) = uSyscallNumber; + + // @note / SapDragon: mov r11, gadgetAddress + pBuffer[8] = 0x49; + pBuffer[9] = 0xBB; + *reinterpret_cast(&pBuffer[10]) = reinterpret_cast(pGadgetAddress); + + // @note / SapDragon: push r11 + pBuffer[18] = 0x41; + pBuffer[19] = 0x53; + + // @note / SapDragon: ret + pBuffer[20] = 0xC3; + } + }; + +#endif + + struct exception + { + static constexpr bool bRequiresGadget = true; + static constexpr size_t getStubSize() { return 8; } + static void generate(uint8_t* pBuffer, uint32_t /*uSyscallNumber*/, void* /*pGadgetAddress*/) + { + pBuffer[0] = 0x0F; + pBuffer[1] = 0x0B; + pBuffer[2] = 0xC3; + std::fill_n(pBuffer + 3, getStubSize() - 3, 0x90); + } + }; + + struct direct + { + static constexpr bool bRequiresGadget = false; + +#if SYSCALL_PLATFORM_WINDOWS_64 + inline static constinit std::array arrShellcode = + { + 0x51, // push rcx + 0x41, 0x5A, // pop r10 + 0xB8, 0x00, 0x00, 0x00, 0x00, // mov eax, 0x00000000 (syscall number placeholder) + 0x0F, 0x05, // syscall + 0x48, 0x83, 0xC4, 0x08, // add rsp, 8 + 0xFF, 0x64, 0x24, 0xF8 // jmp qword ptr [rsp-8] + }; +#elif SYSCALL_PLATFORM_WINDOWS_32 + inline static constinit std::array arrShellcode = + { + 0xB8, 0x00, 0x00, 0x00, 0x00, // mov eax, 0x00000000 (syscall number placeholder) + 0x89, 0xE2, // mov edx, esp + 0x64, 0xFF, 0x15, 0xC0, 0x00, 0x00, 0x00, // call dword ptr fs:[0xC0] + 0xC3 // ret + }; +#endif + static void generate(uint8_t* pBuffer, uint32_t uSyscallNumber, void* /*pGadgetAddress*/) + { + std::copy_n(arrShellcode.data(), arrShellcode.size(), pBuffer); + if constexpr (platform::isWindows64) + *reinterpret_cast(pBuffer + 4) = uSyscallNumber; + else + *reinterpret_cast(pBuffer + 1) = uSyscallNumber; + } + static constexpr size_t getStubSize() { return arrShellcode.size(); } + }; + + } + namespace parser + { + struct directory + { + static std::vector parse(const ModuleInfo_t& module) + { + std::vector vecFoundSyscalls; + // @note / sapdragon: exception parser on x64 + if constexpr (platform::isWindows64) + { + auto uExceptionDirRva = module.m_pNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXCEPTION].VirtualAddress; + if (!uExceptionDirRva) + return vecFoundSyscalls; + + auto pRuntimeFunctions = reinterpret_cast(module.m_pModuleBase + uExceptionDirRva); + auto uExceptionDirSize = module.m_pNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXCEPTION].Size; + auto uFunctionCount = uExceptionDirSize / sizeof(IMAGE_RUNTIME_FUNCTION_ENTRY); + + auto pFunctionsRVA = reinterpret_cast(module.m_pModuleBase + module.m_pExportDir->AddressOfFunctions); + auto pNamesRVA = reinterpret_cast(module.m_pModuleBase + module.m_pExportDir->AddressOfNames); + auto pOrdinalsRva = reinterpret_cast(module.m_pModuleBase + module.m_pExportDir->AddressOfNameOrdinals); + + std::unordered_map mapRvaToName; + for (uint32_t i = 0; i < module.m_pExportDir->NumberOfNames; ++i) + { + const char* szName = reinterpret_cast(module.m_pModuleBase + pNamesRVA[i]); + uint16_t uOrdinal = pOrdinalsRva[i]; + uint32_t uFunctionRva = pFunctionsRVA[uOrdinal]; + mapRvaToName[uFunctionRva] = szName; + } + + uint32_t uSyscallNumber = 0; + for (DWORD i = 0; i < uFunctionCount; ++i) + { + auto pFunction = &pRuntimeFunctions[i]; + if (pFunction->BeginAddress == 0) + break; + + auto it = mapRvaToName.find(pFunction->BeginAddress); + if (it != mapRvaToName.end()) + { + const char* szName = it->second; + + if (hashing::calculateHashRuntime(szName, 2) == hashing::calculateHash("Zw")) + { + char szNtName[128]; + crt::string::copy(szNtName, 128, szName); + szNtName[0] = 'N'; + szNtName[1] = 't'; + + const SyscallKey_t key = SYSCALL_ID_RT(szNtName); + + vecFoundSyscalls.push_back(SyscallEntry_t{ key, uSyscallNumber, 0 }); + uSyscallNumber++; + } + } + } + } + // @note / sapdragon: sorting zw exports + else + { + auto pFunctionsRVA = reinterpret_cast(module.m_pModuleBase + module.m_pExportDir->AddressOfFunctions); + auto pNamesRVA = reinterpret_cast(module.m_pModuleBase + module.m_pExportDir->AddressOfNames); + auto pOrdinalsRva = reinterpret_cast(module.m_pModuleBase + module.m_pExportDir->AddressOfNameOrdinals); + + std::vector> vecZwFunctions; + for (uint32_t i = 0; i < module.m_pExportDir->NumberOfNames; ++i) + { + const char* szName = reinterpret_cast(module.m_pModuleBase + pNamesRVA[i]); + + if (szName[0] == 'Z' && szName[1] == 'w') + { + uint16_t uOrdinal = pOrdinalsRva[i]; + uint32_t uFunctionRva = pFunctionsRVA[uOrdinal]; + + auto pExportSectionStart = module.m_pNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress; + auto pExportSectionEnd = pExportSectionStart + module.m_pNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size; + if (uFunctionRva >= pExportSectionStart && uFunctionRva < pExportSectionEnd) + continue; + + uintptr_t uFunctionAddress = reinterpret_cast(module.m_pModuleBase + uFunctionRva); + vecZwFunctions.push_back({ uFunctionAddress, szName }); + } + } + + if (vecZwFunctions.empty()) + return vecFoundSyscalls; + + std::sort(vecZwFunctions.begin(), vecZwFunctions.end(), + [](const auto& a, const auto& b) { + return a.first < b.first; + }); + + uint32_t uSyscallNumber = 0; + for (const auto& [_, szName] : vecZwFunctions) + { + char szNtName[128]; + crt::string::copy(szNtName, 128, szName); + szNtName[0] = 'N'; + szNtName[1] = 't'; + + const SyscallKey_t key = SYSCALL_ID_RT(szNtName); + vecFoundSyscalls.push_back(SyscallEntry_t{ key, uSyscallNumber, 0 }); + uSyscallNumber++; + } + } + + return vecFoundSyscalls; + } + }; + + struct signature + { + static std::vector parse(const ModuleInfo_t& module) + { + std::vector vecFoundSyscalls; + + auto pFunctionsRVA = reinterpret_cast(module.m_pModuleBase + module.m_pExportDir->AddressOfFunctions); + auto pNamesRVA = reinterpret_cast(module.m_pModuleBase + module.m_pExportDir->AddressOfNames); + auto pOrdinalsRva = reinterpret_cast(module.m_pModuleBase + module.m_pExportDir->AddressOfNameOrdinals); + + for (uint32_t i = 0; i < module.m_pExportDir->NumberOfNames; i++) + { + const char* szName = reinterpret_cast(module.m_pModuleBase + pNamesRVA[i]); + + if (hashing::calculateHashRuntime(szName, 2) != hashing::calculateHash("Nt")) + continue; + + uint16_t uOrdinal = pOrdinalsRva[i]; + uint32_t uFunctionRva = pFunctionsRVA[uOrdinal]; + + auto pExportSectionStart = module.m_pNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress; + auto pExportSectionEnd = pExportSectionStart + module.m_pNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size; + if (uFunctionRva >= pExportSectionStart && uFunctionRva < pExportSectionEnd) + continue; + + uint8_t* pFunctionStart = module.m_pModuleBase + uFunctionRva; + uint32_t uSyscallNumber = 0; + + if constexpr (platform::isWindows64) + if (*reinterpret_cast(pFunctionStart) == 0xB8D18B4C) + uSyscallNumber = *reinterpret_cast(pFunctionStart + 4); + + if constexpr (platform::isWindows32) + if (*pFunctionStart == 0xB8) + uSyscallNumber = *reinterpret_cast(pFunctionStart + 1); + + // @note / SapDragon: checks hooks on x64 + if constexpr (platform::isWindows64) + { + if (isFunctionHooked(pFunctionStart) && !uSyscallNumber) + { + // @note / SapDragon: stable only on x64 + + // @note / SapDragon: search up + for (int j = 1; j < 20; ++j) + { + uint8_t* pNeighborFunc = pFunctionStart - (j * 0x20); + if (reinterpret_cast(pNeighborFunc) < reinterpret_cast(module.m_pModuleBase)) break; + if (*reinterpret_cast(pNeighborFunc) == 0xB8D18B4C) + { + uint32_t uNeighborSyscall = *reinterpret_cast(pNeighborFunc + 4); + uSyscallNumber = uNeighborSyscall + j; + break; + } + } + + // @note / SapDragon: search down + if (!uSyscallNumber) + { + for (int j = 1; j < 20; ++j) + { + uint8_t* pNeighborFunc = pFunctionStart + (j * 0x20); + if (reinterpret_cast(pNeighborFunc) > (reinterpret_cast(module.m_pModuleBase) + module.m_pNtHeaders->OptionalHeader.SizeOfImage)) break; + if (*reinterpret_cast(pNeighborFunc) == 0xB8D18B4C) + { + uint32_t uNeighborSyscall = *reinterpret_cast(pNeighborFunc + 4); + uSyscallNumber = uNeighborSyscall - j; + break; + } + } + } + } + } + + if (uSyscallNumber) + { + const SyscallKey_t key = SYSCALL_ID_RT(szName); + vecFoundSyscalls.push_back(SyscallEntry_t{ + key, + uSyscallNumber, + 0 + }); + } + } + return vecFoundSyscalls; + } + private: + static bool isFunctionHooked(const uint8_t* pFunctionStart) + { + const uint8_t* pCurrent = pFunctionStart; + + while (*pCurrent == 0x90) + pCurrent++; + + switch (pCurrent[0]) + { + // @note / SapDragon: JMP rel32 + case 0xE9: + // @note / SapDragon: JMP rel8 + case 0xEB: + // @note / SapDragon: push imm32 + case 0x68: + return true; + + // @note / SapDragon: jmp [mem] / jmp [rip + offset] + case 0xFF: + if (pCurrent[1] == 0x25) + return true; + break; + + // @note / SapDragon: int3... + case 0xCC: + return true; + + // @note / SapDragon: ud2 + case 0x0F: + if (pCurrent[1] == 0x0B) + return true; + break; + + // @note / SapDragon: int 0x3 + case 0xCD: + if (pCurrent[1] == 0x03) + return true; + break; + + default: + break; + } + + return false; + } + }; + } // parsing + } // policies + + + template + concept IsIAllocationPolicy = requires(size_t uSize, const std::spanvecBuffer, void*& pRegion, HANDLE & hObject) + { + { T::allocate(uSize, vecBuffer, pRegion, hObject) } -> std::convertible_to; + { T::release(pRegion, hObject) } -> std::same_as; + }; + + template + concept IsStubGenerationPolicy = requires(uint8_t * pBuffer, uint32_t uSyscallNumber, void* pGadget) + { + { T::bRequiresGadget } -> std::same_as; + { T::getStubSize() } -> std::convertible_to; + { T::generate(pBuffer, uSyscallNumber, pGadget) } -> std::same_as; + }; + + template + concept IsSyscallParsingPolicy = requires(const ModuleInfo_t & module) + { + { T::parse(module) } -> std::convertible_to>; + }; + + template + struct ParserChain_t + { + static_assert(sizeof...(IParsers) > 0, "Parsedchain_t cannot be empty."); + }; + + template< + typename IAllocationPolicy, + typename IStubGenerationPolicy, + typename IFirstParser, + typename ... IFallbackParsers + > + class ManagerImpl + { + private: + static_assert(IsIAllocationPolicy, + "[Syscall] The provided allocation policy is not valid. " + "A valid allocation policy must provide two static functions: " + "1. 'static bool allocate(size_t, const std::span, void*&, HANDLE&);' " + "2. 'static void release(void*, HANDLE);'" + ); + + static_assert(IsStubGenerationPolicy, + "[Syscall] The provided stub generation policy is not valid. " + "A valid stub generation policy must provide: " + "1. A 'static constexpr bool bRequiresGadget' member. " + "2. A 'static constexpr size_t getStubSize()' function. " + "3. A 'static void generate(uint8_t*, uint32_t, void*)' function." + ); + + static_assert(IsSyscallParsingPolicy && (IsSyscallParsingPolicy && ...), + "[Syscall] One or more provided syscall parsing policies are not valid. " + "A valid parsing policy must provide a static function: " + "1. 'static std::vector parse(const ModuleInfo_t&);'" + ); + + std::mutex m_mutex; + std::vector m_vecParsedSyscalls; + void* m_pSyscallRegion = nullptr; + std::vector m_vecSyscallGadgets; + size_t m_uRegionSize = 0; + bool m_bInitialized = false; + HANDLE m_hObjectHandle = nullptr; + void* m_pVehHandle = nullptr; + public: + ManagerImpl() = default; + ~ManagerImpl() + { + if constexpr (platform::isWindows64) + if (m_pVehHandle) + RemoveVectoredExceptionHandler(m_pVehHandle); + + IAllocationPolicy::release(m_pSyscallRegion, m_hObjectHandle); + } + + ManagerImpl(const ManagerImpl&) = delete; + ManagerImpl& operator=(const ManagerImpl&) = delete; + ManagerImpl(ManagerImpl&& other) noexcept + { + std::lock_guard lock(other.m_mutex); + m_vecParsedSyscalls = std::move(other.m_vecParsedSyscalls); + m_pSyscallRegion = other.m_pSyscallRegion; + m_vecSyscallGadgets = std::move(other.m_vecSyscallGadgets); + m_uRegionSize = other.m_uRegionSize; + m_bInitialized = other.m_bInitialized; + m_hObjectHandle = other.m_hObjectHandle; + other.m_pSyscallRegion = nullptr; + other.m_hObjectHandle = nullptr; + } + + ManagerImpl& operator=(ManagerImpl&& other) noexcept + { + if (this != &other) + { + std::scoped_lock lock(m_mutex, other.m_mutex); + IAllocationPolicy::release(m_pSyscallRegion, m_hObjectHandle); + m_vecParsedSyscalls = std::move(other.m_vecParsedSyscalls); + m_pSyscallRegion = other.m_pSyscallRegion; + m_vecSyscallGadgets = std::move(other.m_vecSyscallGadgets); + m_uRegionSize = other.m_uRegionSize; + m_bInitialized = other.m_bInitialized; + m_hObjectHandle = other.m_hObjectHandle; + other.m_pSyscallRegion = nullptr; + other.m_hObjectHandle = nullptr; + } + + return *this; + } + + [[nodiscard]] bool initialize(const std::vector& vecModuleKeys = { SYSCALL_ID("ntdll.dll") }) + { + if (m_bInitialized) + return true; + + std::lock_guard lock(m_mutex); + + if (m_bInitialized) + return true; +#if SYSCALL_PLATFORM_WINDOWS_64 + if constexpr (IStubGenerationPolicy::bRequiresGadget) + if (!findSyscallGadgets()) + return false; +#endif + + m_vecParsedSyscalls.clear(); + for (const auto& moduleKey : vecModuleKeys) + { + ModuleInfo_t moduleInfo; + if (!getModuleInfo(moduleKey, moduleInfo)) + continue; + + std::vector moduleSyscalls = tryParseSyscalls(moduleInfo); + + m_vecParsedSyscalls.insert(m_vecParsedSyscalls.end(), moduleSyscalls.begin(), moduleSyscalls.end()); + } + + if (m_vecParsedSyscalls.empty()) + return false; + + if (m_vecParsedSyscalls.size() > 1) + for (size_t i = m_vecParsedSyscalls.size() - 1; i > 0; --i) + std::swap(m_vecParsedSyscalls[i], m_vecParsedSyscalls[native::rdtscp() % (i + 1)]); + + for (size_t i = 0; i < m_vecParsedSyscalls.size(); ++i) + m_vecParsedSyscalls[i].m_uOffset = static_cast(i * IStubGenerationPolicy::getStubSize()); + + + std::sort(m_vecParsedSyscalls.begin(), m_vecParsedSyscalls.end(), + [](const SyscallEntry_t& a, const SyscallEntry_t& b) { + return a.m_key < b.m_key; + }); + + m_bInitialized = createSyscalls(); + if (m_bInitialized) + { + if constexpr (std::is_same_v) + { + m_pVehHandle = AddVectoredExceptionHandler(1, VectoredExceptionHandler); + if (!m_pVehHandle) + { + IAllocationPolicy::release(m_pSyscallRegion, m_hObjectHandle); + m_pSyscallRegion = nullptr; + m_bInitialized = false; + } + } + } + + return m_bInitialized; + } + template + [[nodiscard]] SYSCALL_FORCE_INLINE Ret invoke(const SyscallKey_t& syscallId, Args... args) + { + if (!m_bInitialized) + { + if (!initialize()) + { + if constexpr (std::is_same_v) + return native::STATUS_UNSUCCESSFUL; + + return Ret{}; + } + } + auto it = std::lower_bound(m_vecParsedSyscalls.begin(), m_vecParsedSyscalls.end(), syscallId, + [](const SyscallEntry_t& entry, const SyscallKey_t& id) { + return entry.m_key < id; + }); + + if (it == m_vecParsedSyscalls.end() || it->m_key != syscallId) + { + if constexpr (std::is_same_v) + return native::STATUS_PROCEDURE_NOT_FOUND; + + return Ret{}; + } + + using Function_t = Ret(SYSCALL_API*)(Args...); + + uint8_t* pStubAddress = reinterpret_cast(m_pSyscallRegion) + it->m_uOffset; + if constexpr (std::is_same_v) + { +#if SYSCALL_PLATFORM_WINDOWS_64 + const size_t uGadgetCount = m_vecSyscallGadgets.size(); + + if (!uGadgetCount) + { + if constexpr (std::is_same_v) + return native::STATUS_UNSUCCESSFUL; + return Ret{}; + } + + const size_t uRandomIndex = native::rdtscp() % uGadgetCount; + void* pRandomGadget = m_vecSyscallGadgets[uRandomIndex]; +#else SYSCALL_PLATFORM_WINDOWS_32 + void* pRandomGadget = (void*)__readfsdword(0xC0); +#endif + + CExceptionContextGuard contextGuard(pStubAddress, pRandomGadget, it->m_uSyscallNumber); + return reinterpret_cast(pStubAddress)(std::forward(args)...); + } + + return reinterpret_cast(pStubAddress)(std::forward(args)...); + } + private: + template + std::vector tryParseSyscalls(const ModuleInfo_t& moduleInfo) + { + auto vecSyscalls = CurrentParser::parse(moduleInfo); + + if (!vecSyscalls.empty()) + return vecSyscalls; + + if constexpr (sizeof...(OtherParsers) > 0) + return tryParseSyscalls(moduleInfo); + + return vecSyscalls; + } + + + bool createSyscalls() + { + if (m_vecParsedSyscalls.empty()) + return false; +#if SYSCALL_PLATFORM_WINDOWS_64 + if constexpr (IStubGenerationPolicy::bRequiresGadget) + if (m_vecSyscallGadgets.empty()) + return false; +#endif + + m_uRegionSize = m_vecParsedSyscalls.size() * IStubGenerationPolicy::getStubSize(); + std::vector vecTempBuffer(m_uRegionSize); + + const size_t uGadgetsCount = m_vecSyscallGadgets.size(); + + for (const SyscallEntry_t& entry : m_vecParsedSyscalls) + { + uint8_t* pStubLocation = vecTempBuffer.data() + entry.m_uOffset; + void* pGadgetForStub = nullptr; +#if SYSCALL_PLATFORM_WINDOWS_64 + if constexpr (IStubGenerationPolicy::bRequiresGadget) + { + const size_t uRandomIndex = native::rdtscp() % uGadgetsCount; + pGadgetForStub = m_vecSyscallGadgets[uRandomIndex]; + } +#endif + + IStubGenerationPolicy::generate(pStubLocation, entry.m_uSyscallNumber, pGadgetForStub); + } + + return IAllocationPolicy::allocate(m_uRegionSize, vecTempBuffer, m_pSyscallRegion, m_hObjectHandle); + } + + bool getModuleInfo(SyscallKey_t moduleKey, ModuleInfo_t& info) + { + HMODULE hModule = native::getModuleBase(moduleKey); + if (!hModule) + return false; + + info.m_pModuleBase = reinterpret_cast(hModule); + + auto pDosHeader = reinterpret_cast(info.m_pModuleBase); + if (pDosHeader->e_magic != IMAGE_DOS_SIGNATURE) + return false; + + info.m_pNtHeaders = reinterpret_cast(info.m_pModuleBase + pDosHeader->e_lfanew); + if (info.m_pNtHeaders->Signature != IMAGE_NT_SIGNATURE) + return false; + + auto uExportRva = info.m_pNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress; + if (!uExportRva) + return false; + + info.m_pExportDir = reinterpret_cast(info.m_pModuleBase + uExportRva); + + return true; + } + +#if SYSCALL_PLATFORM_WINDOWS_64 + bool findSyscallGadgets() + { + ModuleInfo_t ntdll; + if (!getModuleInfo(SYSCALL_ID("ntdll.dll"), ntdll)) + return false; + + IMAGE_SECTION_HEADER* pSections = IMAGE_FIRST_SECTION(ntdll.m_pNtHeaders); + uint8_t* pTextSection = nullptr; + uint32_t uTextSectionSize = 0; + for (int i = 0; i < ntdll.m_pNtHeaders->FileHeader.NumberOfSections; ++i) + { + if (hashing::calculateHashRuntime(reinterpret_cast(pSections[i].Name)) == hashing::calculateHash(".text")) + { + pTextSection = ntdll.m_pModuleBase + pSections[i].VirtualAddress; + uTextSectionSize = pSections[i].Misc.VirtualSize; + break; + } + } + + if (!pTextSection || !uTextSectionSize) + return false; + + m_vecSyscallGadgets.clear(); + for (DWORD i = 0; i < uTextSectionSize - 2; ++i) + if (pTextSection[i] == 0x0F && pTextSection[i + 1] == 0x05 && pTextSection[i + 2] == 0xC3) + m_vecSyscallGadgets.push_back(&pTextSection[i]); + + return !m_vecSyscallGadgets.empty(); + } +#endif + + }; + + using DefaultParserChain = syscall::ParserChain_t< + syscall::policies::parser::directory, + syscall::policies::parser::signature + >; + + // @note / sapdragon: fucking templates, is that a legal cpp hack? unpack overloads... + template + class Manager : public Manager + { + }; + + template< typename AllocPolicy, typename StubPolicy, IsSyscallParsingPolicy... ParsersInChain > + class Manager> + : public ManagerImpl + { + }; + + template< typename AllocPolicy, typename StubPolicy, typename FirstParser, typename... FallbackParsers> + class Manager + : public ManagerImpl + { + }; +} +#if SYSCALL_PLATFORM_WINDOWS_64 +using SyscallSectionGadget = syscall::Manager; +using SyscallHeapGadget = syscall::Manager; +#endif +using SyscallSectionDirect = syscall::Manager; + +#endif \ No newline at end of file diff --git a/auth.cpp b/auth.cpp index 90fd644..26e6001 100644 --- a/auth.cpp +++ b/auth.cpp @@ -59,7 +59,9 @@ #include #define SHA256_HASH_SIZE 32 +#include "protection/protection.h" +DWORD64 Function_Address; static std::string hexDecode(const std::string& hex); std::string get_str_between_two_str(const std::string& s, const std::string& start_delim, const std::string& stop_delim); int VerifyPayload(std::string signature, std::string timestamp, std::string body); @@ -188,7 +190,7 @@ void KeyAuth::api::init() if (json[(XorStr("success"))]) { if (json[(XorStr("newSession"))]) { - Sleep(100); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); } sessionid = json[(XorStr("sessionid"))]; initialized = true; @@ -1366,7 +1368,7 @@ std::string KeyAuth::api::var(std::string varid) { } void KeyAuth::api::log(std::string message) { - checkInit(); + checkInit(); // why call when ppl can nop it, program will fail if its 0 char acUserName[100]; DWORD nUserName = sizeof(acUserName); @@ -1659,7 +1661,6 @@ int VerifyPayload(std::string signature, std::string timestamp, std::string body return value & 0xFFFF; } - // credits https://stackoverflow.com/a/3790661 static std::string hexDecode(const std::string& hex) { @@ -1673,6 +1674,7 @@ static std::string hexDecode(const std::string& hex) } return newString; } + // credits https://stackoverflow.com/a/43002794 std::string get_str_between_two_str(const std::string& s, const std::string& start_delim, @@ -1731,6 +1733,7 @@ void error(std::string message) { system((XorStr("start cmd /C \"color b && title Error && echo ").c_str() + message + XorStr(" && timeout /t 5\"")).c_str()); LI_FN(__fastfail)(0); } + // code submitted in pull request from https://github.com/Roblox932 auto check_section_integrity( const char *section_name, bool fix = false ) -> bool { @@ -1855,20 +1858,21 @@ void checkAtoms() { LI_FN(exit)(13); LI_FN(__fastfail)(0); } - Sleep(1000); // thread interval + std::this_thread::sleep_for(std::chrono::milliseconds(1000)); } } void checkFiles() { + // who did this lol, calling exit and fastfail + // use fastfail if Security issue and exit if safe while (true) { std::string file_path = XorStr("C:\\ProgramData\\").c_str() + seed; DWORD file_attr = LI_FN(GetFileAttributesA)(file_path.c_str()); if (file_attr == INVALID_FILE_ATTRIBUTES || (file_attr & FILE_ATTRIBUTE_DIRECTORY)) { LI_FN(exit)(14); - LI_FN(__fastfail)(0); } - Sleep(2000); // thread interval, files are more intensive than Atom tables which use memory + std::this_thread::sleep_for(std::chrono::milliseconds(2000)); } } @@ -1880,10 +1884,9 @@ void checkRegistry() { LONG result = LI_FN(RegOpenKeyExA)(HKEY_CURRENT_USER, regPath.c_str(), 0, KEY_READ, &hKey); if (result != ERROR_SUCCESS) { LI_FN(exit)(15); - LI_FN(__fastfail)(0); } LI_FN(RegCloseKey)(hKey); - Sleep(1500); // thread interval + std::this_thread::sleep_for(std::chrono::milliseconds(1500)); } } @@ -2060,6 +2063,7 @@ void checkInit() { error(XorStr("You need to run the KeyAuthApp.init(); function before any other KeyAuth functions")); } } + // code submitted in pull request from https://github.com/BINM7MD BOOL bDataCompare(const BYTE* pData, const BYTE* bMask, const char* szMask) { @@ -2086,12 +2090,17 @@ DWORD64 FindPattern(BYTE* bMask, const char* szMask) return NULL; } -DWORD64 Function_Address; void modify() { + + // 3% CPU usage its okay + // code submitted in pull request from https://github.com/Roblox932 check_section_integrity( XorStr( ".text" ).c_str( ), true ); + // submitted by https://github.com/officialchristheg + CProtection::InitializeProtection(); + while (true) { // new code by https://github.com/LiamG53 @@ -2116,7 +2125,7 @@ void modify() if ((DWORD64)Instruction == 0xE9) { error(XorStr("Pattern checksum failed, don't tamper with the program.")); } - Sleep(50); + std::this_thread::sleep_for(std::chrono::milliseconds(50)); } } diff --git a/curl/curl.h b/curl/curl.h index 183488a..6567950 100644 --- a/curl/curl.h +++ b/curl/curl.h @@ -1325,7 +1325,7 @@ typedef enum { /* Set the interface string to use as outgoing network interface */ CURLOPT(CURLOPT_INTERFACE, CURLOPTTYPE_STRINGPOINT, 62), - /* Set the krb4/5 security level, this also enables krb4/5 awareness. This + /* Set the krb4/5 Security level, this also enables krb4/5 awareness. This * is a string, 'clear', 'safe', 'confidential' or 'private'. If the string * is set but does not match one of these, 'private' will be used. */ CURLOPT(CURLOPT_KRBLEVEL, CURLOPTTYPE_STRINGPOINT, 63), diff --git a/library.vcxproj b/library.vcxproj index 884ee9e..2b7414b 100644 --- a/library.vcxproj +++ b/library.vcxproj @@ -146,7 +146,7 @@ true NotUsing pch.h - stdcpp17 + stdcpp20 MultiThreadedDLL None %(AdditionalIncludeDirectories) @@ -167,6 +167,18 @@ + + + + + + + + + + + + @@ -294,10 +306,21 @@ + + + + + + + + + + + @@ -313,4 +336,4 @@ - + \ No newline at end of file diff --git a/library.vcxproj.filters b/library.vcxproj.filters index d518cb1..3fc0934 100644 --- a/library.vcxproj.filters +++ b/library.vcxproj.filters @@ -1,857 +1,171 @@ -  - - {4FC737F1-C7A5-4376-A066-2A32D752A2FF} - cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx - - - {93995380-89BD-4b04-88EB-625FBE52EBFB} - h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd - - - {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} - rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms - - - {864c9f28-b529-408a-93b0-9c8423c793e8} - - - {0c5bac00-2508-41c7-add9-a53839eceb90} - - - {6ef545b7-b1cb-4adf-a67a-47abb93bd56d} - + + + + + + + + + + + + + + + + + - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - Header Files - - - Header Files\curl - - - Header Files\curl - - - Header Files\curl - - - Header Files\curl - - - Header Files\curl - - - Header Files\curl - - - Header Files\curl - - - Header Files\curl - - - Header Files\curl - - - Header Files\nlohmann - - - Header Files\nlohmann - - - Header Files\nlohmann - - - Header Files\nlohmann - - - Header Files\nlohmann - - - Header Files\nlohmann - - - Header Files\nlohmann - - - Header Files\nlohmann - - - Header Files\nlohmann - - - Header Files\nlohmann - - - Header Files\nlohmann - - - Header Files\nlohmann - - - Header Files\nlohmann - - - Header Files\nlohmann - - - Header Files\nlohmann - - - Header Files\nlohmann - - - Header Files\nlohmann - - - Header Files\nlohmann - - - Header Files\nlohmann - - - Header Files\nlohmann - - - Header Files\nlohmann - - - Header Files\nlohmann - - - Header Files\nlohmann - - - Header Files\nlohmann - - - Header Files\nlohmann - - - Header Files\nlohmann - - - Header Files\nlohmann - - - Header Files\nlohmann - - - Header Files\nlohmann - - - Header Files\nlohmann - - - Header Files\nlohmann - - - Header Files\nlohmann - - - Header Files\nlohmann - - - Header Files\nlohmann - - - Header Files\nlohmann - - - Header Files\nlohmann - - - Header Files\nlohmann - - - Source Files - - - Source Files - - - Source Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - - - Header Files\curl - + - - Header Files\curl - - - Header Files\curl - - - - - - - {4FC737F1-C7A5-4376-A066-2A32D752A2FF} - cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx - - - {93995380-89BD-4b04-88EB-625FBE52EBFB} - h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd - - - {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} - rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms - - - {864c9f28-b529-408a-93b0-9c8423c793e8} - - - {0c5bac00-2508-41c7-add9-a53839eceb90} - - - {6ef545b7-b1cb-4adf-a67a-47abb93bd56d} - - - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - - - Header Files - - - Header Files\curl - - - Header Files\curl - - - Header Files\curl - - - Header Files\curl - - - Header Files\curl - - - Header Files\curl - - - Header Files\curl - - - Header Files\curl - - - Header Files\curl - - - Header Files\nlohmann - - - Header Files\nlohmann - - - Header Files\nlohmann - - - Header Files\nlohmann - - - Header Files\nlohmann - - - Header Files\nlohmann - - - Header Files\nlohmann - - - Header Files\nlohmann - - - Header Files\nlohmann - - - Header Files\nlohmann - - - Header Files\nlohmann - - - Header Files\nlohmann - - - Header Files\nlohmann - - - Header Files\nlohmann - - - Header Files\nlohmann - - - Header Files\nlohmann - - - Header Files\nlohmann - - - Header Files\nlohmann - - - Header Files\nlohmann - - - Header Files\nlohmann - - - Header Files\nlohmann - - - Header Files\nlohmann - - - Header Files\nlohmann - - - Header Files\nlohmann - - - Header Files\nlohmann - - - Header Files\nlohmann - - - Header Files\nlohmann - - - Header Files\nlohmann - - - Header Files\nlohmann - - - Header Files\nlohmann - - - Header Files\nlohmann - - - Header Files\nlohmann - - - Header Files\nlohmann - - - Header Files\nlohmann - - - Header Files\nlohmann - - - Header Files\nlohmann - - - Header Files\nlohmann - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Header Files - - - Header Files\libsodium - - - Header Files\libsodium - - - Header Files\libsodium - - - Header Files\libsodium - - - Header Files\libsodium - - - Header Files\libsodium - - - Header Files\libsodium - - - Header Files\libsodium - - - Header Files\libsodium - - - Header Files\libsodium - - - Header Files\libsodium - - - Header Files\libsodium - - - Header Files\libsodium - - - Header Files\libsodium - - - Header Files\libsodium - - - Header Files\libsodium - - - Header Files\libsodium - - - Header Files\libsodium - - - Header Files\libsodium - - - Header Files\libsodium - - - Header Files\libsodium - - - Header Files\libsodium - - - Header Files\libsodium - - - Header Files\libsodium - - - Header Files\libsodium - - - Header Files\libsodium - - - Header Files\libsodium - - - Header Files\libsodium - - - Header Files\libsodium - - - Header Files\libsodium - - - Header Files\libsodium - - - Header Files\libsodium - - - Header Files\libsodium - - - Header Files\libsodium - - - Header Files\libsodium - - - Header Files\libsodium - - - Header Files\libsodium - - - Header Files\libsodium - - - Header Files\libsodium - - - Header Files\libsodium - - - Header Files\libsodium - - - Header Files\libsodium - - - Header Files\libsodium - - - Header Files\libsodium - - - Header Files\libsodium - - - Header Files\libsodium - - - Header Files\libsodium - - - Header Files\libsodium - - - Header Files\libsodium - - - Header Files\libsodium - - - Header Files\libsodium - - - Header Files\libsodium - - - Header Files\libsodium - - - Header Files\libsodium - - - Header Files\libsodium - - - Header Files\libsodium - - - Header Files\libsodium - - - Header Files\libsodium - - - Header Files\libsodium - - - Header Files\libsodium - - - Header Files\libsodium - - - Header Files\libsodium - - - Header Files\libsodium - - - Header Files\libsodium - - - Header Files\libsodium - - - Header Files\libsodium - - - Header Files\libsodium - - - - - Header Files\curl - - - - - - Header Files\curl - - - Header Files\curl - + + \ No newline at end of file diff --git a/libsodium/sodium/crypto_aead_aes256gcm.h b/libsodium/sodium/crypto_aead_aes256gcm.h index 9baeb3f..c0f5b33 100644 --- a/libsodium/sodium/crypto_aead_aes256gcm.h +++ b/libsodium/sodium/crypto_aead_aes256gcm.h @@ -10,7 +10,7 @@ * message sizes. * * In addition, nonces are short and repeated nonces would totally destroy - * the security of this scheme. + * the Security of this scheme. * * Nonces should thus come from atomic counters, which can be difficult to * set up in a distributed environment. diff --git a/libsodium/sodium/crypto_stream.h b/libsodium/sodium/crypto_stream.h index 88dab5f..bb320b5 100644 --- a/libsodium/sodium/crypto_stream.h +++ b/libsodium/sodium/crypto_stream.h @@ -4,7 +4,7 @@ /* * WARNING: This is just a stream cipher. It is NOT authenticated encryption. * While it provides some protection against eavesdropping, it does NOT - * provide any security against active attacks. + * provide any Security against active attacks. * Unless you know what you're doing, what you are looking for is probably * the crypto_box functions. */ diff --git a/libsodium/sodium/crypto_stream_chacha20.h b/libsodium/sodium/crypto_stream_chacha20.h index 4088975..85a858e 100644 --- a/libsodium/sodium/crypto_stream_chacha20.h +++ b/libsodium/sodium/crypto_stream_chacha20.h @@ -4,7 +4,7 @@ /* * WARNING: This is just a stream cipher. It is NOT authenticated encryption. * While it provides some protection against eavesdropping, it does NOT - * provide any security against active attacks. + * provide any Security against active attacks. * Unless you know what you're doing, what you are looking for is probably * the crypto_box functions. */ diff --git a/libsodium/sodium/crypto_stream_salsa20.h b/libsodium/sodium/crypto_stream_salsa20.h index 45b3b3e..3de2f79 100644 --- a/libsodium/sodium/crypto_stream_salsa20.h +++ b/libsodium/sodium/crypto_stream_salsa20.h @@ -4,7 +4,7 @@ /* * WARNING: This is just a stream cipher. It is NOT authenticated encryption. * While it provides some protection against eavesdropping, it does NOT - * provide any security against active attacks. + * provide any Security against active attacks. * Unless you know what you're doing, what you are looking for is probably * the crypto_box functions. */ diff --git a/libsodium/sodium/crypto_stream_salsa2012.h b/libsodium/sodium/crypto_stream_salsa2012.h index 6c5d303..3f582ab 100644 --- a/libsodium/sodium/crypto_stream_salsa2012.h +++ b/libsodium/sodium/crypto_stream_salsa2012.h @@ -4,7 +4,7 @@ /* * WARNING: This is just a stream cipher. It is NOT authenticated encryption. * While it provides some protection against eavesdropping, it does NOT - * provide any security against active attacks. + * provide any Security against active attacks. * Unless you know what you're doing, what you are looking for is probably * the crypto_box functions. */ diff --git a/libsodium/sodium/crypto_stream_salsa208.h b/libsodium/sodium/crypto_stream_salsa208.h index d574f30..f872d26 100644 --- a/libsodium/sodium/crypto_stream_salsa208.h +++ b/libsodium/sodium/crypto_stream_salsa208.h @@ -4,7 +4,7 @@ /* * WARNING: This is just a stream cipher. It is NOT authenticated encryption. * While it provides some protection against eavesdropping, it does NOT - * provide any security against active attacks. + * provide any Security against active attacks. * Unless you know what you're doing, what you are looking for is probably * the crypto_box functions. */ diff --git a/libsodium/sodium/crypto_stream_xchacha20.h b/libsodium/sodium/crypto_stream_xchacha20.h index c4002db..0b6f07d 100644 --- a/libsodium/sodium/crypto_stream_xchacha20.h +++ b/libsodium/sodium/crypto_stream_xchacha20.h @@ -4,7 +4,7 @@ /* * WARNING: This is just a stream cipher. It is NOT authenticated encryption. * While it provides some protection against eavesdropping, it does NOT - * provide any security against active attacks. + * provide any Security against active attacks. * Unless you know what you're doing, what you are looking for is probably * the crypto_box functions. */ diff --git a/libsodium/sodium/crypto_stream_xsalsa20.h b/libsodium/sodium/crypto_stream_xsalsa20.h index 20034e3..9371517 100644 --- a/libsodium/sodium/crypto_stream_xsalsa20.h +++ b/libsodium/sodium/crypto_stream_xsalsa20.h @@ -4,7 +4,7 @@ /* * WARNING: This is just a stream cipher. It is NOT authenticated encryption. * While it provides some protection against eavesdropping, it does NOT - * provide any security against active attacks. + * provide any Security against active attacks. * Unless you know what you're doing, what you are looking for is probably * the crypto_box functions. */ diff --git a/protection/antiDLL/antiDLL.cpp b/protection/antiDLL/antiDLL.cpp new file mode 100644 index 0000000..ff176e5 --- /dev/null +++ b/protection/antiDLL/antiDLL.cpp @@ -0,0 +1,90 @@ +#include "antiDLL.h" +#include "xorstr.hpp" +#include "utils.hpp" + +bool AntiDLL::IsDllLoaded(const wchar_t* dllName) { + if (GetModuleHandleW(dllName) != nullptr) { + return true; + } + + HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); + if (hSnapshot == INVALID_HANDLE_VALUE) { + return false; + } + + PROCESSENTRY32W pe32; + pe32.dwSize = sizeof(PROCESSENTRY32W); + + if (!Process32FirstW(hSnapshot, &pe32)) { + CloseHandle(hSnapshot); + return false; + } + + bool found = false; + do { + if (pe32.th32ProcessID <= 4) + continue; + + HANDLE hModuleSnapshot = CreateToolhelp32Snapshot( + TH32CS_SNAPMODULE | TH32CS_SNAPMODULE32, + pe32.th32ProcessID + ); + + if (hModuleSnapshot == INVALID_HANDLE_VALUE) { + continue; + } + + MODULEENTRY32W me32; + me32.dwSize = sizeof(MODULEENTRY32W); + + if (Module32FirstW(hModuleSnapshot, &me32)) { + do { + if (_wcsicmp(me32.szModule, dllName) == 0) { + found = true; + CloseHandle(hModuleSnapshot); + goto cleanup; + } + } while (Module32NextW(hModuleSnapshot, &me32)); + } + + CloseHandle(hModuleSnapshot); + + } while (Process32NextW(hSnapshot, &pe32)); + +cleanup: + CloseHandle(hSnapshot); + return found; +} + +void AntiDLL::DetectX64dbg() { + if (IsValid) { + const wchar_t* dllName = (XorStr(L"x64dbg.dll").c_str()); + if (IsDllLoaded(dllName)) { + utils::raiseTermination(XorStr("KeyAuth Security Violation: 0xC0000605").c_str()); + } + } +} + +void AntiDLL::DetectCheatEngine() { + if (IsValid) { + const wchar_t* dllName = (XorStr(L"lua53-64.dll").c_str()); + if (IsDllLoaded(dllName)) { + utils::raiseTermination(XorStr("KeyAuth Security Violation: 0xC0000904").c_str()); + } + } +} + +void AntiDLL::DetectEchoMirage() { + if (IsValid) { + const wchar_t* dllName = (XorStr(L"EchoMirageHooks64.dll").c_str()); + if (IsDllLoaded(dllName)) { + utils::raiseTermination(XorStr("KeyAuth Security Violation: 0xC0000902").c_str()); + } + } +} + +void AntiDLL::Detection() { + AntiDLL::DetectCheatEngine(); + AntiDLL::DetectEchoMirage(); + AntiDLL::DetectX64dbg(); +} \ No newline at end of file diff --git a/protection/antiDLL/antiDLL.h b/protection/antiDLL/antiDLL.h new file mode 100644 index 0000000..3b99990 --- /dev/null +++ b/protection/antiDLL/antiDLL.h @@ -0,0 +1,19 @@ +#pragma once +#include +#include +#include + +class AntiDLL { +public: + static void SetValid(bool valid) { IsValid = valid; } + + static void Detection(); + +private: + static bool IsDllLoaded(const wchar_t* dllName); + static void DetectX64dbg(); + static void DetectCheatEngine(); + static void DetectEchoMirage(); + + inline static bool IsValid = true; +}; diff --git a/protection/hooking/detours/creatwth.cpp b/protection/hooking/detours/creatwth.cpp new file mode 100644 index 0000000..c2819af --- /dev/null +++ b/protection/hooking/detours/creatwth.cpp @@ -0,0 +1,1783 @@ +////////////////////////////////////////////////////////////////////////////// +// +// Create a process with a DLL (creatwth.cpp of detours.lib) +// +// Microsoft Research Detours Package, Version 4.0.1 +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// + +// #define DETOUR_DEBUG 1 +#define DETOURS_INTERNAL +#include "detours.h" +#include + +#if DETOURS_VERSION != 0x4c0c1 // 0xMAJORcMINORcPATCH +#error detours.h version mismatch +#endif + +#define IMPORT_DIRECTORY OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT] +#define BOUND_DIRECTORY OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT] +#define CLR_DIRECTORY OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR] +#define IAT_DIRECTORY OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IAT] + +////////////////////////////////////////////////////////////////////////////// +// +const GUID DETOUR_EXE_HELPER_GUID = { /* ea0251b9-5cde-41b5-98d0-2af4a26b0fee */ + 0xea0251b9, 0x5cde, 0x41b5, + { 0x98, 0xd0, 0x2a, 0xf4, 0xa2, 0x6b, 0x0f, 0xee }}; + +////////////////////////////////////////////////////////////////////////////// +// +// Enumerate through modules in the target process. +// +static PVOID LoadNtHeaderFromProcess(_In_ HANDLE hProcess, + _In_ HMODULE hModule, + _Out_ PIMAGE_NT_HEADERS32 pNtHeader) +{ + ZeroMemory(pNtHeader, sizeof(*pNtHeader)); + PBYTE pbModule = (PBYTE)hModule; + + if (pbModule == NULL) { + SetLastError(ERROR_INVALID_PARAMETER); + return NULL; + } + + MEMORY_BASIC_INFORMATION mbi; + ZeroMemory(&mbi, sizeof(mbi)); + + if (VirtualQueryEx(hProcess, hModule, &mbi, sizeof(mbi)) == 0) { + return NULL; + } + + IMAGE_DOS_HEADER idh; + if (!ReadProcessMemory(hProcess, pbModule, &idh, sizeof(idh), NULL)) { + DETOUR_TRACE(("ReadProcessMemory(idh@%p..%p) failed: %lu\n", + pbModule, pbModule + sizeof(idh), GetLastError())); + return NULL; + } + + if (idh.e_magic != IMAGE_DOS_SIGNATURE || + (DWORD)idh.e_lfanew > mbi.RegionSize || + (DWORD)idh.e_lfanew < sizeof(idh)) { + + SetLastError(ERROR_BAD_EXE_FORMAT); + return NULL; + } + + if (!ReadProcessMemory(hProcess, pbModule + idh.e_lfanew, + pNtHeader, sizeof(*pNtHeader), NULL)) { + DETOUR_TRACE(("ReadProcessMemory(inh@%p..%p:%p) failed: %lu\n", + pbModule + idh.e_lfanew, + pbModule + idh.e_lfanew + sizeof(*pNtHeader), + pbModule, + GetLastError())); + return NULL; + } + + if (pNtHeader->Signature != IMAGE_NT_SIGNATURE) { + SetLastError(ERROR_BAD_EXE_FORMAT); + return NULL; + } + + return pbModule + idh.e_lfanew; +} + +static HMODULE EnumerateModulesInProcess(_In_ HANDLE hProcess, + _In_opt_ HMODULE hModuleLast, + _Out_ PIMAGE_NT_HEADERS32 pNtHeader, + _Out_opt_ PVOID *pRemoteNtHeader) +{ + ZeroMemory(pNtHeader, sizeof(*pNtHeader)); + if (pRemoteNtHeader) { + *pRemoteNtHeader = NULL; + } + + PBYTE pbLast = (PBYTE)hModuleLast + MM_ALLOCATION_GRANULARITY; + + MEMORY_BASIC_INFORMATION mbi; + ZeroMemory(&mbi, sizeof(mbi)); + + // Find the next memory region that contains a mapped PE image. + // + + for (;; pbLast = (PBYTE)mbi.BaseAddress + mbi.RegionSize) { + if (VirtualQueryEx(hProcess, (PVOID)pbLast, &mbi, sizeof(mbi)) == 0) { + break; + } + + // Usermode address space has such an unaligned region size always at the + // end and only at the end. + // + if ((mbi.RegionSize & 0xfff) == 0xfff) { + break; + } + if (((PBYTE)mbi.BaseAddress + mbi.RegionSize) < pbLast) { + break; + } + + // Skip uncommitted regions and guard pages. + // + if ((mbi.State != MEM_COMMIT) || + ((mbi.Protect & 0xff) == PAGE_NOACCESS) || + (mbi.Protect & PAGE_GUARD)) { + continue; + } + + PVOID remoteHeader + = LoadNtHeaderFromProcess(hProcess, (HMODULE)pbLast, pNtHeader); + if (remoteHeader) { + if (pRemoteNtHeader) { + *pRemoteNtHeader = remoteHeader; + } + + return (HMODULE)pbLast; + } + } + return NULL; +} + +////////////////////////////////////////////////////////////////////////////// +// +// Find payloads in target process. +// + +static PVOID FindDetourSectionInRemoteModule(_In_ HANDLE hProcess, + _In_ HMODULE hModule, + _In_ const IMAGE_NT_HEADERS32 *pNtHeader, + _In_ PVOID pRemoteNtHeader) +{ + if (pNtHeader->FileHeader.SizeOfOptionalHeader == 0) { + SetLastError(ERROR_EXE_MARKED_INVALID); + return NULL; + } + + PIMAGE_SECTION_HEADER pRemoteSectionHeaders + = (PIMAGE_SECTION_HEADER)((PBYTE)pRemoteNtHeader + + sizeof(pNtHeader->Signature) + + sizeof(pNtHeader->FileHeader) + + pNtHeader->FileHeader.SizeOfOptionalHeader); + + IMAGE_SECTION_HEADER header; + for (DWORD n = 0; n < pNtHeader->FileHeader.NumberOfSections; ++n) { + if (!ReadProcessMemory(hProcess, pRemoteSectionHeaders + n, &header, sizeof(header), NULL)) { + DETOUR_TRACE(("ReadProcessMemory(ish@%p..%p) failed: %lu\n", + pRemoteSectionHeaders + n, + (PBYTE)(pRemoteSectionHeaders + n) + sizeof(header), + GetLastError())); + + return NULL; + } + + if (strcmp((PCHAR)header.Name, ".detour") == 0) { + if (header.VirtualAddress == 0 || + header.SizeOfRawData == 0) { + + break; + } + + SetLastError(NO_ERROR); + return (PBYTE)hModule + header.VirtualAddress; + } + } + + SetLastError(ERROR_EXE_MARKED_INVALID); + return NULL; +} + +static PVOID FindPayloadInRemoteDetourSection(_In_ HANDLE hProcess, + _In_ REFGUID rguid, + _Out_opt_ DWORD *pcbData, + _In_ PVOID pvRemoteDetoursSection) +{ + if (pcbData) { + *pcbData = 0; + } + + PBYTE pbData = (PBYTE)pvRemoteDetoursSection; + + DETOUR_SECTION_HEADER header; + if (!ReadProcessMemory(hProcess, pbData, &header, sizeof(header), NULL)) { + DETOUR_TRACE(("ReadProcessMemory(dsh@%p..%p) failed: %lu\n", + pbData, + pbData + sizeof(header), + GetLastError())); + return NULL; + } + + if (header.cbHeaderSize < sizeof(DETOUR_SECTION_HEADER) || + header.nSignature != DETOUR_SECTION_HEADER_SIGNATURE) { + SetLastError(ERROR_EXE_MARKED_INVALID); + return NULL; + } + + if (header.nDataOffset == 0) { + header.nDataOffset = header.cbHeaderSize; + } + + for (PVOID pvSection = pbData + header.nDataOffset; pvSection < pbData + header.cbDataSize;) { + DETOUR_SECTION_RECORD section; + if (!ReadProcessMemory(hProcess, pvSection, §ion, sizeof(section), NULL)) { + DETOUR_TRACE(("ReadProcessMemory(dsr@%p..%p) failed: %lu\n", + pvSection, + (PBYTE)pvSection + sizeof(section), + GetLastError())); + return NULL; + } + + if (DetourAreSameGuid(section.guid, rguid)) { + if (pcbData) { + *pcbData = section.cbBytes - sizeof(section); + } + SetLastError(NO_ERROR); + return (DETOUR_SECTION_RECORD *)pvSection + 1; + } + + pvSection = (PBYTE)pvSection + section.cbBytes; + } + + return NULL; +} + +_Success_(return != NULL) +PVOID WINAPI DetourFindRemotePayload(_In_ HANDLE hProcess, + _In_ REFGUID rguid, + _Out_opt_ DWORD *pcbData) +{ + if (hProcess == NULL) { + SetLastError(ERROR_INVALID_HANDLE); + return NULL; + } + + IMAGE_NT_HEADERS32 header; + PVOID pvRemoteHeader; + for (HMODULE hMod = NULL; (hMod = EnumerateModulesInProcess(hProcess, hMod, &header, &pvRemoteHeader)) != NULL;) { + PVOID pvData = FindDetourSectionInRemoteModule(hProcess, hMod, &header, pvRemoteHeader); + if (pvData != NULL) { + pvData = FindPayloadInRemoteDetourSection(hProcess, rguid, pcbData, pvData); + if (pvData != NULL) { + return pvData; + } + } + } + + SetLastError(ERROR_MOD_NOT_FOUND); + return NULL; +} + +////////////////////////////////////////////////////////////////////////////// +// +// Find a region of memory in which we can create a replacement import table. +// +static PBYTE FindAndAllocateNearBase(HANDLE hProcess, PBYTE pbModule, PBYTE pbBase, DWORD cbAlloc) +{ + MEMORY_BASIC_INFORMATION mbi; + ZeroMemory(&mbi, sizeof(mbi)); + + PBYTE pbLast = pbBase; + for (;; pbLast = (PBYTE)mbi.BaseAddress + mbi.RegionSize) { + + ZeroMemory(&mbi, sizeof(mbi)); + if (VirtualQueryEx(hProcess, (PVOID)pbLast, &mbi, sizeof(mbi)) == 0) { + if (GetLastError() == ERROR_INVALID_PARAMETER) { + break; + } + DETOUR_TRACE(("VirtualQueryEx(%p) failed: %lu\n", + pbLast, GetLastError())); + break; + } + // Usermode address space has such an unaligned region size always at the + // end and only at the end. + // + if ((mbi.RegionSize & 0xfff) == 0xfff) { + break; + } + + // Skip anything other than a pure free region. + // + if (mbi.State != MEM_FREE) { + continue; + } + + // Use the max of mbi.BaseAddress and pbBase, in case mbi.BaseAddress < pbBase. + PBYTE pbAddress = (PBYTE)mbi.BaseAddress > pbBase ? (PBYTE)mbi.BaseAddress : pbBase; + + // Round pbAddress up to the nearest MM allocation boundary. + const DWORD_PTR mmGranularityMinusOne = (DWORD_PTR)(MM_ALLOCATION_GRANULARITY -1); + pbAddress = (PBYTE)(((DWORD_PTR)pbAddress + mmGranularityMinusOne) & ~mmGranularityMinusOne); + +#ifdef _WIN64 + // The offset from pbModule to any replacement import must fit into 32 bits. + // For simplicity, we check that the offset to the last byte fits into 32 bits, + // instead of the largest offset we'll actually use. The values are very similar. + const size_t GB4 = ((((size_t)1) << 32) - 1); + if ((size_t)(pbAddress + cbAlloc - 1 - pbModule) > GB4) { + DETOUR_TRACE(("FindAndAllocateNearBase(1) failing due to distance >4GB %p\n", pbAddress)); + return NULL; + } +#else + UNREFERENCED_PARAMETER(pbModule); +#endif + + DETOUR_TRACE(("Free region %p..%p\n", + mbi.BaseAddress, + (PBYTE)mbi.BaseAddress + mbi.RegionSize)); + + for (; pbAddress < (PBYTE)mbi.BaseAddress + mbi.RegionSize; pbAddress += MM_ALLOCATION_GRANULARITY) { + PBYTE pbAlloc = (PBYTE)VirtualAllocEx(hProcess, pbAddress, cbAlloc, + MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); + if (pbAlloc == NULL) { + DETOUR_TRACE(("VirtualAllocEx(%p) failed: %lu\n", pbAddress, GetLastError())); + continue; + } +#ifdef _WIN64 + // The offset from pbModule to any replacement import must fit into 32 bits. + if ((size_t)(pbAddress + cbAlloc - 1 - pbModule) > GB4) { + DETOUR_TRACE(("FindAndAllocateNearBase(2) failing due to distance >4GB %p\n", pbAddress)); + return NULL; + } +#endif + DETOUR_TRACE(("[%p..%p] Allocated for import table.\n", + pbAlloc, pbAlloc + cbAlloc)); + return pbAlloc; + } + } + return NULL; +} + +static inline DWORD PadToDword(DWORD dw) +{ + return (dw + 3) & ~3u; +} + +static inline DWORD PadToDwordPtr(DWORD dw) +{ + return (dw + 7) & ~7u; +} + +static inline HRESULT ReplaceOptionalSizeA(_Inout_z_count_(cchDest) LPSTR pszDest, + _In_ size_t cchDest, + _In_z_ LPCSTR pszSize) +{ + if (cchDest == 0 || pszDest == NULL || pszSize == NULL || + pszSize[0] == '\0' || pszSize[1] == '\0' || pszSize[2] != '\0') { + + // can not write into empty buffer or with string other than two chars. + return ERROR_INVALID_PARAMETER; + } + + for (; cchDest >= 2; cchDest--, pszDest++) { + if (pszDest[0] == '?' && pszDest[1] == '?') { + pszDest[0] = pszSize[0]; + pszDest[1] = pszSize[1]; + break; + } + } + + return S_OK; +} + +static BOOL RecordExeRestore(HANDLE hProcess, HMODULE hModule, DETOUR_EXE_RESTORE& der) +{ + // Save the various headers for DetourRestoreAfterWith. + ZeroMemory(&der, sizeof(der)); + der.cb = sizeof(der); + + der.pidh = (PBYTE)hModule; + der.cbidh = sizeof(der.idh); + if (!ReadProcessMemory(hProcess, der.pidh, &der.idh, sizeof(der.idh), NULL)) { + DETOUR_TRACE(("ReadProcessMemory(idh@%p..%p) failed: %lu\n", + der.pidh, der.pidh + der.cbidh, GetLastError())); + return FALSE; + } + DETOUR_TRACE(("IDH: %p..%p\n", der.pidh, der.pidh + der.cbidh)); + + // We read the NT header in two passes to get the full size. + // First we read just the Signature and FileHeader. + der.pinh = der.pidh + der.idh.e_lfanew; + der.cbinh = FIELD_OFFSET(IMAGE_NT_HEADERS, OptionalHeader); + if (!ReadProcessMemory(hProcess, der.pinh, &der.inh, der.cbinh, NULL)) { + DETOUR_TRACE(("ReadProcessMemory(inh@%p..%p) failed: %lu\n", + der.pinh, der.pinh + der.cbinh, GetLastError())); + return FALSE; + } + + // Second we read the OptionalHeader and Section headers. + der.cbinh = (FIELD_OFFSET(IMAGE_NT_HEADERS, OptionalHeader) + + der.inh.FileHeader.SizeOfOptionalHeader + + der.inh.FileHeader.NumberOfSections * sizeof(IMAGE_SECTION_HEADER)); + + if (der.cbinh > sizeof(der.raw)) { + return FALSE; + } + + if (!ReadProcessMemory(hProcess, der.pinh, &der.inh, der.cbinh, NULL)) { + DETOUR_TRACE(("ReadProcessMemory(inh@%p..%p) failed: %lu\n", + der.pinh, der.pinh + der.cbinh, GetLastError())); + return FALSE; + } + DETOUR_TRACE(("INH: %p..%p\n", der.pinh, der.pinh + der.cbinh)); + + // Third, we read the CLR header + + if (der.inh.OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC) { + if (der.inh32.CLR_DIRECTORY.VirtualAddress != 0 && + der.inh32.CLR_DIRECTORY.Size != 0) { + + DETOUR_TRACE(("CLR32.VirtAddr=%08lx, CLR.Size=%lu\n", + der.inh32.CLR_DIRECTORY.VirtualAddress, + der.inh32.CLR_DIRECTORY.Size)); + + der.pclr = ((PBYTE)hModule) + der.inh32.CLR_DIRECTORY.VirtualAddress; + } + } + else if (der.inh.OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC) { + if (der.inh64.CLR_DIRECTORY.VirtualAddress != 0 && + der.inh64.CLR_DIRECTORY.Size != 0) { + + DETOUR_TRACE(("CLR64.VirtAddr=%08lx, CLR.Size=%lu\n", + der.inh64.CLR_DIRECTORY.VirtualAddress, + der.inh64.CLR_DIRECTORY.Size)); + + der.pclr = ((PBYTE)hModule) + der.inh64.CLR_DIRECTORY.VirtualAddress; + } + } + + if (der.pclr != 0) { + der.cbclr = sizeof(der.clr); + if (!ReadProcessMemory(hProcess, der.pclr, &der.clr, der.cbclr, NULL)) { + DETOUR_TRACE(("ReadProcessMemory(clr@%p..%p) failed: %lu\n", + der.pclr, der.pclr + der.cbclr, GetLastError())); + return FALSE; + } + DETOUR_TRACE(("CLR: %p..%p\n", der.pclr, der.pclr + der.cbclr)); + } + + return TRUE; +} + +////////////////////////////////////////////////////////////////////////////// +// +#if DETOURS_32BIT +#define DWORD_XX DWORD32 +#define IMAGE_NT_HEADERS_XX IMAGE_NT_HEADERS32 +#define IMAGE_NT_OPTIONAL_HDR_MAGIC_XX IMAGE_NT_OPTIONAL_HDR32_MAGIC +#define IMAGE_ORDINAL_FLAG_XX IMAGE_ORDINAL_FLAG32 +#define IMAGE_THUNK_DATAXX IMAGE_THUNK_DATA32 +#define UPDATE_IMPORTS_XX UpdateImports32 +#define DETOURS_BITS_XX 32 +#include "uimports.cpp" +#undef DETOUR_EXE_RESTORE_FIELD_XX +#undef DWORD_XX +#undef IMAGE_NT_HEADERS_XX +#undef IMAGE_NT_OPTIONAL_HDR_MAGIC_XX +#undef IMAGE_ORDINAL_FLAG_XX +#undef UPDATE_IMPORTS_XX +#endif // DETOURS_32BIT + +#if DETOURS_64BIT +#define DWORD_XX DWORD64 +#define IMAGE_NT_HEADERS_XX IMAGE_NT_HEADERS64 +#define IMAGE_NT_OPTIONAL_HDR_MAGIC_XX IMAGE_NT_OPTIONAL_HDR64_MAGIC +#define IMAGE_ORDINAL_FLAG_XX IMAGE_ORDINAL_FLAG64 +#define IMAGE_THUNK_DATAXX IMAGE_THUNK_DATA64 +#define UPDATE_IMPORTS_XX UpdateImports64 +#define DETOURS_BITS_XX 64 +#include "uimports.cpp" +#undef DETOUR_EXE_RESTORE_FIELD_XX +#undef DWORD_XX +#undef IMAGE_NT_HEADERS_XX +#undef IMAGE_NT_OPTIONAL_HDR_MAGIC_XX +#undef IMAGE_ORDINAL_FLAG_XX +#undef UPDATE_IMPORTS_XX +#endif // DETOURS_64BIT + +////////////////////////////////////////////////////////////////////////////// +// +#if DETOURS_64BIT + +C_ASSERT(sizeof(IMAGE_NT_HEADERS64) == sizeof(IMAGE_NT_HEADERS32) + 16); + +static BOOL UpdateFrom32To64(HANDLE hProcess, HMODULE hModule, WORD machine, + DETOUR_EXE_RESTORE& der) +{ + IMAGE_DOS_HEADER idh; + IMAGE_NT_HEADERS32 inh32; + IMAGE_NT_HEADERS64 inh64; + IMAGE_SECTION_HEADER sects[32]; + PBYTE pbModule = (PBYTE)hModule; + DWORD n; + + ZeroMemory(&inh32, sizeof(inh32)); + ZeroMemory(&inh64, sizeof(inh64)); + ZeroMemory(sects, sizeof(sects)); + + DETOUR_TRACE(("UpdateFrom32To64(%04x)\n", machine)); + //////////////////////////////////////////////////////// Read old headers. + // + if (!ReadProcessMemory(hProcess, pbModule, &idh, sizeof(idh), NULL)) { + DETOUR_TRACE(("ReadProcessMemory(idh@%p..%p) failed: %lu\n", + pbModule, pbModule + sizeof(idh), GetLastError())); + return FALSE; + } + DETOUR_TRACE(("ReadProcessMemory(idh@%p..%p)\n", + pbModule, pbModule + sizeof(idh))); + + PBYTE pnh = pbModule + idh.e_lfanew; + if (!ReadProcessMemory(hProcess, pnh, &inh32, sizeof(inh32), NULL)) { + DETOUR_TRACE(("ReadProcessMemory(inh@%p..%p) failed: %lu\n", + pnh, pnh + sizeof(inh32), GetLastError())); + return FALSE; + } + DETOUR_TRACE(("ReadProcessMemory(inh@%p..%p)\n", pnh, pnh + sizeof(inh32))); + + if (inh32.FileHeader.NumberOfSections > (sizeof(sects)/sizeof(sects[0]))) { + return FALSE; + } + + PBYTE psects = pnh + + FIELD_OFFSET(IMAGE_NT_HEADERS, OptionalHeader) + + inh32.FileHeader.SizeOfOptionalHeader; + ULONG cb = inh32.FileHeader.NumberOfSections * sizeof(IMAGE_SECTION_HEADER); + if (!ReadProcessMemory(hProcess, psects, §s, cb, NULL)) { + DETOUR_TRACE(("ReadProcessMemory(ish@%p..%p) failed: %lu\n", + psects, psects + cb, GetLastError())); + return FALSE; + } + DETOUR_TRACE(("ReadProcessMemory(ish@%p..%p)\n", psects, psects + cb)); + + ////////////////////////////////////////////////////////// Convert header. + // + inh64.Signature = inh32.Signature; + inh64.FileHeader = inh32.FileHeader; + inh64.FileHeader.Machine = machine; + inh64.FileHeader.SizeOfOptionalHeader = sizeof(IMAGE_OPTIONAL_HEADER64); + + inh64.OptionalHeader.Magic = IMAGE_NT_OPTIONAL_HDR64_MAGIC; + inh64.OptionalHeader.MajorLinkerVersion = inh32.OptionalHeader.MajorLinkerVersion; + inh64.OptionalHeader.MinorLinkerVersion = inh32.OptionalHeader.MinorLinkerVersion; + inh64.OptionalHeader.SizeOfCode = inh32.OptionalHeader.SizeOfCode; + inh64.OptionalHeader.SizeOfInitializedData = inh32.OptionalHeader.SizeOfInitializedData; + inh64.OptionalHeader.SizeOfUninitializedData = inh32.OptionalHeader.SizeOfUninitializedData; + inh64.OptionalHeader.AddressOfEntryPoint = inh32.OptionalHeader.AddressOfEntryPoint; + inh64.OptionalHeader.BaseOfCode = inh32.OptionalHeader.BaseOfCode; + inh64.OptionalHeader.ImageBase = inh32.OptionalHeader.ImageBase; + inh64.OptionalHeader.SectionAlignment = inh32.OptionalHeader.SectionAlignment; + inh64.OptionalHeader.FileAlignment = inh32.OptionalHeader.FileAlignment; + inh64.OptionalHeader.MajorOperatingSystemVersion + = inh32.OptionalHeader.MajorOperatingSystemVersion; + inh64.OptionalHeader.MinorOperatingSystemVersion + = inh32.OptionalHeader.MinorOperatingSystemVersion; + inh64.OptionalHeader.MajorImageVersion = inh32.OptionalHeader.MajorImageVersion; + inh64.OptionalHeader.MinorImageVersion = inh32.OptionalHeader.MinorImageVersion; + inh64.OptionalHeader.MajorSubsystemVersion = inh32.OptionalHeader.MajorSubsystemVersion; + inh64.OptionalHeader.MinorSubsystemVersion = inh32.OptionalHeader.MinorSubsystemVersion; + inh64.OptionalHeader.Win32VersionValue = inh32.OptionalHeader.Win32VersionValue; + inh64.OptionalHeader.SizeOfImage = inh32.OptionalHeader.SizeOfImage; + inh64.OptionalHeader.SizeOfHeaders = inh32.OptionalHeader.SizeOfHeaders; + inh64.OptionalHeader.CheckSum = inh32.OptionalHeader.CheckSum; + inh64.OptionalHeader.Subsystem = inh32.OptionalHeader.Subsystem; + inh64.OptionalHeader.DllCharacteristics = inh32.OptionalHeader.DllCharacteristics; + inh64.OptionalHeader.SizeOfStackReserve = inh32.OptionalHeader.SizeOfStackReserve; + inh64.OptionalHeader.SizeOfStackCommit = inh32.OptionalHeader.SizeOfStackCommit; + inh64.OptionalHeader.SizeOfHeapReserve = inh32.OptionalHeader.SizeOfHeapReserve; + inh64.OptionalHeader.SizeOfHeapCommit = inh32.OptionalHeader.SizeOfHeapCommit; + inh64.OptionalHeader.LoaderFlags = inh32.OptionalHeader.LoaderFlags; + inh64.OptionalHeader.NumberOfRvaAndSizes = inh32.OptionalHeader.NumberOfRvaAndSizes; + for (n = 0; n < IMAGE_NUMBEROF_DIRECTORY_ENTRIES; n++) { + inh64.OptionalHeader.DataDirectory[n] = inh32.OptionalHeader.DataDirectory[n]; + } + + /////////////////////////////////////////////////////// Write new headers. + // + DWORD dwProtect = 0; + if (!DetourVirtualProtectSameExecuteEx(hProcess, pbModule, inh64.OptionalHeader.SizeOfHeaders, + PAGE_EXECUTE_READWRITE, &dwProtect)) { + return FALSE; + } + + if (!WriteProcessMemory(hProcess, pnh, &inh64, sizeof(inh64), NULL)) { + DETOUR_TRACE(("WriteProcessMemory(inh@%p..%p) failed: %lu\n", + pnh, pnh + sizeof(inh64), GetLastError())); + return FALSE; + } + DETOUR_TRACE(("WriteProcessMemory(inh@%p..%p)\n", pnh, pnh + sizeof(inh64))); + + psects = pnh + + FIELD_OFFSET(IMAGE_NT_HEADERS, OptionalHeader) + + inh64.FileHeader.SizeOfOptionalHeader; + cb = inh64.FileHeader.NumberOfSections * sizeof(IMAGE_SECTION_HEADER); + if (!WriteProcessMemory(hProcess, psects, §s, cb, NULL)) { + DETOUR_TRACE(("WriteProcessMemory(ish@%p..%p) failed: %lu\n", + psects, psects + cb, GetLastError())); + return FALSE; + } + DETOUR_TRACE(("WriteProcessMemory(ish@%p..%p)\n", psects, psects + cb)); + + // Record the updated headers. + if (!RecordExeRestore(hProcess, hModule, der)) { + return FALSE; + } + + // Remove the import table. + if (der.pclr != NULL && (der.clr.Flags & COMIMAGE_FLAGS_ILONLY)) { + inh64.IMPORT_DIRECTORY.VirtualAddress = 0; + inh64.IMPORT_DIRECTORY.Size = 0; + + if (!WriteProcessMemory(hProcess, pnh, &inh64, sizeof(inh64), NULL)) { + DETOUR_TRACE(("WriteProcessMemory(inh@%p..%p) failed: %lu\n", + pnh, pnh + sizeof(inh64), GetLastError())); + return FALSE; + } + } + + DWORD dwOld = 0; + if (!VirtualProtectEx(hProcess, pbModule, inh64.OptionalHeader.SizeOfHeaders, + dwProtect, &dwOld)) { + return FALSE; + } + + return TRUE; +} +#endif // DETOURS_64BIT + +typedef BOOL(WINAPI *LPFN_ISWOW64PROCESS)(HANDLE, PBOOL); + +static BOOL IsWow64ProcessHelper(HANDLE hProcess, + PBOOL Wow64Process) +{ +#ifdef _X86_ + if (Wow64Process == NULL) { + return FALSE; + } + + // IsWow64Process is not available on all supported versions of Windows. + // + HMODULE hKernel32 = LoadLibraryW(L"KERNEL32.DLL"); + if (hKernel32 == NULL) { + DETOUR_TRACE(("LoadLibraryW failed: %lu\n", GetLastError())); + return FALSE; + } + + LPFN_ISWOW64PROCESS pfnIsWow64Process = (LPFN_ISWOW64PROCESS)GetProcAddress( + hKernel32, "IsWow64Process"); + + if (pfnIsWow64Process == NULL) { + DETOUR_TRACE(("GetProcAddress failed: %lu\n", GetLastError())); + return FALSE; + } + return pfnIsWow64Process(hProcess, Wow64Process); +#else + return IsWow64Process(hProcess, Wow64Process); +#endif +} + +////////////////////////////////////////////////////////////////////////////// +// +BOOL WINAPI DetourUpdateProcessWithDll(_In_ HANDLE hProcess, + _In_reads_(nDlls) LPCSTR *rlpDlls, + _In_ DWORD nDlls) +{ + // Find the next memory region that contains a mapped PE image. + // + BOOL bIs32BitProcess; + BOOL bIs64BitOS = FALSE; + HMODULE hModule = NULL; + HMODULE hLast = NULL; + + DETOUR_TRACE(("DetourUpdateProcessWithDll(%p,dlls=%lu)\n", hProcess, nDlls)); + + for (;;) { + IMAGE_NT_HEADERS32 inh; + + if ((hLast = EnumerateModulesInProcess(hProcess, hLast, &inh, NULL)) == NULL) { + break; + } + + DETOUR_TRACE(("%p machine=%04x magic=%04x\n", + hLast, inh.FileHeader.Machine, inh.OptionalHeader.Magic)); + + if ((inh.FileHeader.Characteristics & IMAGE_FILE_DLL) == 0) { + hModule = hLast; + DETOUR_TRACE(("%p Found EXE\n", hLast)); + } + } + + if (hModule == NULL) { + SetLastError(ERROR_INVALID_OPERATION); + return FALSE; + } + + // Determine if the target process is 32bit or 64bit. This is a two-stop process: + // + // 1. First, determine if we're running on a 64bit operating system. + // - If we're running 64bit code (i.e. _WIN64 is defined), this is trivially true. + // - If we're running 32bit code (i.e. _WIN64 is not defined), test if + // we're running under Wow64. If so, it implies that the operating system + // is 64bit. + // +#ifdef _WIN64 + bIs64BitOS = TRUE; +#else + if (!IsWow64ProcessHelper(GetCurrentProcess(), &bIs64BitOS)) { + return FALSE; + } +#endif + + // 2. With the operating system bitness known, we can now consider the target process: + // - If we're running on a 64bit OS, the target process is 32bit in case + // it is running under Wow64. Otherwise, it's 64bit, running natively + // (without Wow64). + // - If we're running on a 32bit OS, the target process must be 32bit, too. + // + if (bIs64BitOS) { + if (!IsWow64ProcessHelper(hProcess, &bIs32BitProcess)) { + return FALSE; + } + } else { + bIs32BitProcess = TRUE; + } + + DETOUR_TRACE((" 32BitProcess=%d\n", bIs32BitProcess)); + + return DetourUpdateProcessWithDllEx(hProcess, + hModule, + bIs32BitProcess, + rlpDlls, + nDlls); +} + +BOOL WINAPI DetourUpdateProcessWithDllEx(_In_ HANDLE hProcess, + _In_ HMODULE hModule, + _In_ BOOL bIs32BitProcess, + _In_reads_(nDlls) LPCSTR *rlpDlls, + _In_ DWORD nDlls) +{ + // Find the next memory region that contains a mapped PE image. + // + BOOL bIs32BitExe = FALSE; + + DETOUR_TRACE(("DetourUpdateProcessWithDllEx(%p,%p,dlls=%lu)\n", hProcess, hModule, nDlls)); + + IMAGE_NT_HEADERS32 inh; + + if (hModule == NULL || !LoadNtHeaderFromProcess(hProcess, hModule, &inh)) { + SetLastError(ERROR_INVALID_OPERATION); + return FALSE; + } + + if (inh.OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC + && inh.FileHeader.Machine != 0) { + + bIs32BitExe = TRUE; + } + + DETOUR_TRACE((" 32BitExe=%d\n", bIs32BitExe)); + + if (hModule == NULL) { + SetLastError(ERROR_INVALID_OPERATION); + return FALSE; + } + + // Save the various headers for DetourRestoreAfterWith. + // + DETOUR_EXE_RESTORE der; + + if (!RecordExeRestore(hProcess, hModule, der)) { + return FALSE; + } + +#if defined(DETOURS_64BIT) + // Try to convert a neutral 32-bit managed binary to a 64-bit managed binary. + if (bIs32BitExe && !bIs32BitProcess) { + if (!der.pclr // Native binary + || (der.clr.Flags & COMIMAGE_FLAGS_ILONLY) == 0 // Or mixed-mode MSIL + || (der.clr.Flags & COMIMAGE_FLAGS_32BITREQUIRED) != 0) { // Or 32BIT Required MSIL + + SetLastError(ERROR_INVALID_HANDLE); + return FALSE; + } + + if (!UpdateFrom32To64(hProcess, hModule, +#if defined(DETOURS_X64) + IMAGE_FILE_MACHINE_AMD64, +#elif defined(DETOURS_IA64) + IMAGE_FILE_MACHINE_IA64, +#elif defined(DETOURS_ARM64) + IMAGE_FILE_MACHINE_ARM64, +#else +#error Must define one of DETOURS_X64 or DETOURS_IA64 or DETOURS_ARM64 on 64-bit. +#endif + der)) { + return FALSE; + } + bIs32BitExe = FALSE; + } +#endif // DETOURS_64BIT + + // Now decide if we can insert the detour. + +#if defined(DETOURS_32BIT) + if (bIs32BitProcess) { + // 32-bit native or 32-bit managed process on any platform. + if (!UpdateImports32(hProcess, hModule, rlpDlls, nDlls)) { + return FALSE; + } + } + else { + // 64-bit native or 64-bit managed process. + // + // Can't detour a 64-bit process with 32-bit code. + // Note: This happens for 32-bit PE binaries containing only + // manage code that have been marked as 64-bit ready. + // + SetLastError(ERROR_INVALID_HANDLE); + return FALSE; + } +#elif defined(DETOURS_64BIT) + if (bIs32BitProcess || bIs32BitExe) { + // Can't detour a 32-bit process with 64-bit code. + SetLastError(ERROR_INVALID_HANDLE); + return FALSE; + } + else { + // 64-bit native or 64-bit managed process on any platform. + if (!UpdateImports64(hProcess, hModule, rlpDlls, nDlls)) { + return FALSE; + } + } +#else +#pragma Must define one of DETOURS_32BIT or DETOURS_64BIT. +#endif // DETOURS_64BIT + + /////////////////////////////////////////////////// Update the CLR header. + // + if (der.pclr != NULL) { + DETOUR_CLR_HEADER clr; + CopyMemory(&clr, &der.clr, sizeof(clr)); + clr.Flags &= ~COMIMAGE_FLAGS_ILONLY; // Clear the IL_ONLY flag. + + DWORD dwProtect; + if (!DetourVirtualProtectSameExecuteEx(hProcess, der.pclr, sizeof(clr), PAGE_READWRITE, &dwProtect)) { + DETOUR_TRACE(("VirtualProtectEx(clr) write failed: %lu\n", GetLastError())); + return FALSE; + } + + if (!WriteProcessMemory(hProcess, der.pclr, &clr, sizeof(clr), NULL)) { + DETOUR_TRACE(("WriteProcessMemory(clr) failed: %lu\n", GetLastError())); + return FALSE; + } + + if (!VirtualProtectEx(hProcess, der.pclr, sizeof(clr), dwProtect, &dwProtect)) { + DETOUR_TRACE(("VirtualProtectEx(clr) restore failed: %lu\n", GetLastError())); + return FALSE; + } + DETOUR_TRACE(("CLR: %p..%p\n", der.pclr, der.pclr + der.cbclr)); + +#if DETOURS_64BIT + if (der.clr.Flags & COMIMAGE_FLAGS_32BITREQUIRED) { // Is the 32BIT Required Flag set? + // X64 never gets here because the process appears as a WOW64 process. + // However, on IA64, it doesn't appear to be a WOW process. + DETOUR_TRACE(("CLR Requires 32-bit\n")); + SetLastError(ERROR_INVALID_HANDLE); + return FALSE; + } +#endif // DETOURS_64BIT + } + + //////////////////////////////// Save the undo data to the target process. + // + if (!DetourCopyPayloadToProcess(hProcess, DETOUR_EXE_RESTORE_GUID, &der, sizeof(der))) { + DETOUR_TRACE(("DetourCopyPayloadToProcess failed: %lu\n", GetLastError())); + return FALSE; + } + return TRUE; +} + +////////////////////////////////////////////////////////////////////////////// +// +BOOL WINAPI DetourCreateProcessWithDllA(_In_opt_ LPCSTR lpApplicationName, + _Inout_opt_ LPSTR lpCommandLine, + _In_opt_ LPSECURITY_ATTRIBUTES lpProcessAttributes, + _In_opt_ LPSECURITY_ATTRIBUTES lpThreadAttributes, + _In_ BOOL bInheritHandles, + _In_ DWORD dwCreationFlags, + _In_opt_ LPVOID lpEnvironment, + _In_opt_ LPCSTR lpCurrentDirectory, + _In_ LPSTARTUPINFOA lpStartupInfo, + _Out_ LPPROCESS_INFORMATION lpProcessInformation, + _In_ LPCSTR lpDllName, + _In_opt_ PDETOUR_CREATE_PROCESS_ROUTINEA pfCreateProcessA) +{ + DWORD dwMyCreationFlags = (dwCreationFlags | CREATE_SUSPENDED); + PROCESS_INFORMATION pi; + BOOL fResult = FALSE; + + if (pfCreateProcessA == NULL) { + pfCreateProcessA = CreateProcessA; + } + + fResult = pfCreateProcessA(lpApplicationName, + lpCommandLine, + lpProcessAttributes, + lpThreadAttributes, + bInheritHandles, + dwMyCreationFlags, + lpEnvironment, + lpCurrentDirectory, + lpStartupInfo, + &pi); + + if (lpProcessInformation != NULL) { + CopyMemory(lpProcessInformation, &pi, sizeof(pi)); + } + + if (!fResult) { + return FALSE; + } + + LPCSTR rlpDlls[2]; + DWORD nDlls = 0; + if (lpDllName != NULL) { + rlpDlls[nDlls++] = lpDllName; + } + + if (!DetourUpdateProcessWithDll(pi.hProcess, rlpDlls, nDlls)) { + TerminateProcess(pi.hProcess, ~0u); + return FALSE; + } + + if (!(dwCreationFlags & CREATE_SUSPENDED)) { + ResumeThread(pi.hThread); + } + return TRUE; +} + + +BOOL WINAPI DetourCreateProcessWithDllW(_In_opt_ LPCWSTR lpApplicationName, + _Inout_opt_ LPWSTR lpCommandLine, + _In_opt_ LPSECURITY_ATTRIBUTES lpProcessAttributes, + _In_opt_ LPSECURITY_ATTRIBUTES lpThreadAttributes, + _In_ BOOL bInheritHandles, + _In_ DWORD dwCreationFlags, + _In_opt_ LPVOID lpEnvironment, + _In_opt_ LPCWSTR lpCurrentDirectory, + _In_ LPSTARTUPINFOW lpStartupInfo, + _Out_ LPPROCESS_INFORMATION lpProcessInformation, + _In_ LPCSTR lpDllName, + _In_opt_ PDETOUR_CREATE_PROCESS_ROUTINEW pfCreateProcessW) +{ + DWORD dwMyCreationFlags = (dwCreationFlags | CREATE_SUSPENDED); + PROCESS_INFORMATION pi; + + if (pfCreateProcessW == NULL) { + pfCreateProcessW = CreateProcessW; + } + + BOOL fResult = pfCreateProcessW(lpApplicationName, + lpCommandLine, + lpProcessAttributes, + lpThreadAttributes, + bInheritHandles, + dwMyCreationFlags, + lpEnvironment, + lpCurrentDirectory, + lpStartupInfo, + &pi); + + if (lpProcessInformation) { + CopyMemory(lpProcessInformation, &pi, sizeof(pi)); + } + + if (!fResult) { + return FALSE; + } + + LPCSTR rlpDlls[2]; + DWORD nDlls = 0; + if (lpDllName != NULL) { + rlpDlls[nDlls++] = lpDllName; + } + + if (!DetourUpdateProcessWithDll(pi.hProcess, rlpDlls, nDlls)) { + TerminateProcess(pi.hProcess, ~0u); + return FALSE; + } + + if (!(dwCreationFlags & CREATE_SUSPENDED)) { + ResumeThread(pi.hThread); + } + return TRUE; +} + +BOOL WINAPI DetourCopyPayloadToProcess(_In_ HANDLE hProcess, + _In_ REFGUID rguid, + _In_reads_bytes_(cbData) LPCVOID pvData, + _In_ DWORD cbData) +{ + return DetourCopyPayloadToProcessEx(hProcess, rguid, pvData, cbData) != NULL; +} + +_Success_(return != NULL) +PVOID WINAPI DetourCopyPayloadToProcessEx(_In_ HANDLE hProcess, + _In_ REFGUID rguid, + _In_reads_bytes_(cbData) LPCVOID pvData, + _In_ DWORD cbData) +{ + if (hProcess == NULL) { + SetLastError(ERROR_INVALID_HANDLE); + return NULL; + } + + DWORD cbTotal = (sizeof(IMAGE_DOS_HEADER) + + sizeof(IMAGE_NT_HEADERS) + + sizeof(IMAGE_SECTION_HEADER) + + sizeof(DETOUR_SECTION_HEADER) + + sizeof(DETOUR_SECTION_RECORD) + + cbData); + + PBYTE pbBase = (PBYTE)VirtualAllocEx(hProcess, NULL, cbTotal, + MEM_COMMIT, PAGE_READWRITE); + if (pbBase == NULL) { + DETOUR_TRACE(("VirtualAllocEx(%lu) failed: %lu\n", cbTotal, GetLastError())); + return NULL; + } + + // As you can see in the following code, + // the memory layout of the payload range "[pbBase, pbBase+cbTotal]" is a PE executable file, + // so DetourFreePayload can use "DetourGetContainingModule(Payload pointer)" to get the above "pbBase" pointer, + // pbBase: the memory block allocated by VirtualAllocEx will be released in DetourFreePayload by VirtualFree. + + PBYTE pbTarget = pbBase; + IMAGE_DOS_HEADER idh; + IMAGE_NT_HEADERS inh; + IMAGE_SECTION_HEADER ish; + DETOUR_SECTION_HEADER dsh; + DETOUR_SECTION_RECORD dsr; + SIZE_T cbWrote = 0; + + ZeroMemory(&idh, sizeof(idh)); + idh.e_magic = IMAGE_DOS_SIGNATURE; + idh.e_lfanew = sizeof(idh); + if (!WriteProcessMemory(hProcess, pbTarget, &idh, sizeof(idh), &cbWrote) || + cbWrote != sizeof(idh)) { + DETOUR_TRACE(("WriteProcessMemory(idh) failed: %lu\n", GetLastError())); + return NULL; + } + pbTarget += sizeof(idh); + + ZeroMemory(&inh, sizeof(inh)); + inh.Signature = IMAGE_NT_SIGNATURE; + inh.FileHeader.SizeOfOptionalHeader = sizeof(inh.OptionalHeader); + inh.FileHeader.Characteristics = IMAGE_FILE_DLL; + inh.FileHeader.NumberOfSections = 1; + inh.OptionalHeader.Magic = IMAGE_NT_OPTIONAL_HDR_MAGIC; + if (!WriteProcessMemory(hProcess, pbTarget, &inh, sizeof(inh), &cbWrote) || + cbWrote != sizeof(inh)) { + return NULL; + } + pbTarget += sizeof(inh); + + ZeroMemory(&ish, sizeof(ish)); + memcpy(ish.Name, ".detour", sizeof(ish.Name)); + ish.VirtualAddress = (DWORD)((pbTarget + sizeof(ish)) - pbBase); + ish.SizeOfRawData = (sizeof(DETOUR_SECTION_HEADER) + + sizeof(DETOUR_SECTION_RECORD) + + cbData); + if (!WriteProcessMemory(hProcess, pbTarget, &ish, sizeof(ish), &cbWrote) || + cbWrote != sizeof(ish)) { + return NULL; + } + pbTarget += sizeof(ish); + + ZeroMemory(&dsh, sizeof(dsh)); + dsh.cbHeaderSize = sizeof(dsh); + dsh.nSignature = DETOUR_SECTION_HEADER_SIGNATURE; + dsh.nDataOffset = sizeof(DETOUR_SECTION_HEADER); + dsh.cbDataSize = (sizeof(DETOUR_SECTION_HEADER) + + sizeof(DETOUR_SECTION_RECORD) + + cbData); + if (!WriteProcessMemory(hProcess, pbTarget, &dsh, sizeof(dsh), &cbWrote) || + cbWrote != sizeof(dsh)) { + return NULL; + } + pbTarget += sizeof(dsh); + + ZeroMemory(&dsr, sizeof(dsr)); + dsr.cbBytes = cbData + sizeof(DETOUR_SECTION_RECORD); + dsr.nReserved = 0; + dsr.guid = rguid; + if (!WriteProcessMemory(hProcess, pbTarget, &dsr, sizeof(dsr), &cbWrote) || + cbWrote != sizeof(dsr)) { + return NULL; + } + pbTarget += sizeof(dsr); + + if (!WriteProcessMemory(hProcess, pbTarget, pvData, cbData, &cbWrote) || + cbWrote != cbData) { + return NULL; + } + + DETOUR_TRACE(("Copied %lu byte payload into target process at %p\n", + cbData, pbTarget)); + + SetLastError(NO_ERROR); + return pbTarget; +} + +static BOOL s_fSearchedForHelper = FALSE; +static PDETOUR_EXE_HELPER s_pHelper = NULL; + +VOID CALLBACK DetourFinishHelperProcess(_In_ HWND, + _In_ HINSTANCE, + _In_ LPSTR, + _In_ INT) +{ + LPCSTR * rlpDlls = NULL; + DWORD Result = 9900; + DWORD cOffset = 0; + DWORD cSize = 0; + HANDLE hProcess = NULL; + + if (s_pHelper == NULL) { + DETOUR_TRACE(("DetourFinishHelperProcess called with s_pHelper = NULL.\n")); + Result = 9905; + goto Cleanup; + } + + hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, s_pHelper->pid); + if (hProcess == NULL) { + DETOUR_TRACE(("OpenProcess(pid=%lu) failed: %lu\n", + s_pHelper->pid, GetLastError())); + Result = 9901; + goto Cleanup; + } + + rlpDlls = new NOTHROW LPCSTR [s_pHelper->nDlls]; + cSize = s_pHelper->cb - sizeof(DETOUR_EXE_HELPER); + for (DWORD n = 0; n < s_pHelper->nDlls; n++) { + size_t cchDest = 0; + HRESULT hr = StringCchLengthA(&s_pHelper->rDlls[cOffset], cSize - cOffset, &cchDest); + if (!SUCCEEDED(hr)) { + Result = 9902; + goto Cleanup; + } + + rlpDlls[n] = &s_pHelper->rDlls[cOffset]; + cOffset += (DWORD)cchDest + 1; + } + + if (!DetourUpdateProcessWithDll(hProcess, rlpDlls, s_pHelper->nDlls)) { + DETOUR_TRACE(("DetourUpdateProcessWithDll(pid=%lu) failed: %lu\n", + s_pHelper->pid, GetLastError())); + Result = 9903; + goto Cleanup; + } + Result = 0; + + Cleanup: + if (rlpDlls != NULL) { + delete[] rlpDlls; + rlpDlls = NULL; + } + + // Note: s_pHelper is allocated as part of injecting the payload in DetourCopyPayloadToProcess(..), + // it's a fake section and not data allocated by the system PE loader. + + // Delete the payload after execution to release the memory occupied by it + if (s_pHelper != NULL) { + DetourFreePayload(s_pHelper); + s_pHelper = NULL; + } + + ExitProcess(Result); +} + +BOOL WINAPI DetourIsHelperProcess(VOID) +{ + PVOID pvData; + DWORD cbData; + + if (s_fSearchedForHelper) { + return (s_pHelper != NULL); + } + + s_fSearchedForHelper = TRUE; + pvData = DetourFindPayloadEx(DETOUR_EXE_HELPER_GUID, &cbData); + + if (pvData == NULL || cbData < sizeof(DETOUR_EXE_HELPER)) { + return FALSE; + } + + s_pHelper = (PDETOUR_EXE_HELPER)pvData; + if (s_pHelper->cb < sizeof(*s_pHelper)) { + s_pHelper = NULL; + return FALSE; + } + + return TRUE; +} + +static +BOOL WINAPI AllocExeHelper(_Out_ PDETOUR_EXE_HELPER *pHelper, + _In_ DWORD dwTargetPid, + _In_ DWORD nDlls, + _In_reads_(nDlls) LPCSTR *rlpDlls) +{ + PDETOUR_EXE_HELPER Helper = NULL; + BOOL Result = FALSE; + _Field_range_(0, cSize - 4) DWORD cOffset = 0; + DWORD cSize = 4; + + if (pHelper == NULL) { + goto Cleanup; + } + *pHelper = NULL; + + if (nDlls < 1 || nDlls > 4096) { + SetLastError(ERROR_INVALID_PARAMETER); + goto Cleanup; + } + + for (DWORD n = 0; n < nDlls; n++) { + HRESULT hr; + size_t cchDest = 0; + + hr = StringCchLengthA(rlpDlls[n], 4096, &cchDest); + if (!SUCCEEDED(hr)) { + goto Cleanup; + } + + cSize += (DWORD)cchDest + 1; + } + + Helper = (PDETOUR_EXE_HELPER) new NOTHROW BYTE[sizeof(DETOUR_EXE_HELPER) + cSize]; + if (Helper == NULL) { + goto Cleanup; + } + + Helper->cb = sizeof(DETOUR_EXE_HELPER) + cSize; + Helper->pid = dwTargetPid; + Helper->nDlls = nDlls; + + for (DWORD n = 0; n < nDlls; n++) { + HRESULT hr; + size_t cchDest = 0; + + if (cOffset > 0x10000 || cSize > 0x10000 || cOffset + 2 >= cSize) { + goto Cleanup; + } + + if (cOffset + 2 >= cSize || cOffset + 65536 < cSize) { + goto Cleanup; + } + + _Analysis_assume_(cOffset + 1 < cSize); + _Analysis_assume_(cOffset < 0x10000); + _Analysis_assume_(cSize < 0x10000); + + PCHAR psz = &Helper->rDlls[cOffset]; + + hr = StringCchCopyA(psz, cSize - cOffset, rlpDlls[n]); + if (!SUCCEEDED(hr)) { + goto Cleanup; + } + +// REVIEW 28020 The expression '1<=_Param_(2)& &_Param_(2)<=2147483647' is not true at this call. +// REVIEW 28313 Analysis will not proceed past this point because of annotation evaluation. The annotation expression *_Param_(3)<_Param_(2)&&*_Param_(3)<=stringLength$(_Param_(1)) cannot be true under any assumptions at this point in the program. +#pragma warning(suppress:28020 28313) + hr = StringCchLengthA(psz, cSize - cOffset, &cchDest); + if (!SUCCEEDED(hr)) { + goto Cleanup; + } + + // Replace "32." with "64." or "64." with "32." + + for (DWORD c = (DWORD)cchDest + 1; c > 3; c--) { +#if DETOURS_32BIT + if (psz[c - 3] == '3' && psz[c - 2] == '2' && psz[c - 1] == '.') { + psz[c - 3] = '6'; psz[c - 2] = '4'; + break; + } +#else + if (psz[c - 3] == '6' && psz[c - 2] == '4' && psz[c - 1] == '.') { + psz[c - 3] = '3'; psz[c - 2] = '2'; + break; + } +#endif + } + + cOffset += (DWORD)cchDest + 1; + } + + *pHelper = Helper; + Helper = NULL; + Result = TRUE; + + Cleanup: + if (Helper != NULL) { + delete[] (PBYTE)Helper; + Helper = NULL; + } + return Result; +} + +static +VOID WINAPI FreeExeHelper(PDETOUR_EXE_HELPER *pHelper) +{ + if (*pHelper != NULL) { + delete[] (PBYTE)*pHelper; + *pHelper = NULL; + } +} + +BOOL WINAPI DetourProcessViaHelperA(_In_ DWORD dwTargetPid, + _In_ LPCSTR lpDllName, + _In_ PDETOUR_CREATE_PROCESS_ROUTINEA pfCreateProcessA) +{ + return DetourProcessViaHelperDllsA(dwTargetPid, 1, &lpDllName, pfCreateProcessA); +} + + +BOOL WINAPI DetourProcessViaHelperDllsA(_In_ DWORD dwTargetPid, + _In_ DWORD nDlls, + _In_reads_(nDlls) LPCSTR *rlpDlls, + _In_ PDETOUR_CREATE_PROCESS_ROUTINEA pfCreateProcessA) +{ + BOOL Result = FALSE; + PROCESS_INFORMATION pi; + STARTUPINFOA si; + CHAR szExe[MAX_PATH]; + CHAR szCommand[MAX_PATH]; + PDETOUR_EXE_HELPER helper = NULL; + HRESULT hr; + DWORD nLen = GetEnvironmentVariableA("WINDIR", szExe, ARRAYSIZE(szExe)); + + DETOUR_TRACE(("DetourProcessViaHelperDlls(pid=%lu,dlls=%lu)\n", dwTargetPid, nDlls)); + if (nDlls < 1 || nDlls > 4096) { + SetLastError(ERROR_INVALID_PARAMETER); + goto Cleanup; + } + if (!AllocExeHelper(&helper, dwTargetPid, nDlls, rlpDlls)) { + goto Cleanup; + } + + if (nLen == 0 || nLen >= ARRAYSIZE(szExe)) { + goto Cleanup; + } + +#if DETOURS_OPTION_BITS +#if DETOURS_32BIT + hr = StringCchCatA(szExe, ARRAYSIZE(szExe), "\\sysnative\\rundll32.exe"); +#else // !DETOURS_32BIT + hr = StringCchCatA(szExe, ARRAYSIZE(szExe), "\\syswow64\\rundll32.exe"); +#endif // !DETOURS_32BIT +#else // DETOURS_OPTIONS_BITS + hr = StringCchCatA(szExe, ARRAYSIZE(szExe), "\\system32\\rundll32.exe"); +#endif // DETOURS_OPTIONS_BITS + if (!SUCCEEDED(hr)) { + goto Cleanup; + } + + //for East Asia languages and so on, like Chinese, print format with "%hs" can not work fine before user call _tsetlocale(LC_ALL,_T(".ACP")); + //so we can't use "%hs" in format string, because the dll that contain this code would inject to any process, even not call _tsetlocale(LC_ALL,_T(".ACP")) before + hr = StringCchPrintfA(szCommand, ARRAYSIZE(szCommand), + "rundll32.exe \"%s\",#1", &helper->rDlls[0]); + if (!SUCCEEDED(hr)) { + goto Cleanup; + } + + ZeroMemory(&pi, sizeof(pi)); + ZeroMemory(&si, sizeof(si)); + si.cb = sizeof(si); + + DETOUR_TRACE(("DetourProcessViaHelperDlls(\"%hs\", \"%hs\")\n", szExe, szCommand)); + if (pfCreateProcessA(szExe, szCommand, NULL, NULL, FALSE, CREATE_SUSPENDED, + NULL, NULL, &si, &pi)) { + + if (!DetourCopyPayloadToProcess(pi.hProcess, + DETOUR_EXE_HELPER_GUID, + helper, helper->cb)) { + DETOUR_TRACE(("DetourCopyPayloadToProcess failed: %lu\n", GetLastError())); + TerminateProcess(pi.hProcess, ~0u); + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + goto Cleanup; + } + + ResumeThread(pi.hThread); + WaitForSingleObject(pi.hProcess, INFINITE); + + DWORD dwResult = 500; + GetExitCodeProcess(pi.hProcess, &dwResult); + + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + + if (dwResult != 0) { + DETOUR_TRACE(("Rundll32.exe failed: result=%lu\n", dwResult)); + goto Cleanup; + } + Result = TRUE; + } + else { + DETOUR_TRACE(("CreateProcess failed: %lu\n", GetLastError())); + goto Cleanup; + } + + Cleanup: + FreeExeHelper(&helper); + return Result; +} + +BOOL WINAPI DetourProcessViaHelperW(_In_ DWORD dwTargetPid, + _In_ LPCSTR lpDllName, + _In_ PDETOUR_CREATE_PROCESS_ROUTINEW pfCreateProcessW) +{ + return DetourProcessViaHelperDllsW(dwTargetPid, 1, &lpDllName, pfCreateProcessW); +} + +BOOL WINAPI DetourProcessViaHelperDllsW(_In_ DWORD dwTargetPid, + _In_ DWORD nDlls, + _In_reads_(nDlls) LPCSTR *rlpDlls, + _In_ PDETOUR_CREATE_PROCESS_ROUTINEW pfCreateProcessW) +{ + BOOL Result = FALSE; + PROCESS_INFORMATION pi; + STARTUPINFOW si; + WCHAR szExe[MAX_PATH]; + WCHAR szCommand[MAX_PATH]; + PDETOUR_EXE_HELPER helper = NULL; + HRESULT hr; + WCHAR szDllName[MAX_PATH]; + int cchWrittenWideChar; + DWORD nLen = GetEnvironmentVariableW(L"WINDIR", szExe, ARRAYSIZE(szExe)); + + DETOUR_TRACE(("DetourProcessViaHelperDlls(pid=%lu,dlls=%lu)\n", dwTargetPid, nDlls)); + if (nDlls < 1 || nDlls > 4096) { + SetLastError(ERROR_INVALID_PARAMETER); + goto Cleanup; + } + if (!AllocExeHelper(&helper, dwTargetPid, nDlls, rlpDlls)) { + goto Cleanup; + } + + if (nLen == 0 || nLen >= ARRAYSIZE(szExe)) { + goto Cleanup; + } + +#if DETOURS_OPTION_BITS +#if DETOURS_32BIT + hr = StringCchCatW(szExe, ARRAYSIZE(szExe), L"\\sysnative\\rundll32.exe"); +#else // !DETOURS_32BIT + hr = StringCchCatW(szExe, ARRAYSIZE(szExe), L"\\syswow64\\rundll32.exe"); +#endif // !DETOURS_32BIT +#else // DETOURS_OPTIONS_BITS + hr = StringCchCatW(szExe, ARRAYSIZE(szExe), L"\\system32\\rundll32.exe"); +#endif // DETOURS_OPTIONS_BITS + if (!SUCCEEDED(hr)) { + goto Cleanup; + } + + //for East Asia languages and so on, like Chinese, print format with "%hs" can not work fine before user call _tsetlocale(LC_ALL,_T(".ACP")); + //so we can't use "%hs" in format string, because the dll that contain this code would inject to any process, even not call _tsetlocale(LC_ALL,_T(".ACP")) before + + cchWrittenWideChar = MultiByteToWideChar(CP_ACP, 0, &helper->rDlls[0], -1, szDllName, ARRAYSIZE(szDllName)); + if (cchWrittenWideChar >= ARRAYSIZE(szDllName) || cchWrittenWideChar <= 0) { + goto Cleanup; + } + hr = StringCchPrintfW(szCommand, ARRAYSIZE(szCommand), + L"rundll32.exe \"%s\",#1", szDllName); + if (!SUCCEEDED(hr)) { + goto Cleanup; + } + + ZeroMemory(&pi, sizeof(pi)); + ZeroMemory(&si, sizeof(si)); + si.cb = sizeof(si); + + DETOUR_TRACE(("DetourProcessViaHelperDlls(\"%ls\", \"%ls\")\n", szExe, szCommand)); + if (pfCreateProcessW(szExe, szCommand, NULL, NULL, FALSE, CREATE_SUSPENDED, + NULL, NULL, &si, &pi)) { + + if (!DetourCopyPayloadToProcess(pi.hProcess, + DETOUR_EXE_HELPER_GUID, + helper, helper->cb)) { + DETOUR_TRACE(("DetourCopyPayloadToProcess failed: %lu\n", GetLastError())); + TerminateProcess(pi.hProcess, ~0u); + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + goto Cleanup; + } + + ResumeThread(pi.hThread); + WaitForSingleObject(pi.hProcess, INFINITE); + + DWORD dwResult = 500; + GetExitCodeProcess(pi.hProcess, &dwResult); + + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + + if (dwResult != 0) { + DETOUR_TRACE(("Rundll32.exe failed: result=%lu\n", dwResult)); + goto Cleanup; + } + Result = TRUE; + } + else { + DETOUR_TRACE(("CreateProcess failed: %lu\n", GetLastError())); + goto Cleanup; + } + + Cleanup: + FreeExeHelper(&helper); + return Result; +} + +BOOL WINAPI DetourCreateProcessWithDllExA(_In_opt_ LPCSTR lpApplicationName, + _Inout_opt_ LPSTR lpCommandLine, + _In_opt_ LPSECURITY_ATTRIBUTES lpProcessAttributes, + _In_opt_ LPSECURITY_ATTRIBUTES lpThreadAttributes, + _In_ BOOL bInheritHandles, + _In_ DWORD dwCreationFlags, + _In_opt_ LPVOID lpEnvironment, + _In_opt_ LPCSTR lpCurrentDirectory, + _In_ LPSTARTUPINFOA lpStartupInfo, + _Out_ LPPROCESS_INFORMATION lpProcessInformation, + _In_ LPCSTR lpDllName, + _In_opt_ PDETOUR_CREATE_PROCESS_ROUTINEA pfCreateProcessA) +{ + if (pfCreateProcessA == NULL) { + pfCreateProcessA = CreateProcessA; + } + + PROCESS_INFORMATION backup; + if (lpProcessInformation == NULL) { + lpProcessInformation = &backup; + ZeroMemory(&backup, sizeof(backup)); + } + + if (!pfCreateProcessA(lpApplicationName, + lpCommandLine, + lpProcessAttributes, + lpThreadAttributes, + bInheritHandles, + dwCreationFlags | CREATE_SUSPENDED, + lpEnvironment, + lpCurrentDirectory, + lpStartupInfo, + lpProcessInformation)) { + return FALSE; + } + + LPCSTR szDll = lpDllName; + + if (!DetourUpdateProcessWithDll(lpProcessInformation->hProcess, &szDll, 1) && + !DetourProcessViaHelperA(lpProcessInformation->dwProcessId, + lpDllName, + pfCreateProcessA)) { + + TerminateProcess(lpProcessInformation->hProcess, ~0u); + CloseHandle(lpProcessInformation->hProcess); + CloseHandle(lpProcessInformation->hThread); + return FALSE; + } + + if (!(dwCreationFlags & CREATE_SUSPENDED)) { + ResumeThread(lpProcessInformation->hThread); + } + + if (lpProcessInformation == &backup) { + CloseHandle(lpProcessInformation->hProcess); + CloseHandle(lpProcessInformation->hThread); + } + + return TRUE; +} + +BOOL WINAPI DetourCreateProcessWithDllExW(_In_opt_ LPCWSTR lpApplicationName, + _Inout_opt_ LPWSTR lpCommandLine, + _In_opt_ LPSECURITY_ATTRIBUTES lpProcessAttributes, + _In_opt_ LPSECURITY_ATTRIBUTES lpThreadAttributes, + _In_ BOOL bInheritHandles, + _In_ DWORD dwCreationFlags, + _In_opt_ LPVOID lpEnvironment, + _In_opt_ LPCWSTR lpCurrentDirectory, + _In_ LPSTARTUPINFOW lpStartupInfo, + _Out_ LPPROCESS_INFORMATION lpProcessInformation, + _In_ LPCSTR lpDllName, + _In_opt_ PDETOUR_CREATE_PROCESS_ROUTINEW pfCreateProcessW) +{ + if (pfCreateProcessW == NULL) { + pfCreateProcessW = CreateProcessW; + } + + PROCESS_INFORMATION backup; + if (lpProcessInformation == NULL) { + lpProcessInformation = &backup; + ZeroMemory(&backup, sizeof(backup)); + } + + if (!pfCreateProcessW(lpApplicationName, + lpCommandLine, + lpProcessAttributes, + lpThreadAttributes, + bInheritHandles, + dwCreationFlags | CREATE_SUSPENDED, + lpEnvironment, + lpCurrentDirectory, + lpStartupInfo, + lpProcessInformation)) { + return FALSE; + } + + + LPCSTR sz = lpDllName; + + if (!DetourUpdateProcessWithDll(lpProcessInformation->hProcess, &sz, 1) && + !DetourProcessViaHelperW(lpProcessInformation->dwProcessId, + lpDllName, + pfCreateProcessW)) { + + TerminateProcess(lpProcessInformation->hProcess, ~0u); + CloseHandle(lpProcessInformation->hProcess); + CloseHandle(lpProcessInformation->hThread); + return FALSE; + } + + if (!(dwCreationFlags & CREATE_SUSPENDED)) { + ResumeThread(lpProcessInformation->hThread); + } + + if (lpProcessInformation == &backup) { + CloseHandle(lpProcessInformation->hProcess); + CloseHandle(lpProcessInformation->hThread); + } + return TRUE; +} + +BOOL WINAPI DetourCreateProcessWithDllsA(_In_opt_ LPCSTR lpApplicationName, + _Inout_opt_ LPSTR lpCommandLine, + _In_opt_ LPSECURITY_ATTRIBUTES lpProcessAttributes, + _In_opt_ LPSECURITY_ATTRIBUTES lpThreadAttributes, + _In_ BOOL bInheritHandles, + _In_ DWORD dwCreationFlags, + _In_opt_ LPVOID lpEnvironment, + _In_opt_ LPCSTR lpCurrentDirectory, + _In_ LPSTARTUPINFOA lpStartupInfo, + _Out_ LPPROCESS_INFORMATION lpProcessInformation, + _In_ DWORD nDlls, + _In_reads_(nDlls) LPCSTR *rlpDlls, + _In_opt_ PDETOUR_CREATE_PROCESS_ROUTINEA pfCreateProcessA) +{ + if (pfCreateProcessA == NULL) { + pfCreateProcessA = CreateProcessA; + } + + PROCESS_INFORMATION backup; + if (lpProcessInformation == NULL) { + lpProcessInformation = &backup; + ZeroMemory(&backup, sizeof(backup)); + } + + if (!pfCreateProcessA(lpApplicationName, + lpCommandLine, + lpProcessAttributes, + lpThreadAttributes, + bInheritHandles, + dwCreationFlags | CREATE_SUSPENDED, + lpEnvironment, + lpCurrentDirectory, + lpStartupInfo, + lpProcessInformation)) { + return FALSE; + } + + if (!DetourUpdateProcessWithDll(lpProcessInformation->hProcess, rlpDlls, nDlls) && + !DetourProcessViaHelperDllsA(lpProcessInformation->dwProcessId, + nDlls, + rlpDlls, + pfCreateProcessA)) { + + TerminateProcess(lpProcessInformation->hProcess, ~0u); + CloseHandle(lpProcessInformation->hProcess); + CloseHandle(lpProcessInformation->hThread); + return FALSE; + } + + if (!(dwCreationFlags & CREATE_SUSPENDED)) { + ResumeThread(lpProcessInformation->hThread); + } + + if (lpProcessInformation == &backup) { + CloseHandle(lpProcessInformation->hProcess); + CloseHandle(lpProcessInformation->hThread); + } + + return TRUE; +} + +BOOL WINAPI DetourCreateProcessWithDllsW(_In_opt_ LPCWSTR lpApplicationName, + _Inout_opt_ LPWSTR lpCommandLine, + _In_opt_ LPSECURITY_ATTRIBUTES lpProcessAttributes, + _In_opt_ LPSECURITY_ATTRIBUTES lpThreadAttributes, + _In_ BOOL bInheritHandles, + _In_ DWORD dwCreationFlags, + _In_opt_ LPVOID lpEnvironment, + _In_opt_ LPCWSTR lpCurrentDirectory, + _In_ LPSTARTUPINFOW lpStartupInfo, + _Out_ LPPROCESS_INFORMATION lpProcessInformation, + _In_ DWORD nDlls, + _In_reads_(nDlls) LPCSTR *rlpDlls, + _In_opt_ PDETOUR_CREATE_PROCESS_ROUTINEW pfCreateProcessW) +{ + if (pfCreateProcessW == NULL) { + pfCreateProcessW = CreateProcessW; + } + + PROCESS_INFORMATION backup; + if (lpProcessInformation == NULL) { + lpProcessInformation = &backup; + ZeroMemory(&backup, sizeof(backup)); + } + + if (!pfCreateProcessW(lpApplicationName, + lpCommandLine, + lpProcessAttributes, + lpThreadAttributes, + bInheritHandles, + dwCreationFlags | CREATE_SUSPENDED, + lpEnvironment, + lpCurrentDirectory, + lpStartupInfo, + lpProcessInformation)) { + return FALSE; + } + + + if (!DetourUpdateProcessWithDll(lpProcessInformation->hProcess, rlpDlls, nDlls) && + !DetourProcessViaHelperDllsW(lpProcessInformation->dwProcessId, + nDlls, + rlpDlls, + pfCreateProcessW)) { + + TerminateProcess(lpProcessInformation->hProcess, ~0u); + CloseHandle(lpProcessInformation->hProcess); + CloseHandle(lpProcessInformation->hThread); + return FALSE; + } + + if (!(dwCreationFlags & CREATE_SUSPENDED)) { + ResumeThread(lpProcessInformation->hThread); + } + + if (lpProcessInformation == &backup) { + CloseHandle(lpProcessInformation->hProcess); + CloseHandle(lpProcessInformation->hThread); + } + return TRUE; +} + +// +///////////////////////////////////////////////////////////////// End of File. diff --git a/protection/hooking/detours/detours.cpp b/protection/hooking/detours/detours.cpp new file mode 100644 index 0000000..9073fc5 --- /dev/null +++ b/protection/hooking/detours/detours.cpp @@ -0,0 +1,2703 @@ +////////////////////////////////////////////////////////////////////////////// +// +// Core Detours Functionality (detours.cpp of detours.lib) +// +// Microsoft Research Detours Package, Version 4.0.1 +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// + + +//#define DETOUR_DEBUG 1 +#define DETOURS_INTERNAL +#include "detours.h" + +#if DETOURS_VERSION != 0x4c0c1 // 0xMAJORcMINORcPATCH +#error detours.h version mismatch +#endif + +#define NOTHROW + +////////////////////////////////////////////////////////////////////////////// +// + +#ifdef _DEBUG +extern "C" IMAGE_DOS_HEADER __ImageBase; +int Detour_AssertExprWithFunctionName(int reportType, const char* filename, int linenumber, const char* FunctionName, const char* msg) +{ + int nRet = 0; + DWORD dwLastError = GetLastError(); + CHAR szModuleNameWithFunctionName[MAX_PATH * 2]; + szModuleNameWithFunctionName[0] = 0; + GetModuleFileNameA((HMODULE)&__ImageBase, szModuleNameWithFunctionName, ARRAYSIZE(szModuleNameWithFunctionName)); + StringCchCatNA(szModuleNameWithFunctionName, ARRAYSIZE(szModuleNameWithFunctionName), ",", ARRAYSIZE(szModuleNameWithFunctionName) - strlen(szModuleNameWithFunctionName) - 1); + StringCchCatNA(szModuleNameWithFunctionName, ARRAYSIZE(szModuleNameWithFunctionName), FunctionName, ARRAYSIZE(szModuleNameWithFunctionName) - strlen(szModuleNameWithFunctionName) - 1); + SetLastError(dwLastError); + nRet = _CrtDbgReport(reportType, filename, linenumber, szModuleNameWithFunctionName, msg); + SetLastError(dwLastError); + return nRet; +} +#endif// _DEBUG + +////////////////////////////////////////////////////////////////////////////// +// +struct _DETOUR_ALIGN +{ + BYTE obTarget : 3; + BYTE obTrampoline : 5; +}; + +C_ASSERT(sizeof(_DETOUR_ALIGN) == 1); + +////////////////////////////////////////////////////////////////////////////// +// +// Region reserved for system DLLs, which cannot be used for trampolines. +// +static PVOID s_pSystemRegionLowerBound = (PVOID)(ULONG_PTR)0x70000000; +static PVOID s_pSystemRegionUpperBound = (PVOID)(ULONG_PTR)0x80000000; + +////////////////////////////////////////////////////////////////////////////// +// +static bool detour_is_imported(PBYTE pbCode, PBYTE pbAddress) +{ + MEMORY_BASIC_INFORMATION mbi; + VirtualQuery((PVOID)pbCode, &mbi, sizeof(mbi)); + __try { + PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)mbi.AllocationBase; + if (pDosHeader->e_magic != IMAGE_DOS_SIGNATURE) { + return false; + } + + PIMAGE_NT_HEADERS pNtHeader = (PIMAGE_NT_HEADERS)((PBYTE)pDosHeader + + pDosHeader->e_lfanew); + if (pNtHeader->Signature != IMAGE_NT_SIGNATURE) { + return false; + } + + if (pbAddress >= ((PBYTE)pDosHeader + + pNtHeader->OptionalHeader + .DataDirectory[IMAGE_DIRECTORY_ENTRY_IAT].VirtualAddress) && + pbAddress < ((PBYTE)pDosHeader + + pNtHeader->OptionalHeader + .DataDirectory[IMAGE_DIRECTORY_ENTRY_IAT].VirtualAddress + + pNtHeader->OptionalHeader + .DataDirectory[IMAGE_DIRECTORY_ENTRY_IAT].Size)) { + return true; + } + } +#pragma prefast(suppress:28940, "A bad pointer means this probably isn't a PE header.") + __except (GetExceptionCode() == EXCEPTION_ACCESS_VIOLATION ? + EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH) { + return false; + } + return false; +} + +inline ULONG_PTR detour_2gb_below(ULONG_PTR address) +{ + return (address > (ULONG_PTR)0x7ff80000) ? address - 0x7ff80000 : 0x80000; +} + +inline ULONG_PTR detour_2gb_above(ULONG_PTR address) +{ +#if defined(DETOURS_64BIT) + return (address < (ULONG_PTR)0xffffffff80000000) ? address + 0x7ff80000 : (ULONG_PTR)0xfffffffffff80000; +#else + return (address < (ULONG_PTR)0x80000000) ? address + 0x7ff80000 : (ULONG_PTR)0xfff80000; +#endif +} + +///////////////////////////////////////////////////////////////////////// X86. +// +#ifdef DETOURS_X86 + +struct _DETOUR_TRAMPOLINE +{ + BYTE rbCode[30]; // target code + jmp to pbRemain + BYTE cbCode; // size of moved target code. + BYTE cbCodeBreak; // padding to make debugging easier. + BYTE rbRestore[22]; // original target code. + BYTE cbRestore; // size of original target code. + BYTE cbRestoreBreak; // padding to make debugging easier. + _DETOUR_ALIGN rAlign[8]; // instruction alignment array. + PBYTE pbRemain; // first instruction after moved code. [free list] + PBYTE pbDetour; // first instruction of detour function. +}; + +C_ASSERT(sizeof(_DETOUR_TRAMPOLINE) == 72); + +enum { + SIZE_OF_JMP = 5 +}; + +inline PBYTE detour_gen_jmp_immediate(PBYTE pbCode, PBYTE pbJmpVal) +{ + PBYTE pbJmpSrc = pbCode + 5; + *pbCode++ = 0xE9; // jmp +imm32 + *((INT32*&)pbCode)++ = (INT32)(pbJmpVal - pbJmpSrc); + return pbCode; +} + +inline PBYTE detour_gen_jmp_indirect(PBYTE pbCode, PBYTE* ppbJmpVal) +{ + *pbCode++ = 0xff; // jmp [+imm32] + *pbCode++ = 0x25; + *((INT32*&)pbCode)++ = (INT32)((PBYTE)ppbJmpVal); + return pbCode; +} + +inline PBYTE detour_gen_brk(PBYTE pbCode, PBYTE pbLimit) +{ + while (pbCode < pbLimit) { + *pbCode++ = 0xcc; // brk; + } + return pbCode; +} + +inline PBYTE detour_skip_jmp(PBYTE pbCode, PVOID* ppGlobals) +{ + PBYTE pbCodeOriginal; + + if (pbCode == NULL) { + return NULL; + } + if (ppGlobals != NULL) { + *ppGlobals = NULL; + } + + // First, skip over the import vector if there is one. + if (pbCode[0] == 0xff && pbCode[1] == 0x25) { // jmp [imm32] + // Looks like an import alias jump, then get the code it points to. + PBYTE pbTarget = *(UNALIGNED PBYTE*) & pbCode[2]; + if (detour_is_imported(pbCode, pbTarget)) { + PBYTE pbNew = *(UNALIGNED PBYTE*)pbTarget; + DETOUR_TRACE(("%p->%p: skipped over import table.\n", pbCode, pbNew)); + pbCode = pbNew; + } + } + + // Then, skip over a patch jump + if (pbCode[0] == 0xeb) { // jmp +imm8 + PBYTE pbNew = pbCode + 2 + *(CHAR*)&pbCode[1]; + DETOUR_TRACE(("%p->%p: skipped over short jump.\n", pbCode, pbNew)); + pbCode = pbNew; + pbCodeOriginal = pbCode; + + // First, skip over the import vector if there is one. + if (pbCode[0] == 0xff && pbCode[1] == 0x25) { // jmp [imm32] + // Looks like an import alias jump, then get the code it points to. + PBYTE pbTarget = *(UNALIGNED PBYTE*) & pbCode[2]; + if (detour_is_imported(pbCode, pbTarget)) { + pbNew = *(UNALIGNED PBYTE*)pbTarget; + DETOUR_TRACE(("%p->%p: skipped over import table.\n", pbCode, pbNew)); + pbCode = pbNew; + } + } + // Finally, skip over a long jump if it is the target of the patch jump. + else if (pbCode[0] == 0xe9) { // jmp +imm32 + pbNew = pbCode + 5 + *(UNALIGNED INT32*) & pbCode[1]; + DETOUR_TRACE(("%p->%p: skipped over long jump.\n", pbCode, pbNew)); + pbCode = pbNew; + + // Patches applied by the OS will jump through an HPAT page to get + // the target function in the patch image. The jump is always performed + // to the target function found at the current instruction pointer + + // PAGE_SIZE - 6 (size of jump). + // If this is an OS patch, we want to detour at the point of the target function + // padding in the base image. Ideally, we would detour at the target function, but + // since it's patched it begins with a short jump (to padding) which isn't long + // enough to hold the detour code bytes. + if (pbCode[0] == 0xff && + pbCode[1] == 0x25 && + *(UNALIGNED INT32*) & pbCode[2] == (UNALIGNED INT32)(pbCode + 0x1000)) { // jmp [eip+PAGE_SIZE-6] + + DETOUR_TRACE(("%p->%p: OS patch encountered, reset back to long jump 5 bytes prior to target function.\n", pbCode, pbCodeOriginal)); + pbCode = pbCodeOriginal; + } + + } + } + return pbCode; +} + +inline void detour_find_jmp_bounds(PBYTE pbCode, + PDETOUR_TRAMPOLINE* ppLower, + PDETOUR_TRAMPOLINE* ppUpper) +{ + // We have to place trampolines within +/- 2GB of code. + ULONG_PTR lo = detour_2gb_below((ULONG_PTR)pbCode); + ULONG_PTR hi = detour_2gb_above((ULONG_PTR)pbCode); + DETOUR_TRACE(("[%p..%p..%p]\n", (PVOID)lo, pbCode, (PVOID)hi)); + + // And, within +/- 2GB of relative jmp targets. + if (pbCode[0] == 0xe9) { // jmp +imm32 + PBYTE pbNew = pbCode + 5 + *(UNALIGNED INT32*) & pbCode[1]; + + if (pbNew < pbCode) { + hi = detour_2gb_above((ULONG_PTR)pbNew); + } + else { + lo = detour_2gb_below((ULONG_PTR)pbNew); + } + DETOUR_TRACE(("[%p..%p..%p] +imm32\n", (PVOID)lo, pbCode, (PVOID)hi)); + } + + *ppLower = (PDETOUR_TRAMPOLINE)lo; + *ppUpper = (PDETOUR_TRAMPOLINE)hi; +} + +inline BOOL detour_does_code_end_function(PBYTE pbCode) +{ + if (pbCode[0] == 0xeb || // jmp +imm8 + pbCode[0] == 0xe9 || // jmp +imm32 + pbCode[0] == 0xe0 || // jmp eax + pbCode[0] == 0xc2 || // ret +imm8 + pbCode[0] == 0xc3 || // ret + pbCode[0] == 0xcc) { // brk + return TRUE; + } + else if (pbCode[0] == 0xf3 && pbCode[1] == 0xc3) { // rep ret + return TRUE; + } + else if (pbCode[0] == 0xff && pbCode[1] == 0x25) { // jmp [+imm32] + return TRUE; + } + else if ((pbCode[0] == 0x26 || // jmp es: + pbCode[0] == 0x2e || // jmp cs: + pbCode[0] == 0x36 || // jmp ss: + pbCode[0] == 0x3e || // jmp ds: + pbCode[0] == 0x64 || // jmp fs: + pbCode[0] == 0x65) && // jmp gs: + pbCode[1] == 0xff && // jmp [+imm32] + pbCode[2] == 0x25) { + return TRUE; + } + return FALSE; +} + +inline ULONG detour_is_code_filler(PBYTE pbCode) +{ + // 1-byte through 11-byte NOPs. + if (pbCode[0] == 0x90) { + return 1; + } + if (pbCode[0] == 0x66 && pbCode[1] == 0x90) { + return 2; + } + if (pbCode[0] == 0x0F && pbCode[1] == 0x1F && pbCode[2] == 0x00) { + return 3; + } + if (pbCode[0] == 0x0F && pbCode[1] == 0x1F && pbCode[2] == 0x40 && + pbCode[3] == 0x00) { + return 4; + } + if (pbCode[0] == 0x0F && pbCode[1] == 0x1F && pbCode[2] == 0x44 && + pbCode[3] == 0x00 && pbCode[4] == 0x00) { + return 5; + } + if (pbCode[0] == 0x66 && pbCode[1] == 0x0F && pbCode[2] == 0x1F && + pbCode[3] == 0x44 && pbCode[4] == 0x00 && pbCode[5] == 0x00) { + return 6; + } + if (pbCode[0] == 0x0F && pbCode[1] == 0x1F && pbCode[2] == 0x80 && + pbCode[3] == 0x00 && pbCode[4] == 0x00 && pbCode[5] == 0x00 && + pbCode[6] == 0x00) { + return 7; + } + if (pbCode[0] == 0x0F && pbCode[1] == 0x1F && pbCode[2] == 0x84 && + pbCode[3] == 0x00 && pbCode[4] == 0x00 && pbCode[5] == 0x00 && + pbCode[6] == 0x00 && pbCode[7] == 0x00) { + return 8; + } + if (pbCode[0] == 0x66 && pbCode[1] == 0x0F && pbCode[2] == 0x1F && + pbCode[3] == 0x84 && pbCode[4] == 0x00 && pbCode[5] == 0x00 && + pbCode[6] == 0x00 && pbCode[7] == 0x00 && pbCode[8] == 0x00) { + return 9; + } + if (pbCode[0] == 0x66 && pbCode[1] == 0x66 && pbCode[2] == 0x0F && + pbCode[3] == 0x1F && pbCode[4] == 0x84 && pbCode[5] == 0x00 && + pbCode[6] == 0x00 && pbCode[7] == 0x00 && pbCode[8] == 0x00 && + pbCode[9] == 0x00) { + return 10; + } + if (pbCode[0] == 0x66 && pbCode[1] == 0x66 && pbCode[2] == 0x66 && + pbCode[3] == 0x0F && pbCode[4] == 0x1F && pbCode[5] == 0x84 && + pbCode[6] == 0x00 && pbCode[7] == 0x00 && pbCode[8] == 0x00 && + pbCode[9] == 0x00 && pbCode[10] == 0x00) { + return 11; + } + + // int 3. + if (pbCode[0] == 0xcc) { + return 1; + } + return 0; +} + +#endif // DETOURS_X86 + +///////////////////////////////////////////////////////////////////////// X64. +// +#ifdef DETOURS_X64 + +struct _DETOUR_TRAMPOLINE +{ + // An X64 instuction can be 15 bytes long. + // In practice 11 seems to be the limit. + BYTE rbCode[30]; // target code + jmp to pbRemain. + BYTE cbCode; // size of moved target code. + BYTE cbCodeBreak; // padding to make debugging easier. + BYTE rbRestore[30]; // original target code. + BYTE cbRestore; // size of original target code. + BYTE cbRestoreBreak; // padding to make debugging easier. + _DETOUR_ALIGN rAlign[8]; // instruction alignment array. + PBYTE pbRemain; // first instruction after moved code. [free list] + PBYTE pbDetour; // first instruction of detour function. + BYTE rbCodeIn[8]; // jmp [pbDetour] +}; + +C_ASSERT(sizeof(_DETOUR_TRAMPOLINE) == 96); + +enum { + SIZE_OF_JMP = 5 +}; + +inline PBYTE detour_gen_jmp_immediate(PBYTE pbCode, PBYTE pbJmpVal) +{ + PBYTE pbJmpSrc = pbCode + 5; + *pbCode++ = 0xE9; // jmp +imm32 + *((INT32*&)pbCode)++ = (INT32)(pbJmpVal - pbJmpSrc); + return pbCode; +} + +inline PBYTE detour_gen_jmp_indirect(PBYTE pbCode, PBYTE* ppbJmpVal) +{ + PBYTE pbJmpSrc = pbCode + 6; + *pbCode++ = 0xff; // jmp [+imm32] + *pbCode++ = 0x25; + *((INT32*&)pbCode)++ = (INT32)((PBYTE)ppbJmpVal - pbJmpSrc); + return pbCode; +} + +inline PBYTE detour_gen_brk(PBYTE pbCode, PBYTE pbLimit) +{ + while (pbCode < pbLimit) { + *pbCode++ = 0xcc; // brk; + } + return pbCode; +} + +inline PBYTE detour_skip_jmp(PBYTE pbCode, PVOID* ppGlobals) +{ + PBYTE pbCodeOriginal; + + if (pbCode == NULL) { + return NULL; + } + if (ppGlobals != NULL) { + *ppGlobals = NULL; + } + + // First, skip over the import vector if there is one. + if (pbCode[0] == 0xff && pbCode[1] == 0x25) { // jmp [+imm32] + // Looks like an import alias jump, then get the code it points to. + PBYTE pbTarget = pbCode + 6 + *(UNALIGNED INT32*) & pbCode[2]; + if (detour_is_imported(pbCode, pbTarget)) { + PBYTE pbNew = *(UNALIGNED PBYTE*)pbTarget; + DETOUR_TRACE(("%p->%p: skipped over import table.\n", pbCode, pbNew)); + pbCode = pbNew; + } + } + + // Then, skip over a patch jump + if (pbCode[0] == 0xeb) { // jmp +imm8 + PBYTE pbNew = pbCode + 2 + *(CHAR*)&pbCode[1]; + DETOUR_TRACE(("%p->%p: skipped over short jump.\n", pbCode, pbNew)); + pbCode = pbNew; + pbCodeOriginal = pbCode; + + // First, skip over the import vector if there is one. + if (pbCode[0] == 0xff && pbCode[1] == 0x25) { // jmp [+imm32] + // Looks like an import alias jump, then get the code it points to. + PBYTE pbTarget = pbCode + 6 + *(UNALIGNED INT32*) & pbCode[2]; + if (detour_is_imported(pbCode, pbTarget)) { + pbNew = *(UNALIGNED PBYTE*)pbTarget; + DETOUR_TRACE(("%p->%p: skipped over import table.\n", pbCode, pbNew)); + pbCode = pbNew; + } + } + // Finally, skip over a long jump if it is the target of the patch jump. + else if (pbCode[0] == 0xe9) { // jmp +imm32 + pbNew = pbCode + 5 + *(UNALIGNED INT32*) & pbCode[1]; + DETOUR_TRACE(("%p->%p: skipped over long jump.\n", pbCode, pbNew)); + pbCode = pbNew; + + // Patches applied by the OS will jump through an HPAT page to get + // the target function in the patch image. The jump is always performed + // to the target function found at the current instruction pointer + + // PAGE_SIZE - 6 (size of jump). + // If this is an OS patch, we want to detour at the point of the target function + // in the base image. Since we need 5 bytes to perform the jump, detour at the + // point of the long jump instead of the short jump at the start of the target. + if (pbCode[0] == 0xff && + pbCode[1] == 0x25 && + *(UNALIGNED INT32*) & pbCode[2] == 0xFFA) { // jmp [rip+PAGE_SIZE-6] + + DETOUR_TRACE(("%p->%p: OS patch encountered, reset back to long jump 5 bytes prior to target function.\n", pbCode, pbCodeOriginal)); + pbCode = pbCodeOriginal; + } + } + } + return pbCode; +} + +inline void detour_find_jmp_bounds(PBYTE pbCode, + PDETOUR_TRAMPOLINE* ppLower, + PDETOUR_TRAMPOLINE* ppUpper) +{ + // We have to place trampolines within +/- 2GB of code. + ULONG_PTR lo = detour_2gb_below((ULONG_PTR)pbCode); + ULONG_PTR hi = detour_2gb_above((ULONG_PTR)pbCode); + DETOUR_TRACE(("[%p..%p..%p]\n", (PVOID)lo, pbCode, (PVOID)hi)); + + // And, within +/- 2GB of relative jmp vectors. + if (pbCode[0] == 0xff && pbCode[1] == 0x25) { // jmp [+imm32] + PBYTE pbNew = pbCode + 6 + *(UNALIGNED INT32*) & pbCode[2]; + + if (pbNew < pbCode) { + hi = detour_2gb_above((ULONG_PTR)pbNew); + } + else { + lo = detour_2gb_below((ULONG_PTR)pbNew); + } + DETOUR_TRACE(("[%p..%p..%p] [+imm32]\n", (PVOID)lo, pbCode, (PVOID)hi)); + } + // And, within +/- 2GB of relative jmp targets. + else if (pbCode[0] == 0xe9) { // jmp +imm32 + PBYTE pbNew = pbCode + 5 + *(UNALIGNED INT32*) & pbCode[1]; + + if (pbNew < pbCode) { + hi = detour_2gb_above((ULONG_PTR)pbNew); + } + else { + lo = detour_2gb_below((ULONG_PTR)pbNew); + } + DETOUR_TRACE(("[%p..%p..%p] +imm32\n", (PVOID)lo, pbCode, (PVOID)hi)); + } + + *ppLower = (PDETOUR_TRAMPOLINE)lo; + *ppUpper = (PDETOUR_TRAMPOLINE)hi; +} + +inline BOOL detour_does_code_end_function(PBYTE pbCode) +{ + if (pbCode[0] == 0xeb || // jmp +imm8 + pbCode[0] == 0xe9 || // jmp +imm32 + pbCode[0] == 0xe0 || // jmp eax + pbCode[0] == 0xc2 || // ret +imm8 + pbCode[0] == 0xc3 || // ret + pbCode[0] == 0xcc) { // brk + return TRUE; + } + else if (pbCode[0] == 0xf3 && pbCode[1] == 0xc3) { // rep ret + return TRUE; + } + else if (pbCode[0] == 0xff && pbCode[1] == 0x25) { // jmp [+imm32] + return TRUE; + } + else if ((pbCode[0] == 0x26 || // jmp es: + pbCode[0] == 0x2e || // jmp cs: + pbCode[0] == 0x36 || // jmp ss: + pbCode[0] == 0x3e || // jmp ds: + pbCode[0] == 0x64 || // jmp fs: + pbCode[0] == 0x65) && // jmp gs: + pbCode[1] == 0xff && // jmp [+imm32] + pbCode[2] == 0x25) { + return TRUE; + } + return FALSE; +} + +inline ULONG detour_is_code_filler(PBYTE pbCode) +{ + // 1-byte through 11-byte NOPs. + if (pbCode[0] == 0x90) { + return 1; + } + if (pbCode[0] == 0x66 && pbCode[1] == 0x90) { + return 2; + } + if (pbCode[0] == 0x0F && pbCode[1] == 0x1F && pbCode[2] == 0x00) { + return 3; + } + if (pbCode[0] == 0x0F && pbCode[1] == 0x1F && pbCode[2] == 0x40 && + pbCode[3] == 0x00) { + return 4; + } + if (pbCode[0] == 0x0F && pbCode[1] == 0x1F && pbCode[2] == 0x44 && + pbCode[3] == 0x00 && pbCode[4] == 0x00) { + return 5; + } + if (pbCode[0] == 0x66 && pbCode[1] == 0x0F && pbCode[2] == 0x1F && + pbCode[3] == 0x44 && pbCode[4] == 0x00 && pbCode[5] == 0x00) { + return 6; + } + if (pbCode[0] == 0x0F && pbCode[1] == 0x1F && pbCode[2] == 0x80 && + pbCode[3] == 0x00 && pbCode[4] == 0x00 && pbCode[5] == 0x00 && + pbCode[6] == 0x00) { + return 7; + } + if (pbCode[0] == 0x0F && pbCode[1] == 0x1F && pbCode[2] == 0x84 && + pbCode[3] == 0x00 && pbCode[4] == 0x00 && pbCode[5] == 0x00 && + pbCode[6] == 0x00 && pbCode[7] == 0x00) { + return 8; + } + if (pbCode[0] == 0x66 && pbCode[1] == 0x0F && pbCode[2] == 0x1F && + pbCode[3] == 0x84 && pbCode[4] == 0x00 && pbCode[5] == 0x00 && + pbCode[6] == 0x00 && pbCode[7] == 0x00 && pbCode[8] == 0x00) { + return 9; + } + if (pbCode[0] == 0x66 && pbCode[1] == 0x66 && pbCode[2] == 0x0F && + pbCode[3] == 0x1F && pbCode[4] == 0x84 && pbCode[5] == 0x00 && + pbCode[6] == 0x00 && pbCode[7] == 0x00 && pbCode[8] == 0x00 && + pbCode[9] == 0x00) { + return 10; + } + if (pbCode[0] == 0x66 && pbCode[1] == 0x66 && pbCode[2] == 0x66 && + pbCode[3] == 0x0F && pbCode[4] == 0x1F && pbCode[5] == 0x84 && + pbCode[6] == 0x00 && pbCode[7] == 0x00 && pbCode[8] == 0x00 && + pbCode[9] == 0x00 && pbCode[10] == 0x00) { + return 11; + } + + // int 3. + if (pbCode[0] == 0xcc) { + return 1; + } + return 0; +} + +#endif // DETOURS_X64 + +//////////////////////////////////////////////////////////////////////// IA64. +// +#ifdef DETOURS_IA64 + +struct _DETOUR_TRAMPOLINE +{ + // On the IA64, a trampoline is used for both incoming and outgoing calls. + // + // The trampoline contains the following bundles for the outgoing call: + // movl gp=target_gp; + // + // brl target_code; + // + // The trampoline contains the following bundles for the incoming call: + // alloc r41=ar.pfs, b, 0, 8, 0 + // mov r40=rp + // + // adds r50=0, r39 + // adds r49=0, r38 + // adds r48=0, r37 ;; + // + // adds r47=0, r36 + // adds r46=0, r35 + // adds r45=0, r34 + // + // adds r44=0, r33 + // adds r43=0, r32 + // adds r42=0, gp ;; + // + // movl gp=ffffffff`ffffffff ;; + // + // brl.call.sptk.few rp=disas!TestCodes+20e0 (00000000`00404ea0) ;; + // + // adds gp=0, r42 + // mov rp=r40, +0 ;; + // mov.i ar.pfs=r41 + // + // br.ret.sptk.many rp ;; + // + // This way, we only have to relocate a single bundle. + // + // The complicated incoming trampoline is required because we have to + // create an additional stack frame so that we save and restore the gp. + // We must do this because gp is a caller-saved register, but not saved + // if the caller thinks the target is in the same DLL, which changes + // when we insert a detour. + // + DETOUR_IA64_BUNDLE bMovlTargetGp; // Bundle which sets target GP + BYTE rbCode[sizeof(DETOUR_IA64_BUNDLE)]; // moved bundle. + DETOUR_IA64_BUNDLE bBrlRemainEip; // Brl to pbRemain + // This must be adjacent to bBranchIslands. + + // Each instruction in the moved bundle could be a IP-relative chk or branch or call. + // Any such instructions are changed to point to a brl in bBranchIslands. + // This must be adjacent to bBrlRemainEip -- see "pbPool". + DETOUR_IA64_BUNDLE bBranchIslands[DETOUR_IA64_INSTRUCTIONS_PER_BUNDLE]; + + // Target of brl inserted in target function + DETOUR_IA64_BUNDLE bAllocFrame; // alloc frame + DETOUR_IA64_BUNDLE bSave37to39; // save r37, r38, r39. + DETOUR_IA64_BUNDLE bSave34to36; // save r34, r35, r36. + DETOUR_IA64_BUNDLE bSaveGPto33; // save gp, r32, r33. + DETOUR_IA64_BUNDLE bMovlDetourGp; // set detour GP. + DETOUR_IA64_BUNDLE bCallDetour; // call detour. + DETOUR_IA64_BUNDLE bPopFrameGp; // pop frame and restore gp. + DETOUR_IA64_BUNDLE bReturn; // return to caller. + + PLABEL_DESCRIPTOR pldTrampoline; + + BYTE rbRestore[sizeof(DETOUR_IA64_BUNDLE)]; // original target bundle. + BYTE cbRestore; // size of original target code. + BYTE cbCode; // size of moved target code. + _DETOUR_ALIGN rAlign[14]; // instruction alignment array. + PBYTE pbRemain; // first instruction after moved code. [free list] + PBYTE pbDetour; // first instruction of detour function. + PPLABEL_DESCRIPTOR ppldDetour; // [pbDetour,gpDetour] + PPLABEL_DESCRIPTOR ppldTarget; // [pbTarget,gpDetour] +}; + +C_ASSERT(sizeof(DETOUR_IA64_BUNDLE) == 16); +C_ASSERT(sizeof(_DETOUR_TRAMPOLINE) == 256 + DETOUR_IA64_INSTRUCTIONS_PER_BUNDLE * 16); + +enum { + SIZE_OF_JMP = sizeof(DETOUR_IA64_BUNDLE) +}; + +inline PBYTE detour_skip_jmp(PBYTE pPointer, PVOID* ppGlobals) +{ + PBYTE pGlobals = NULL; + PBYTE pbCode = NULL; + + if (pPointer != NULL) { + PPLABEL_DESCRIPTOR ppld = (PPLABEL_DESCRIPTOR)pPointer; + pbCode = (PBYTE)ppld->EntryPoint; + pGlobals = (PBYTE)ppld->GlobalPointer; + } + if (ppGlobals != NULL) { + *ppGlobals = pGlobals; + } + if (pbCode == NULL) { + return NULL; + } + + DETOUR_IA64_BUNDLE* pb = (DETOUR_IA64_BUNDLE*)pbCode; + + // IA64 Local Import Jumps look like: + // addl r2=ffffffff`ffe021c0, gp ;; + // ld8 r2=[r2] + // nop.i 0 ;; + // + // ld8 r3=[r2], 8 ;; + // ld8 gp=[r2] + // mov b6=r3, +0 + // + // nop.m 0 + // nop.i 0 + // br.cond.sptk.few b6 + // + + // 002024000200100b + if ((pb[0].wide[0] & 0xfffffc000603ffff) == 0x002024000200100b && + pb[0].wide[1] == 0x0004000000203008 && + pb[1].wide[0] == 0x001014180420180a && + pb[1].wide[1] == 0x07000830c0203008 && + pb[2].wide[0] == 0x0000000100000010 && + pb[2].wide[1] == 0x0080006000000200) { + + ULONG64 offset = + ((pb[0].wide[0] & 0x0000000001fc0000) >> 18) | // imm7b + ((pb[0].wide[0] & 0x000001ff00000000) >> 25) | // imm9d + ((pb[0].wide[0] & 0x00000000f8000000) >> 11); // imm5c + if (pb[0].wide[0] & 0x0000020000000000) { // sign + offset |= 0xffffffffffe00000; + } + PBYTE pbTarget = pGlobals + offset; + DETOUR_TRACE(("%p: potential import jump, target=%p\n", pb, pbTarget)); + + if (detour_is_imported(pbCode, pbTarget) && *(PBYTE*)pbTarget != NULL) { + DETOUR_TRACE(("%p: is import jump, label=%p\n", pb, *(PBYTE*)pbTarget)); + + PPLABEL_DESCRIPTOR ppld = (PPLABEL_DESCRIPTOR) * (PBYTE*)pbTarget; + pbCode = (PBYTE)ppld->EntryPoint; + pGlobals = (PBYTE)ppld->GlobalPointer; + if (ppGlobals != NULL) { + *ppGlobals = pGlobals; + } + } + } + return pbCode; +} + + +inline void detour_find_jmp_bounds(PBYTE pbCode, + PDETOUR_TRAMPOLINE* ppLower, + PDETOUR_TRAMPOLINE* ppUpper) +{ + (void)pbCode; + *ppLower = (PDETOUR_TRAMPOLINE)(ULONG_PTR)0x0000000000080000; + *ppUpper = (PDETOUR_TRAMPOLINE)(ULONG_PTR)0xfffffffffff80000; +} + +inline BOOL detour_does_code_end_function(PBYTE pbCode) +{ + // Routine not needed on IA64. + (void)pbCode; + return FALSE; +} + +inline ULONG detour_is_code_filler(PBYTE pbCode) +{ + // Routine not needed on IA64. + (void)pbCode; + return 0; +} + +#endif // DETOURS_IA64 + +#ifdef DETOURS_ARM + +struct _DETOUR_TRAMPOLINE +{ + // A Thumb-2 instruction can be 2 or 4 bytes long. + BYTE rbCode[62]; // target code + jmp to pbRemain + BYTE cbCode; // size of moved target code. + BYTE cbCodeBreak; // padding to make debugging easier. + BYTE rbRestore[22]; // original target code. + BYTE cbRestore; // size of original target code. + BYTE cbRestoreBreak; // padding to make debugging easier. + _DETOUR_ALIGN rAlign[8]; // instruction alignment array. + PBYTE pbRemain; // first instruction after moved code. [free list] + PBYTE pbDetour; // first instruction of detour function. +}; + +C_ASSERT(sizeof(_DETOUR_TRAMPOLINE) == 104); + +enum { + SIZE_OF_JMP = 8 +}; + +inline PBYTE align4(PBYTE pValue) +{ + return (PBYTE)(((ULONG)pValue) & ~(ULONG)3u); +} + +inline ULONG fetch_thumb_opcode(PBYTE pbCode) +{ + ULONG Opcode = *(UINT16*)&pbCode[0]; + if (Opcode >= 0xe800) { + Opcode = (Opcode << 16) | *(UINT16*)&pbCode[2]; + } + return Opcode; +} + +inline void write_thumb_opcode(PBYTE& pbCode, ULONG Opcode) +{ + if (Opcode >= 0x10000) { + *((UINT16*&)pbCode)++ = Opcode >> 16; + } + *((UINT16*&)pbCode)++ = (UINT16)Opcode; +} + +PBYTE detour_gen_jmp_immediate(PBYTE pbCode, PBYTE* ppPool, PBYTE pbJmpVal) +{ + PBYTE pbLiteral; + if (ppPool != NULL) { + *ppPool = *ppPool - 4; + pbLiteral = *ppPool; + } + else { + pbLiteral = align4(pbCode + 6); + } + + *((PBYTE*&)pbLiteral) = DETOURS_PBYTE_TO_PFUNC(pbJmpVal); + LONG delta = pbLiteral - align4(pbCode + 4); + + write_thumb_opcode(pbCode, 0xf8dff000 | delta); // LDR PC,[PC+n] + + if (ppPool == NULL) { + if (((ULONG)pbCode & 2) != 0) { + write_thumb_opcode(pbCode, 0xdefe); // BREAK + } + pbCode += 4; + } + return pbCode; +} + +inline PBYTE detour_gen_brk(PBYTE pbCode, PBYTE pbLimit) +{ + while (pbCode < pbLimit) { + write_thumb_opcode(pbCode, 0xdefe); + } + return pbCode; +} + +inline PBYTE detour_skip_jmp(PBYTE pbCode, PVOID* ppGlobals) +{ + if (pbCode == NULL) { + return NULL; + } + if (ppGlobals != NULL) { + *ppGlobals = NULL; + } + + // Skip over the import jump if there is one. + pbCode = (PBYTE)DETOURS_PFUNC_TO_PBYTE(pbCode); + ULONG Opcode = fetch_thumb_opcode(pbCode); + + if ((Opcode & 0xfbf08f00) == 0xf2400c00) { // movw r12,#xxxx + ULONG Opcode2 = fetch_thumb_opcode(pbCode + 4); + + if ((Opcode2 & 0xfbf08f00) == 0xf2c00c00) { // movt r12,#xxxx + ULONG Opcode3 = fetch_thumb_opcode(pbCode + 8); + if (Opcode3 == 0xf8dcf000) { // ldr pc,[r12] + PBYTE pbTarget = (PBYTE)(((Opcode2 << 12) & 0xf7000000) | + ((Opcode2 << 1) & 0x08000000) | + ((Opcode2 << 16) & 0x00ff0000) | + ((Opcode >> 4) & 0x0000f700) | + ((Opcode >> 15) & 0x00000800) | + ((Opcode >> 0) & 0x000000ff)); + if (detour_is_imported(pbCode, pbTarget)) { + PBYTE pbNew = *(PBYTE*)pbTarget; + pbNew = DETOURS_PFUNC_TO_PBYTE(pbNew); + DETOUR_TRACE(("%p->%p: skipped over import table.\n", pbCode, pbNew)); + return pbNew; + } + } + } + } + return pbCode; +} + +inline void detour_find_jmp_bounds(PBYTE pbCode, + PDETOUR_TRAMPOLINE* ppLower, + PDETOUR_TRAMPOLINE* ppUpper) +{ + // We have to place trampolines within +/- 2GB of code. + ULONG_PTR lo = detour_2gb_below((ULONG_PTR)pbCode); + ULONG_PTR hi = detour_2gb_above((ULONG_PTR)pbCode); + DETOUR_TRACE(("[%p..%p..%p]\n", (PVOID)lo, pbCode, (PVOID)hi)); + + *ppLower = (PDETOUR_TRAMPOLINE)lo; + *ppUpper = (PDETOUR_TRAMPOLINE)hi; +} + + +inline BOOL detour_does_code_end_function(PBYTE pbCode) +{ + ULONG Opcode = fetch_thumb_opcode(pbCode); + if ((Opcode & 0xffffff87) == 0x4700 || // bx + (Opcode & 0xf800d000) == 0xf0009000) { // b + return TRUE; + } + if ((Opcode & 0xffff8000) == 0xe8bd8000) { // pop {...,pc} + __debugbreak(); + return TRUE; + } + if ((Opcode & 0xffffff00) == 0x0000bd00) { // pop {...,pc} + __debugbreak(); + return TRUE; + } + return FALSE; +} + +inline ULONG detour_is_code_filler(PBYTE pbCode) +{ + if (pbCode[0] == 0x00 && pbCode[1] == 0xbf) { // nop. + return 2; + } + if (pbCode[0] == 0x00 && pbCode[1] == 0x00) { // zero-filled padding. + return 2; + } + return 0; +} + +#endif // DETOURS_ARM + +#ifdef DETOURS_ARM64 + +struct _DETOUR_TRAMPOLINE +{ + // An ARM64 instruction is 4 bytes long. + // + // The overwrite is always composed of 3 instructions (12 bytes) which perform an indirect jump + // using _DETOUR_TRAMPOLINE::pbDetour as the address holding the target location. + // + // Copied instructions can expand. + // + // The scheme using MovImmediate can cause an instruction + // to grow as much as 6 times. + // That would be Bcc or Tbz with a large address space: + // 4 instructions to form immediate + // inverted tbz/bcc + // br + // + // An expansion of 4 is not uncommon -- bl/blr and small address space: + // 3 instructions to form immediate + // br or brl + // + // A theoretical maximum for rbCode is thefore 4*4*6 + 16 = 112 (another 16 for jmp to pbRemain). + // + // With literals, the maximum expansion is 5, including the literals: 4*4*5 + 16 = 96. + // + // The number is rounded up to 128. m_rbScratchDst should match this. + // + BYTE rbCode[128]; // target code + jmp to pbRemain + BYTE cbCode; // size of moved target code. + BYTE cbCodeBreak[3]; // padding to make debugging easier. + BYTE rbRestore[24]; // original target code. + BYTE cbRestore; // size of original target code. + BYTE cbRestoreBreak[3]; // padding to make debugging easier. + _DETOUR_ALIGN rAlign[8]; // instruction alignment array. + PBYTE pbRemain; // first instruction after moved code. [free list] + PBYTE pbDetour; // first instruction of detour function. +}; + +C_ASSERT(sizeof(_DETOUR_TRAMPOLINE) == 184); + +enum { + SIZE_OF_JMP = 12 +}; + +inline ULONG fetch_opcode(PBYTE pbCode) +{ + return *(ULONG*)pbCode; +} + +inline void write_opcode(PBYTE& pbCode, ULONG Opcode) +{ + *(ULONG*)pbCode = Opcode; + pbCode += 4; +} + +struct ARM64_INDIRECT_JMP { + struct { + ULONG Rd : 5; + ULONG immhi : 19; + ULONG iop : 5; + ULONG immlo : 2; + ULONG op : 1; + } ardp; + + struct { + ULONG Rt : 5; + ULONG Rn : 5; + ULONG imm : 12; + ULONG opc : 2; + ULONG iop1 : 2; + ULONG V : 1; + ULONG iop2 : 3; + ULONG size : 2; + } ldr; + + ULONG br; +}; + +#pragma warning(push) +#pragma warning(disable:4201) + +union ARM64_INDIRECT_IMM { + struct { + ULONG64 pad : 12; + ULONG64 adrp_immlo : 2; + ULONG64 adrp_immhi : 19; + }; + + LONG64 value; +}; + +#pragma warning(pop) + +PBYTE detour_gen_jmp_indirect(BYTE* pbCode, ULONG64* pbJmpVal) +{ + // adrp x17, [jmpval] + // ldr x17, [x17, jmpval] + // br x17 + + struct ARM64_INDIRECT_JMP* pIndJmp; + union ARM64_INDIRECT_IMM jmpIndAddr; + + jmpIndAddr.value = (((LONG64)pbJmpVal) & 0xFFFFFFFFFFFFF000) - + (((LONG64)pbCode) & 0xFFFFFFFFFFFFF000); + + pIndJmp = (struct ARM64_INDIRECT_JMP*)pbCode; + pbCode = (BYTE*)(pIndJmp + 1); + + pIndJmp->ardp.Rd = 17; + pIndJmp->ardp.immhi = jmpIndAddr.adrp_immhi; + pIndJmp->ardp.iop = 0x10; + pIndJmp->ardp.immlo = jmpIndAddr.adrp_immlo; + pIndJmp->ardp.op = 1; + + pIndJmp->ldr.Rt = 17; + pIndJmp->ldr.Rn = 17; + pIndJmp->ldr.imm = (((ULONG64)pbJmpVal) & 0xFFF) / 8; + pIndJmp->ldr.opc = 1; + pIndJmp->ldr.iop1 = 1; + pIndJmp->ldr.V = 0; + pIndJmp->ldr.iop2 = 7; + pIndJmp->ldr.size = 3; + + pIndJmp->br = 0xD61F0220; + + return pbCode; +} + +PBYTE detour_gen_jmp_immediate(PBYTE pbCode, PBYTE* ppPool, PBYTE pbJmpVal) +{ + PBYTE pbLiteral; + if (ppPool != NULL) { + *ppPool = *ppPool - 8; + pbLiteral = *ppPool; + } + else { + pbLiteral = pbCode + 8; + } + + *((PBYTE*&)pbLiteral) = pbJmpVal; + LONG delta = (LONG)(pbLiteral - pbCode); + + write_opcode(pbCode, 0x58000011 | ((delta / 4) << 5)); // LDR X17,[PC+n] + write_opcode(pbCode, 0xd61f0000 | (17 << 5)); // BR X17 + + if (ppPool == NULL) { + pbCode += 8; + } + return pbCode; +} + +inline PBYTE detour_gen_brk(PBYTE pbCode, PBYTE pbLimit) +{ + while (pbCode < pbLimit) { + write_opcode(pbCode, 0xd4100000 | (0xf000 << 5)); + } + return pbCode; +} + +inline INT64 detour_sign_extend(UINT64 value, UINT bits) +{ + const UINT left = 64 - bits; + const INT64 m1 = -1; + const INT64 wide = (INT64)(value << left); + const INT64 sign = (wide < 0) ? (m1 << left) : 0; + return value | sign; +} + +inline PBYTE detour_skip_jmp(PBYTE pbCode, PVOID* ppGlobals) +{ + if (pbCode == NULL) { + return NULL; + } + if (ppGlobals != NULL) { + *ppGlobals = NULL; + } + + // Skip over the import jump if there is one. + pbCode = (PBYTE)pbCode; + ULONG Opcode = fetch_opcode(pbCode); + + if ((Opcode & 0x9f00001f) == 0x90000010) { // adrp x16, IAT + ULONG Opcode2 = fetch_opcode(pbCode + 4); + + if ((Opcode2 & 0xffe003ff) == 0xf9400210) { // ldr x16, [x16, IAT] + ULONG Opcode3 = fetch_opcode(pbCode + 8); + + if (Opcode3 == 0xd61f0200) { // br x16 + + /* https://static.docs.arm.com/ddi0487/bb/DDI0487B_b_armv8_arm.pdf + The ADRP instruction shifts a signed, 21-bit immediate left by 12 bits, adds it to the value of the program counter with + the bottom 12 bits cleared to zero, and then writes the result to a general-purpose register. This permits the + calculation of the address at a 4KB aligned memory region. In conjunction with an ADD (immediate) instruction, or + a Load/Store instruction with a 12-bit immediate offset, this allows for the calculation of, or access to, any address + within +/- 4GB of the current PC. + + PC-rel. addressing + This section describes the encoding of the PC-rel. addressing instruction class. The encodings in this section are + decoded from Data Processing -- Immediate on page C4-226. + Add/subtract (immediate) + This section describes the encoding of the Add/subtract (immediate) instruction class. The encodings in this section + are decoded from Data Processing -- Immediate on page C4-226. + Decode fields + Instruction page + op + 0 ADR + 1 ADRP + + C6.2.10 ADRP + Form PC-relative address to 4KB page adds an immediate value that is shifted left by 12 bits, to the PC value to + form a PC-relative address, with the bottom 12 bits masked out, and writes the result to the destination register. + ADRP ,