From 34ff6fb1b852c12e4401bf4f58d15d80580ec526 Mon Sep 17 00:00:00 2001 From: KingKDot Date: Wed, 19 Aug 2026 16:16:23 -0500 Subject: [PATCH] full rust works (also AI). --- BackstageInjection-Rust/Cargo.lock | 1 - BackstageInjection-Rust/Cargo.toml | 3 - BackstageInjection-Rust/build.rs | 48 -- .../reflective/ReflectiveDLLInjection.h | 51 -- .../reflective/ReflectiveLoader.c | 562 ------------------ .../reflective/ReflectiveLoader.h | 215 ------- BackstageInjection-Rust/reflective/obfstr.h | 57 -- BackstageInjection-Rust/src/lib.rs | 5 +- BackstageInjection-Rust/src/reflective.rs | 544 +++++++++++++++++ scripts/build-backstage-dll.bat | 6 +- 10 files changed, 550 insertions(+), 942 deletions(-) delete mode 100644 BackstageInjection-Rust/build.rs delete mode 100644 BackstageInjection-Rust/reflective/ReflectiveDLLInjection.h delete mode 100644 BackstageInjection-Rust/reflective/ReflectiveLoader.c delete mode 100644 BackstageInjection-Rust/reflective/ReflectiveLoader.h delete mode 100644 BackstageInjection-Rust/reflective/obfstr.h create mode 100644 BackstageInjection-Rust/src/reflective.rs diff --git a/BackstageInjection-Rust/Cargo.lock b/BackstageInjection-Rust/Cargo.lock index 53d06a9..bc0f389 100644 --- a/BackstageInjection-Rust/Cargo.lock +++ b/BackstageInjection-Rust/Cargo.lock @@ -6,7 +6,6 @@ version = 4 name = "backstage-injection-rs" version = "0.1.0" dependencies = [ - "cc", "minhook", ] diff --git a/BackstageInjection-Rust/Cargo.toml b/BackstageInjection-Rust/Cargo.toml index 5668a26..4cb81b5 100644 --- a/BackstageInjection-Rust/Cargo.toml +++ b/BackstageInjection-Rust/Cargo.toml @@ -10,9 +10,6 @@ crate-type = ["cdylib"] [dependencies] minhook = "0.9.0" -[build-dependencies] -cc = "1" - [profile.release] opt-level = 3 lto = true diff --git a/BackstageInjection-Rust/build.rs b/BackstageInjection-Rust/build.rs deleted file mode 100644 index 46f2ebd..0000000 --- a/BackstageInjection-Rust/build.rs +++ /dev/null @@ -1,48 +0,0 @@ -//! Compiles the vendored Harmony Security reflective loader -//! (`reflective/ReflectiveLoader.c`, BSD-2-Clause, see file header) into the -//! cdylib so the DLL exports `ReflectiveLoader`. The position-independent -//! loader is intentionally kept in C: it is the exact loader the C reference -//! DLL uses and re-implementing it in Rust would be unnecessary risk. - -use std::path::PathBuf; - -fn main() { - let dir = PathBuf::from("reflective"); - for f in [ - "reflective/ReflectiveLoader.c", - "reflective/ReflectiveLoader.h", - "reflective/ReflectiveDLLInjection.h", - "reflective/obfstr.h", - ] { - println!("cargo:rerun-if-changed={f}"); - } - - // Matches BackstageInjection.vcxproj (Release|x64): VIA_LOADREMOTELIBRARYR - // lets ReflectiveLoader accept a thread-routine parameter (the agent and - // child injenter pass NULL), and CUSTOM_DLLMAIN avoids emitting a second - // DllMain (the Rust cdylib exports its own). - let mut build = cc::Build::new(); - build - .file("reflective/ReflectiveLoader.c") - .include(dir) - .opt_level(2) - .static_crt(true) - .cargo_metadata(false) - .define("WIN64", None) - .define("NDEBUG", None) - .define("_WINDOWS", None) - .define("_USRDLL", None) - .define("WIN_X64", None) - .define("REFLECTIVEDLLINJECTION_VIA_LOADREMOTELIBRARYR", None) - .define("REFLECTIVEDLLINJECTION_CUSTOM_DLLMAIN", None); - build.compile("reflective_loader"); - - // Force all members of the static lib into the DLL even though no Rust - // symbol references them; `ReflectiveLoader` carries __declspec(dllexport) - // and only becomes an export once its object file is actually linked. - println!( - "cargo:rustc-link-search=native={}", - std::env::var("OUT_DIR").unwrap_or_default() - ); - println!("cargo:rustc-link-lib=static:+whole-archive=reflective_loader"); -} \ No newline at end of file diff --git a/BackstageInjection-Rust/reflective/ReflectiveDLLInjection.h b/BackstageInjection-Rust/reflective/ReflectiveDLLInjection.h deleted file mode 100644 index 72fd96b..0000000 --- a/BackstageInjection-Rust/reflective/ReflectiveDLLInjection.h +++ /dev/null @@ -1,51 +0,0 @@ -//===============================================================================================// -// Copyright (c) 2012, Stephen Fewer of Harmony Security (www.harmonysecurity.com) -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without modification, are permitted -// provided that the following conditions are met: -// -// * Redistributions of source code must retain the above copyright notice, this list of -// conditions and the following disclaimer. -// -// * Redistributions in binary form must reproduce the above copyright notice, this list of -// conditions and the following disclaimer in the documentation and/or other materials provided -// with the distribution. -// -// * Neither the name of Harmony Security nor the names of its contributors may be used to -// endorse or promote products derived from this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR -// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND -// FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR -// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR -// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. -//===============================================================================================// -#ifndef _REFLECTIVEDLLINJECTION_REFLECTIVEDLLINJECTION_H -#define _REFLECTIVEDLLINJECTION_REFLECTIVEDLLINJECTION_H -//===============================================================================================// -#define WIN32_LEAN_AND_MEAN -#include - -// we declare some common stuff in here... - -#define DLL_QUERY_HMODULE 6 - -#define DEREF( name )*(UINT_PTR *)(name) -#define DEREF_64( name )*(DWORD64 *)(name) -#define DEREF_32( name )*(DWORD *)(name) -#define DEREF_16( name )*(WORD *)(name) -#define DEREF_8( name )*(BYTE *)(name) - -typedef ULONG_PTR(WINAPI* REFLECTIVELOADER)(VOID); -typedef BOOL(WINAPI* DLLMAIN)(HINSTANCE, DWORD, LPVOID); - -#define DLLEXPORT __declspec( dllexport ) - -//===============================================================================================// -#endif -//===============================================================================================// diff --git a/BackstageInjection-Rust/reflective/ReflectiveLoader.c b/BackstageInjection-Rust/reflective/ReflectiveLoader.c deleted file mode 100644 index e2ede62..0000000 --- a/BackstageInjection-Rust/reflective/ReflectiveLoader.c +++ /dev/null @@ -1,562 +0,0 @@ -//===============================================================================================// -// Copyright (c) 2012, Stephen Fewer of Harmony Security (www.harmonysecurity.com) -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without modification, are permitted -// provided that the following conditions are met: -// -// * Redistributions of source code must retain the above copyright notice, this list of -// conditions and the following disclaimer. -// -// * Redistributions in binary form must reproduce the above copyright notice, this list of -// conditions and the following disclaimer in the documentation and/or other materials provided -// with the distribution. -// -// * Neither the name of Harmony Security nor the names of its contributors may be used to -// endorse or promote products derived from this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR -// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND -// FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR -// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR -// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. -//===============================================================================================// -#include "ReflectiveLoader.h" -#include "obfstr.h" -//===============================================================================================// -// Our loader will set this to a pseudo correct HINSTANCE/HMODULE value -HINSTANCE hAppInstance = NULL; -// Store the parameter passed to ReflectiveLoader so DllMain can access it -LPVOID g_lpReflectiveParameter = NULL; -//===============================================================================================// -#ifdef _MSC_VER -#pragma intrinsic(_ReturnAddress) -#define RDI_NOINLINE __declspec(noinline) -#else -#define RDI_NOINLINE __attribute__((noinline)) -#endif - -// This function can not be inlined by the compiler or we will not get the address we expect. Ideally -// this code will be compiled with the /O2 and /Ob1 switches. Bonus points if we could take advantage of -// RIP relative addressing in this instance but I dont believe we can do so with the compiler intrinsics -// available (and no inline asm available under x64). -RDI_NOINLINE ULONG_PTR caller(VOID) -{ -#ifdef _MSC_VER - return (ULONG_PTR)_ReturnAddress(); -#elif defined(__GNUC__) || defined(__clang__) - // For MinGW/Clang, use GCC builtins and extract helper for target-specific safety. - return (ULONG_PTR)__builtin_extract_return_addr(__builtin_return_address(0)); -#else - return 0; -#endif -} -//===============================================================================================// - -// Note 1: If you want to have your own DllMain, define REFLECTIVEDLLINJECTION_CUSTOM_DLLMAIN, -// otherwise the DllMain at the end of this file will be used. - -// Note 2: If you are injecting the DLL via LoadRemoteLibraryR, define REFLECTIVEDLLINJECTION_VIA_LOADREMOTELIBRARYR, -// otherwise it is assumed you are calling the ReflectiveLoader via a stub. - -// This is our position independent reflective DLL loader/injector -#ifdef REFLECTIVEDLLINJECTION_VIA_LOADREMOTELIBRARYR -DLLEXPORT ULONG_PTR WINAPI ReflectiveLoader(LPVOID lpParameter) -#else -DLLEXPORT ULONG_PTR WINAPI ReflectiveLoader(VOID) -#endif -{ - // the functions we need - LOADLIBRARYA pLoadLibraryA = NULL; - GETPROCADDRESS pGetProcAddress = NULL; - VIRTUALALLOC pVirtualAlloc = NULL; - NTFLUSHINSTRUCTIONCACHE pNtFlushInstructionCache = NULL; -#ifdef WIN_X64 - FARPROC pRtlAddFunctionTable = NULL; -#endif - - USHORT usCounter; - - // the initial location of this image in memory - ULONG_PTR uiLibraryAddress; - // the kernels base address and later this images newly loaded base address - ULONG_PTR uiBaseAddress; - - // variables for processing the kernels export table - ULONG_PTR uiAddressArray; - ULONG_PTR uiNameArray; - ULONG_PTR uiExportDir; - ULONG_PTR uiNameOrdinals; - DWORD dwHashValue; - - // variables for loading this image - ULONG_PTR uiHeaderValue; - ULONG_PTR uiValueA; - ULONG_PTR uiValueB; - ULONG_PTR uiValueC; - ULONG_PTR uiValueD; - ULONG_PTR uiValueE; - - // STEP 0: calculate our images current base address - - // we will start searching backwards from our callers return address. - uiLibraryAddress = caller(); - - // loop through memory backwards searching for our images base address - // we dont need SEH style search as we shouldnt generate any access violations with this - while (TRUE) - { - if (((PIMAGE_DOS_HEADER)uiLibraryAddress)->e_magic == IMAGE_DOS_SIGNATURE) - { - uiHeaderValue = ((PIMAGE_DOS_HEADER)uiLibraryAddress)->e_lfanew; - // some x64 dll's can trigger a bogus signature (IMAGE_DOS_SIGNATURE == 'POP r10'), - // we sanity check the e_lfanew with an upper threshold value of 1024 to avoid problems. - if (uiHeaderValue >= sizeof(IMAGE_DOS_HEADER) && uiHeaderValue < 1024) - { - uiHeaderValue += uiLibraryAddress; - // break if we have found a valid MZ/PE header - if (((PIMAGE_NT_HEADERS)uiHeaderValue)->Signature == IMAGE_NT_SIGNATURE) - break; - } - } - uiLibraryAddress--; - } - - // STEP 1: process the kernels exports for the functions our loader needs... - - // get the Process Enviroment Block -#ifdef WIN_X64 - uiBaseAddress = __readgsqword(0x60); -#else -#ifdef WIN_X86 - uiBaseAddress = __readfsdword(0x30); -#else WIN_ARM - uiBaseAddress = *(DWORD*)((BYTE*)_MoveFromCoprocessor(15, 0, 13, 0, 2) + 0x30); -#endif -#endif - - // get the processes loaded modules. ref: http://msdn.microsoft.com/en-us/library/aa813708(VS.85).aspx - uiBaseAddress = (ULONG_PTR)((_PPEB)uiBaseAddress)->pLdr; - - // get the first entry of the InMemoryOrder module list - uiValueA = (ULONG_PTR)((PPEB_LDR_DATA)uiBaseAddress)->InMemoryOrderModuleList.Flink; - while (uiValueA) - { - // get pointer to current modules name (unicode string) - uiValueB = (ULONG_PTR)((PLDR_DATA_TABLE_ENTRY)uiValueA)->BaseDllName.pBuffer; - // set bCounter to the length for the loop - usCounter = ((PLDR_DATA_TABLE_ENTRY)uiValueA)->BaseDllName.Length; - // clear uiValueC which will store the hash of the module name - uiValueC = 0; - - // compute the hash of the module name... - do - { - uiValueC = ror((DWORD)uiValueC); - // normalize to uppercase if the madule name is in lowercase - if (*((BYTE*)uiValueB) >= 'a') - uiValueC += *((BYTE*)uiValueB) - 0x20; - else - uiValueC += *((BYTE*)uiValueB); - uiValueB++; - } while (--usCounter); - - // compare the hash with that of kernel32.dll - if ((DWORD)uiValueC == KERNEL32DLL_HASH) - { - // get this modules base address - uiBaseAddress = (ULONG_PTR)((PLDR_DATA_TABLE_ENTRY)uiValueA)->DllBase; - - // get the VA of the modules NT Header - uiExportDir = uiBaseAddress + ((PIMAGE_DOS_HEADER)uiBaseAddress)->e_lfanew; - - // uiNameArray = the address of the modules export directory entry - uiNameArray = (ULONG_PTR) & ((PIMAGE_NT_HEADERS)uiExportDir)->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT]; - - // get the VA of the export directory - uiExportDir = (uiBaseAddress + ((PIMAGE_DATA_DIRECTORY)uiNameArray)->VirtualAddress); - - // get the VA for the array of name pointers - uiNameArray = (uiBaseAddress + ((PIMAGE_EXPORT_DIRECTORY)uiExportDir)->AddressOfNames); - - // get the VA for the array of name ordinals - uiNameOrdinals = (uiBaseAddress + ((PIMAGE_EXPORT_DIRECTORY)uiExportDir)->AddressOfNameOrdinals); - - usCounter = 3; - - // loop while we still have imports to find - while (usCounter > 0) - { - // compute the hash values for this function name - dwHashValue = hash((char*)(uiBaseAddress + DEREF_32(uiNameArray))); - - // if we have found a function we want we get its virtual address - if (dwHashValue == LOADLIBRARYA_HASH || dwHashValue == GETPROCADDRESS_HASH || dwHashValue == VIRTUALALLOC_HASH) - { - // get the VA for the array of addresses - uiAddressArray = (uiBaseAddress + ((PIMAGE_EXPORT_DIRECTORY)uiExportDir)->AddressOfFunctions); - - // use this functions name ordinal as an index into the array of name pointers - uiAddressArray += (DEREF_16(uiNameOrdinals) * sizeof(DWORD)); - - // store this functions VA - if (dwHashValue == LOADLIBRARYA_HASH) - pLoadLibraryA = (LOADLIBRARYA)(uiBaseAddress + DEREF_32(uiAddressArray)); - else if (dwHashValue == GETPROCADDRESS_HASH) - pGetProcAddress = (GETPROCADDRESS)(uiBaseAddress + DEREF_32(uiAddressArray)); - else if (dwHashValue == VIRTUALALLOC_HASH) - pVirtualAlloc = (VIRTUALALLOC)(uiBaseAddress + DEREF_32(uiAddressArray)); - - // decrement our counter - usCounter--; - } - - // get the next exported function name - uiNameArray += sizeof(DWORD); - - // get the next exported function name ordinal - uiNameOrdinals += sizeof(WORD); - } - } - else if ((DWORD)uiValueC == NTDLLDLL_HASH) - { - // get this modules base address - uiBaseAddress = (ULONG_PTR)((PLDR_DATA_TABLE_ENTRY)uiValueA)->DllBase; - - // get the VA of the modules NT Header - uiExportDir = uiBaseAddress + ((PIMAGE_DOS_HEADER)uiBaseAddress)->e_lfanew; - - // uiNameArray = the address of the modules export directory entry - uiNameArray = (ULONG_PTR) & ((PIMAGE_NT_HEADERS)uiExportDir)->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT]; - - // get the VA of the export directory - uiExportDir = (uiBaseAddress + ((PIMAGE_DATA_DIRECTORY)uiNameArray)->VirtualAddress); - - // get the VA for the array of name pointers - uiNameArray = (uiBaseAddress + ((PIMAGE_EXPORT_DIRECTORY)uiExportDir)->AddressOfNames); - - // get the VA for the array of name ordinals - uiNameOrdinals = (uiBaseAddress + ((PIMAGE_EXPORT_DIRECTORY)uiExportDir)->AddressOfNameOrdinals); - - usCounter = 1; - - // loop while we still have imports to find - while (usCounter > 0) - { - // compute the hash values for this function name - dwHashValue = hash((char*)(uiBaseAddress + DEREF_32(uiNameArray))); - - // if we have found a function we want we get its virtual address - if (dwHashValue == NTFLUSHINSTRUCTIONCACHE_HASH) - { - // get the VA for the array of addresses - uiAddressArray = (uiBaseAddress + ((PIMAGE_EXPORT_DIRECTORY)uiExportDir)->AddressOfFunctions); - - // use this functions name ordinal as an index into the array of name pointers - uiAddressArray += (DEREF_16(uiNameOrdinals) * sizeof(DWORD)); - - // store this functions VA - if (dwHashValue == NTFLUSHINSTRUCTIONCACHE_HASH) - pNtFlushInstructionCache = (NTFLUSHINSTRUCTIONCACHE)(uiBaseAddress + DEREF_32(uiAddressArray)); - - // decrement our counter - usCounter--; - } - - // get the next exported function name - uiNameArray += sizeof(DWORD); - - // get the next exported function name ordinal - uiNameOrdinals += sizeof(WORD); - } - } - - // we stop searching when we have found everything we need. - if (pLoadLibraryA && pGetProcAddress && pVirtualAlloc && pNtFlushInstructionCache) - break; - - // get the next entry - uiValueA = DEREF(uiValueA); - } - -#ifdef WIN_X64 - // Resolve RtlAddFunctionTable so we can register exception unwind data. - // Without this, stack unwinding through our hook functions crashes browsers. - { - HMODULE hNtdll = (HMODULE)pLoadLibraryA(OBFS(_enc_ntdll_dll)); - if (hNtdll) - pRtlAddFunctionTable = pGetProcAddress(hNtdll, OBFS(_enc_RtlAddFunctionTable)); - } -#endif - - // STEP 2: load our image into a new permanent location in memory... - - // get the VA of the NT Header for the PE to be loaded - uiHeaderValue = uiLibraryAddress + ((PIMAGE_DOS_HEADER)uiLibraryAddress)->e_lfanew; - - // allocate all the memory for the DLL to be loaded into. we can load at any address because we will - // relocate the image. Also zeros all memory and marks it as READ, WRITE and EXECUTE to avoid any problems. - uiBaseAddress = (ULONG_PTR)pVirtualAlloc(NULL, ((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.SizeOfImage, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE); - - // Guard against allocation failure (out-of-memory, ACG/CFG policy, etc.). - // Without this check, every subsequent write to uiBaseAddress would be a - // write through NULL — an access violation in the remote process. - if (!uiBaseAddress) - return 0; - - // we must now copy over the headers - uiValueA = ((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.SizeOfHeaders; - uiValueB = uiLibraryAddress; - uiValueC = uiBaseAddress; - - while (uiValueA--) - *(BYTE*)uiValueC++ = *(BYTE*)uiValueB++; - - // STEP 3: load in all of our sections... - - // uiValueA = the VA of the first section - uiValueA = ((ULONG_PTR) & ((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader + ((PIMAGE_NT_HEADERS)uiHeaderValue)->FileHeader.SizeOfOptionalHeader); - - // itterate through all sections, loading them into memory. - uiValueE = ((PIMAGE_NT_HEADERS)uiHeaderValue)->FileHeader.NumberOfSections; - while (uiValueE--) - { - // uiValueB is the VA for this section - uiValueB = (uiBaseAddress + ((PIMAGE_SECTION_HEADER)uiValueA)->VirtualAddress); - - // uiValueC if the VA for this sections data - uiValueC = (uiLibraryAddress + ((PIMAGE_SECTION_HEADER)uiValueA)->PointerToRawData); - - // copy the section over - uiValueD = ((PIMAGE_SECTION_HEADER)uiValueA)->SizeOfRawData; - - while (uiValueD--) - *(BYTE*)uiValueB++ = *(BYTE*)uiValueC++; - - // get the VA of the next section - uiValueA += sizeof(IMAGE_SECTION_HEADER); - } - - // STEP 4: process our images import table... - - // uiValueB = the address of the import directory - uiValueB = (ULONG_PTR) & ((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT]; - - // we assume their is an import table to process - // uiValueC is the first entry in the import table - uiValueC = (uiBaseAddress + ((PIMAGE_DATA_DIRECTORY)uiValueB)->VirtualAddress); - - // itterate through all imports - while (((PIMAGE_IMPORT_DESCRIPTOR)uiValueC)->Name) - { - // use LoadLibraryA to load the imported module into memory - uiLibraryAddress = (ULONG_PTR)pLoadLibraryA((LPCSTR)(uiBaseAddress + ((PIMAGE_IMPORT_DESCRIPTOR)uiValueC)->Name)); - - // uiValueD = VA of the OriginalFirstThunk - uiValueD = (uiBaseAddress + ((PIMAGE_IMPORT_DESCRIPTOR)uiValueC)->OriginalFirstThunk); - - // uiValueA = VA of the IAT (via first thunk not origionalfirstthunk) - uiValueA = (uiBaseAddress + ((PIMAGE_IMPORT_DESCRIPTOR)uiValueC)->FirstThunk); - - // itterate through all imported functions, importing by ordinal if no name present - while (DEREF(uiValueA)) - { - // sanity check uiValueD as some compilers only import by FirstThunk - if (uiValueD && ((PIMAGE_THUNK_DATA)uiValueD)->u1.Ordinal & IMAGE_ORDINAL_FLAG) - { - // get the VA of the modules NT Header - uiExportDir = uiLibraryAddress + ((PIMAGE_DOS_HEADER)uiLibraryAddress)->e_lfanew; - - // uiNameArray = the address of the modules export directory entry - uiNameArray = (ULONG_PTR) & ((PIMAGE_NT_HEADERS)uiExportDir)->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT]; - - // get the VA of the export directory - uiExportDir = (uiLibraryAddress + ((PIMAGE_DATA_DIRECTORY)uiNameArray)->VirtualAddress); - - // get the VA for the array of addresses - uiAddressArray = (uiLibraryAddress + ((PIMAGE_EXPORT_DIRECTORY)uiExportDir)->AddressOfFunctions); - - // use the import ordinal (- export ordinal base) as an index into the array of addresses - uiAddressArray += ((IMAGE_ORDINAL(((PIMAGE_THUNK_DATA)uiValueD)->u1.Ordinal) - ((PIMAGE_EXPORT_DIRECTORY)uiExportDir)->Base) * sizeof(DWORD)); - - // patch in the address for this imported function - DEREF(uiValueA) = (uiLibraryAddress + DEREF_32(uiAddressArray)); - } - else - { - // get the VA of this functions import by name struct - uiValueB = (uiBaseAddress + DEREF(uiValueA)); - - // use GetProcAddress and patch in the address for this imported function - DEREF(uiValueA) = (ULONG_PTR)pGetProcAddress((HMODULE)uiLibraryAddress, (LPCSTR)((PIMAGE_IMPORT_BY_NAME)uiValueB)->Name); - } - // get the next imported function - uiValueA += sizeof(ULONG_PTR); - if (uiValueD) - uiValueD += sizeof(ULONG_PTR); - } - - // get the next import - uiValueC += sizeof(IMAGE_IMPORT_DESCRIPTOR); - } - - // STEP 5: process all of our images relocations... - - // calculate the base address delta and perform relocations (even if we load at desired image base) - uiLibraryAddress = uiBaseAddress - ((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.ImageBase; - - // uiValueB = the address of the relocation directory - uiValueB = (ULONG_PTR) & ((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC]; - - // check if their are any relocations present - if (((PIMAGE_DATA_DIRECTORY)uiValueB)->Size) - { - // uiValueC is now the first entry (IMAGE_BASE_RELOCATION) - uiValueC = (uiBaseAddress + ((PIMAGE_DATA_DIRECTORY)uiValueB)->VirtualAddress); - - // and we itterate through all entries... - while (((PIMAGE_BASE_RELOCATION)uiValueC)->SizeOfBlock) - { - // uiValueA = the VA for this relocation block - uiValueA = (uiBaseAddress + ((PIMAGE_BASE_RELOCATION)uiValueC)->VirtualAddress); - - // uiValueB = number of entries in this relocation block - uiValueB = (((PIMAGE_BASE_RELOCATION)uiValueC)->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(IMAGE_RELOC); - - // uiValueD is now the first entry in the current relocation block - uiValueD = uiValueC + sizeof(IMAGE_BASE_RELOCATION); - - // we itterate through all the entries in the current block... - while (uiValueB--) - { - // perform the relocation, skipping IMAGE_REL_BASED_ABSOLUTE as required. - // we dont use a switch statement to avoid the compiler building a jump table - // which would not be very position independent! - if (((PIMAGE_RELOC)uiValueD)->type == IMAGE_REL_BASED_DIR64) - *(ULONG_PTR*)(uiValueA + ((PIMAGE_RELOC)uiValueD)->offset) += uiLibraryAddress; - else if (((PIMAGE_RELOC)uiValueD)->type == IMAGE_REL_BASED_HIGHLOW) - *(DWORD*)(uiValueA + ((PIMAGE_RELOC)uiValueD)->offset) += (DWORD)uiLibraryAddress; -#ifdef WIN_ARM - // Note: On ARM, the compiler optimization /O2 seems to introduce an off by one issue, possibly a code gen bug. Using /O1 instead avoids this problem. - else if (((PIMAGE_RELOC)uiValueD)->type == IMAGE_REL_BASED_ARM_MOV32T) - { - register DWORD dwInstruction; - register DWORD dwAddress; - register WORD wImm; - // get the MOV.T instructions DWORD value (We add 4 to the offset to go past the first MOV.W which handles the low word) - dwInstruction = *(DWORD*)(uiValueA + ((PIMAGE_RELOC)uiValueD)->offset + sizeof(DWORD)); - // flip the words to get the instruction as expected - dwInstruction = MAKELONG(HIWORD(dwInstruction), LOWORD(dwInstruction)); - // sanity chack we are processing a MOV instruction... - if ((dwInstruction & ARM_MOV_MASK) == ARM_MOVT) - { - // pull out the encoded 16bit value (the high portion of the address-to-relocate) - wImm = (WORD)(dwInstruction & 0x000000FF); - wImm |= (WORD)((dwInstruction & 0x00007000) >> 4); - wImm |= (WORD)((dwInstruction & 0x04000000) >> 15); - wImm |= (WORD)((dwInstruction & 0x000F0000) >> 4); - // apply the relocation to the target address - dwAddress = ((WORD)HIWORD(uiLibraryAddress) + wImm) & 0xFFFF; - // now create a new instruction with the same opcode and register param. - dwInstruction = (DWORD)(dwInstruction & ARM_MOV_MASK2); - // patch in the relocated address... - dwInstruction |= (DWORD)(dwAddress & 0x00FF); - dwInstruction |= (DWORD)(dwAddress & 0x0700) << 4; - dwInstruction |= (DWORD)(dwAddress & 0x0800) << 15; - dwInstruction |= (DWORD)(dwAddress & 0xF000) << 4; - // now flip the instructions words and patch back into the code... - *(DWORD*)(uiValueA + ((PIMAGE_RELOC)uiValueD)->offset + sizeof(DWORD)) = MAKELONG(HIWORD(dwInstruction), LOWORD(dwInstruction)); - } - } -#endif - else if (((PIMAGE_RELOC)uiValueD)->type == IMAGE_REL_BASED_HIGH) - *(WORD*)(uiValueA + ((PIMAGE_RELOC)uiValueD)->offset) += HIWORD(uiLibraryAddress); - else if (((PIMAGE_RELOC)uiValueD)->type == IMAGE_REL_BASED_LOW) - *(WORD*)(uiValueA + ((PIMAGE_RELOC)uiValueD)->offset) += LOWORD(uiLibraryAddress); - - // get the next entry in the current relocation block - uiValueD += sizeof(IMAGE_RELOC); - } - - // get the next entry in the relocation directory - uiValueC = uiValueC + ((PIMAGE_BASE_RELOCATION)uiValueC)->SizeOfBlock; - } - } - - // STEP 5b: register exception function table (.pdata) with the OS. - // Reflective loading bypasses the normal loader which would do this via - // LdrpInsertDataTableEntry. Without registration, any exception that - // unwinds through our hook functions causes STATUS_ACCESS_VIOLATION. -#ifdef WIN_X64 - if (pRtlAddFunctionTable) - { - PIMAGE_NT_HEADERS pLoadedNtHdrs = (PIMAGE_NT_HEADERS)(uiBaseAddress + ((PIMAGE_DOS_HEADER)uiBaseAddress)->e_lfanew); - if (pLoadedNtHdrs->OptionalHeader.NumberOfRvaAndSizes > IMAGE_DIRECTORY_ENTRY_EXCEPTION) - { - PIMAGE_DATA_DIRECTORY pExcDir = &pLoadedNtHdrs->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXCEPTION]; - if (pExcDir->VirtualAddress && pExcDir->Size) - { - typedef BOOLEAN(NTAPI* RTLADDFNTABLE)(PRUNTIME_FUNCTION, DWORD, DWORD64); - ((RTLADDFNTABLE)pRtlAddFunctionTable)( - (PRUNTIME_FUNCTION)(uiBaseAddress + pExcDir->VirtualAddress), - pExcDir->Size / sizeof(RUNTIME_FUNCTION), - (DWORD64)uiBaseAddress - ); - } - } - } -#endif - - // STEP 6: call our images entry point - - // uiValueA = the VA of our newly loaded DLL/EXE's entry point - uiValueA = (uiBaseAddress + ((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.AddressOfEntryPoint); - - // We must flush the instruction cache to avoid stale code being used which was updated by our relocation processing. - // Guard against pNtFlushInstructionCache being NULL (e.g. if ntdll was not found in the PEB module list). - if (pNtFlushInstructionCache) - pNtFlushInstructionCache((HANDLE)-1, NULL, 0); - - // call our respective entry point, fudging our hInstance value -#ifdef REFLECTIVEDLLINJECTION_VIA_LOADREMOTELIBRARYR - // Store the parameter globally so DllMain can access it - g_lpReflectiveParameter = lpParameter; - // if we are injecting a DLL via LoadRemoteLibraryR we call DllMain and pass in our parameter (via the DllMain lpReserved parameter) - ((DLLMAIN)uiValueA)((HINSTANCE)uiBaseAddress, DLL_PROCESS_ATTACH, lpParameter); -#else - // if we are injecting an DLL via a stub we call DllMain with no parameter - ((DLLMAIN)uiValueA)((HINSTANCE)uiBaseAddress, DLL_PROCESS_ATTACH, NULL); -#endif - - // STEP 8: return our new entry point address so whatever called us can call DllMain() if needed. - return uiValueA; -} -//===============================================================================================// -#ifndef REFLECTIVEDLLINJECTION_CUSTOM_DLLMAIN - -BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD dwReason, LPVOID lpReserved) -{ - BOOL bReturnValue = TRUE; - switch (dwReason) - { - case DLL_QUERY_HMODULE: - if (lpReserved != NULL) - *(HMODULE*)lpReserved = hAppInstance; - break; - case DLL_PROCESS_ATTACH: - hAppInstance = hinstDLL; - break; - case DLL_PROCESS_DETACH: - case DLL_THREAD_ATTACH: - case DLL_THREAD_DETACH: - break; - } - return bReturnValue; -} - -#endif -//===============================================================================================// diff --git a/BackstageInjection-Rust/reflective/ReflectiveLoader.h b/BackstageInjection-Rust/reflective/ReflectiveLoader.h deleted file mode 100644 index 3e13533..0000000 --- a/BackstageInjection-Rust/reflective/ReflectiveLoader.h +++ /dev/null @@ -1,215 +0,0 @@ -//===============================================================================================// -// Copyright (c) 2012, Stephen Fewer of Harmony Security (www.harmonysecurity.com) -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without modification, are permitted -// provided that the following conditions are met: -// -// * Redistributions of source code must retain the above copyright notice, this list of -// conditions and the following disclaimer. -// -// * Redistributions in binary form must reproduce the above copyright notice, this list of -// conditions and the following disclaimer in the documentation and/or other materials provided -// with the distribution. -// -// * Neither the name of Harmony Security nor the names of its contributors may be used to -// endorse or promote products derived from this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR -// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND -// FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR -// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR -// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. -//===============================================================================================// -#ifndef _REFLECTIVEDLLINJECTION_REFLECTIVELOADER_H -#define _REFLECTIVEDLLINJECTION_REFLECTIVELOADER_H -//===============================================================================================// -#define WIN32_LEAN_AND_MEAN -#include -#include - -#ifdef _MSC_VER -#include -#else -// MinGW/GCC: _rotr lives in , __readgsqword in -#include -#include -#endif - -#include "ReflectiveDLLInjection.h" - -typedef HMODULE(WINAPI* LOADLIBRARYA)(LPCSTR); -typedef FARPROC(WINAPI* GETPROCADDRESS)(HMODULE, LPCSTR); -typedef LPVOID(WINAPI* VIRTUALALLOC)(LPVOID, SIZE_T, DWORD, DWORD); -typedef DWORD(NTAPI* NTFLUSHINSTRUCTIONCACHE)(HANDLE, PVOID, ULONG); - -#define KERNEL32DLL_HASH 0x6A4ABC5B -#define NTDLLDLL_HASH 0x3CFA685D - -#define LOADLIBRARYA_HASH 0xEC0E4E8E -#define GETPROCADDRESS_HASH 0x7C0DFCAA -#define VIRTUALALLOC_HASH 0x91AFCA54 -#define NTFLUSHINSTRUCTIONCACHE_HASH 0x534C0AB8 - -#define IMAGE_REL_BASED_ARM_MOV32A 5 -#define IMAGE_REL_BASED_ARM_MOV32T 7 - -#define ARM_MOV_MASK (DWORD)(0xFBF08000) -#define ARM_MOV_MASK2 (DWORD)(0xFBF08F00) -#define ARM_MOVW 0xF2400000 -#define ARM_MOVT 0xF2C00000 - -#define HASH_KEY 13 -//===============================================================================================// -#ifdef _MSC_VER -#pragma intrinsic( _rotr ) -#define RDI_INLINE __forceinline -#else -#define RDI_INLINE static inline __attribute__((always_inline)) -#endif - -RDI_INLINE DWORD ror(DWORD d) -{ - return _rotr(d, HASH_KEY); -} - -RDI_INLINE DWORD hash(char* c) -{ - register DWORD h = 0; - do - { - h = ror(h); - h += *c; - } while (*++c); - - return h; -} -//===============================================================================================// -typedef struct _UNICODE_STR -{ - USHORT Length; - USHORT MaximumLength; - PWSTR pBuffer; -} UNICODE_STR, * PUNICODE_STR; - -// WinDbg> dt -v ntdll!_LDR_DATA_TABLE_ENTRY -//__declspec( align(8) ) -typedef struct _LDR_DATA_TABLE_ENTRY -{ - //LIST_ENTRY InLoadOrderLinks; // As we search from PPEB_LDR_DATA->InMemoryOrderModuleList we dont use the first entry. - LIST_ENTRY InMemoryOrderModuleList; - LIST_ENTRY InInitializationOrderModuleList; - PVOID DllBase; - PVOID EntryPoint; - ULONG SizeOfImage; - UNICODE_STR FullDllName; - UNICODE_STR BaseDllName; - ULONG Flags; - SHORT LoadCount; - SHORT TlsIndex; - LIST_ENTRY HashTableEntry; - ULONG TimeDateStamp; -} LDR_DATA_TABLE_ENTRY, * PLDR_DATA_TABLE_ENTRY; - -// WinDbg> dt -v ntdll!_PEB_LDR_DATA -typedef struct _PEB_LDR_DATA //, 7 elements, 0x28 bytes -{ - DWORD dwLength; - DWORD dwInitialized; - LPVOID lpSsHandle; - LIST_ENTRY InLoadOrderModuleList; - LIST_ENTRY InMemoryOrderModuleList; - LIST_ENTRY InInitializationOrderModuleList; - LPVOID lpEntryInProgress; -} PEB_LDR_DATA, * PPEB_LDR_DATA; - -// WinDbg> dt -v ntdll!_PEB_FREE_BLOCK -typedef struct _PEB_FREE_BLOCK // 2 elements, 0x8 bytes -{ - struct _PEB_FREE_BLOCK* pNext; - DWORD dwSize; -} PEB_FREE_BLOCK, * PPEB_FREE_BLOCK; - -// struct _PEB is defined in Winternl.h but it is incomplete -// WinDbg> dt -v ntdll!_PEB -typedef struct __PEB // 65 elements, 0x210 bytes -{ - BYTE bInheritedAddressSpace; - BYTE bReadImageFileExecOptions; - BYTE bBeingDebugged; - BYTE bSpareBool; - LPVOID lpMutant; - LPVOID lpImageBaseAddress; - PPEB_LDR_DATA pLdr; - LPVOID lpProcessParameters; - LPVOID lpSubSystemData; - LPVOID lpProcessHeap; - PRTL_CRITICAL_SECTION pFastPebLock; - LPVOID lpFastPebLockRoutine; - LPVOID lpFastPebUnlockRoutine; - DWORD dwEnvironmentUpdateCount; - LPVOID lpKernelCallbackTable; - DWORD dwSystemReserved; - DWORD dwAtlThunkSListPtr32; - PPEB_FREE_BLOCK pFreeList; - DWORD dwTlsExpansionCounter; - LPVOID lpTlsBitmap; - DWORD dwTlsBitmapBits[2]; - LPVOID lpReadOnlySharedMemoryBase; - LPVOID lpReadOnlySharedMemoryHeap; - LPVOID lpReadOnlyStaticServerData; - LPVOID lpAnsiCodePageData; - LPVOID lpOemCodePageData; - LPVOID lpUnicodeCaseTableData; - DWORD dwNumberOfProcessors; - DWORD dwNtGlobalFlag; - LARGE_INTEGER liCriticalSectionTimeout; - DWORD dwHeapSegmentReserve; - DWORD dwHeapSegmentCommit; - DWORD dwHeapDeCommitTotalFreeThreshold; - DWORD dwHeapDeCommitFreeBlockThreshold; - DWORD dwNumberOfHeaps; - DWORD dwMaximumNumberOfHeaps; - LPVOID lpProcessHeaps; - LPVOID lpGdiSharedHandleTable; - LPVOID lpProcessStarterHelper; - DWORD dwGdiDCAttributeList; - LPVOID lpLoaderLock; - DWORD dwOSMajorVersion; - DWORD dwOSMinorVersion; - WORD wOSBuildNumber; - WORD wOSCSDVersion; - DWORD dwOSPlatformId; - DWORD dwImageSubsystem; - DWORD dwImageSubsystemMajorVersion; - DWORD dwImageSubsystemMinorVersion; - DWORD dwImageProcessAffinityMask; - DWORD dwGdiHandleBuffer[34]; - LPVOID lpPostProcessInitRoutine; - LPVOID lpTlsExpansionBitmap; - DWORD dwTlsExpansionBitmapBits[32]; - DWORD dwSessionId; - ULARGE_INTEGER liAppCompatFlags; - ULARGE_INTEGER liAppCompatFlagsUser; - LPVOID lppShimData; - LPVOID lpAppCompatInfo; - UNICODE_STR usCSDVersion; - LPVOID lpActivationContextData; - LPVOID lpProcessAssemblyStorageMap; - LPVOID lpSystemDefaultActivationContextData; - LPVOID lpSystemAssemblyStorageMap; - DWORD dwMinimumStackCommit; -} _PEB, * _PPEB; - -typedef struct -{ - WORD offset : 12; - WORD type : 4; -} IMAGE_RELOC, * PIMAGE_RELOC; -//===============================================================================================// -#endif -//===============================================================================================// diff --git a/BackstageInjection-Rust/reflective/obfstr.h b/BackstageInjection-Rust/reflective/obfstr.h deleted file mode 100644 index 1f14dd5..0000000 --- a/BackstageInjection-Rust/reflective/obfstr.h +++ /dev/null @@ -1,57 +0,0 @@ -#pragma once - -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -#define OBFS_KEY 16 - -#define _OBF_CHR(c) ((unsigned char)(c) + OBFS_KEY) - -static inline char* obfs_dec(char* buf, int len) -{ - for (int i = 0; i < len; i++) - buf[i] = (char)((unsigned char)buf[i] - OBFS_KEY); - return buf; -} - -#ifdef _MSC_VER -#define OBFS(arr) \ - obfs_dec((char*)memcpy((char*)_alloca(sizeof(arr)), (const char*)(arr), sizeof(arr)), \ - (int)(sizeof(arr) - 1)) -#else -#define OBFS(arr) \ - obfs_dec((char*)memcpy((char[sizeof(arr)]){0}, (const char*)(arr), sizeof(arr)), \ - (int)(sizeof(arr) - 1)) -#endif - -// ---- Encrypted string arrays ---- -// Use _OBF_CHR() for each character so the compiler folds to constants. - -static const unsigned char _enc_ReflectiveLoader[] = { - _OBF_CHR('R'),_OBF_CHR('e'),_OBF_CHR('f'),_OBF_CHR('l'), - _OBF_CHR('e'),_OBF_CHR('c'),_OBF_CHR('t'),_OBF_CHR('i'), - _OBF_CHR('v'),_OBF_CHR('e'),_OBF_CHR('L'),_OBF_CHR('o'), - _OBF_CHR('a'),_OBF_CHR('d'),_OBF_CHR('e'),_OBF_CHR('r'), 0 -}; - -static const unsigned char _enc_ntdll_dll[] = { - _OBF_CHR('n'),_OBF_CHR('t'),_OBF_CHR('d'),_OBF_CHR('l'), - _OBF_CHR('l'),_OBF_CHR('.'),_OBF_CHR('d'),_OBF_CHR('l'), - _OBF_CHR('l'), 0 -}; - -static const unsigned char _enc_RtlAddFunctionTable[] = { - _OBF_CHR('R'),_OBF_CHR('t'),_OBF_CHR('l'),_OBF_CHR('A'), - _OBF_CHR('d'),_OBF_CHR('d'),_OBF_CHR('F'),_OBF_CHR('u'), - _OBF_CHR('n'),_OBF_CHR('c'),_OBF_CHR('t'),_OBF_CHR('i'), - _OBF_CHR('o'),_OBF_CHR('n'),_OBF_CHR('T'),_OBF_CHR('a'), - _OBF_CHR('b'),_OBF_CHR('l'),_OBF_CHR('e'), 0 -}; - -#ifdef __cplusplus -} -#endif \ No newline at end of file diff --git a/BackstageInjection-Rust/src/lib.rs b/BackstageInjection-Rust/src/lib.rs index 6d30150..260b4f9 100644 --- a/BackstageInjection-Rust/src/lib.rs +++ b/BackstageInjection-Rust/src/lib.rs @@ -10,8 +10,8 @@ //! from the shared-section bytes, or falls back to Injecting the module's //! own on-disk path via LoadLibraryW when no section is present. //! -//! The cdylib also exports `ReflectiveLoader` (compiled from the vendored C -//! loader in reflective/) so the initial injection itself can be reflective. +//! The cdylib also exports `ReflectiveLoader` (implemented in pure Rust in +//! `reflective.rs`) so the initial injection itself can be reflective. //! //! On DLL_PROCESS_DETACH all hooks are disabled best-effort. @@ -23,6 +23,7 @@ mod config; mod hooks; mod inject; mod log; +mod reflective; mod util; use core::ffi::c_void; diff --git a/BackstageInjection-Rust/src/reflective.rs b/BackstageInjection-Rust/src/reflective.rs new file mode 100644 index 0000000..9880d8d --- /dev/null +++ b/BackstageInjection-Rust/src/reflective.rs @@ -0,0 +1,544 @@ +//! Position-independent reflective loader, pure Rust (no C). +//! +//! This is a direct port of the Harmony Security `ReflectiveLoader` that was +//! previously compiled from `reflective/ReflectiveLoader.c`. The injection +//! stubs (`inject_reflective`) copy this DLL's raw bytes into a remote process +//! and start a thread on our exported `ReflectiveLoader`. When that thread +//! begins, the copied image is *not* relocated and its imports are *not* +//! resolved, so this function must be position independent end to end: +//! +//! - It never reads relocatable data. All image structures are reached by +//! computing addresses at runtime and reading with volatile scalar loads. +//! - It resolves `LoadLibraryA`, `GetProcAddress`, `VirtualAlloc`, +//! `NtFlushInstructionCache` and `RtlAddFunctionTable` by walking the PEB +//! module list and export tables by hand. Module whose names it needs are +//! matched by a rotate hash of *immediate* constants — never through +//! `.rodata` string literals, because a RIP-relative load into the +//! file-offset-mapped raw copy would read the wrong bytes until the image +//! has been relocated. +//! - It copies the image into a fresh RWX allocation, fixes up imports, +//! applies relocations, registers `.pdata` for exception unwinding and +//! finally invokes the DLL's entry point. +//! +//! All loops use `wrapping_*` arithmetic and every memory access is volatile +//! so the compiler cannot lower any access to a `memcpy`/`memset` libcall or +//! introduce a panic edge (both would route through a not-yet-loaded IAT or +//! unwinder). + +use core::arch::asm; + +const MEM_RESERVE_COMMIT: u32 = 0x0000_3000; +const PAGE_EXECUTE_READWRITE: u32 = 0x40; +const DLL_PROCESS_ATTACH: u32 = 1; + +// rotate-right-by-1 hashes of the names the loader resolves, derived with the +// exact hash below (`kernel32.dll`, `ntdll.dll`, export names). Pure immediates: +// keeping them out of `.rodata` is what keeps the loader relocation-free. +const KERNEL32_HASH: u32 = 0xC3A0_008F; +const NTDLL_HASH: u32 = 0xE600_0091; +const LOADLIBRARYA_HASH: u32 = 0x8DC0_0093; +const GETPROCADDRESS_HASH: u32 = 0x8708_00A0; +const VIRTUALALLOC_HASH: u32 = 0xB800_008F; +const NTFLUSH_HASH: u32 = 0xED3A_788A; + +type LoadLibraryFn = unsafe extern "system" fn(name: *const u8) -> usize; +type GetProcAddressFn = unsafe extern "system" fn(module: usize, name: *const u8) -> usize; +type VirtualAllocFn = unsafe extern "system" fn( + addr: usize, + size: usize, + allocation_type: u32, + protect: u32, +) -> usize; +type NtFlushFn = unsafe extern "system" fn(handle: isize, base: usize, len: usize) -> i32; +type RtlAddFunctionTableFn = unsafe extern "system" fn( + function_table: usize, + entry_count: u32, + base_address: u64, +) -> i32; +type DllMainFn = unsafe extern "system" fn(hinstance: usize, reason: u32, reserved: usize) -> i32; + +// ---- Volatile scalar memory access (never lowered to libcalls) ---- + +#[inline(always)] +unsafe fn rd_u8(p: usize, off: usize) -> u8 { + core::ptr::read_volatile((p + off) as *const u8) +} + +#[inline(always)] +unsafe fn rd_u16(p: usize, off: usize) -> u16 { + core::ptr::read_volatile((p + off) as *const u16) +} + +#[inline(always)] +unsafe fn rd_u32(p: usize, off: usize) -> u32 { + core::ptr::read_volatile((p + off) as *const u32) +} + +#[inline(always)] +unsafe fn rd_u64(p: usize, off: usize) -> u64 { + core::ptr::read_volatile((p + off) as *const u64) +} + +#[inline(always)] +unsafe fn wr_u8(p: usize, off: usize, v: u8) { + core::ptr::write_volatile((p + off) as *mut u8, v); +} + +#[inline(always)] +unsafe fn wr_u16(p: usize, off: usize, v: u16) { + core::ptr::write_volatile((p + off) as *mut u16, v); +} + +#[inline(always)] +unsafe fn wr_u32(p: usize, off: usize, v: u32) { + core::ptr::write_volatile((p + off) as *mut u32, v); +} + +#[inline(always)] +unsafe fn wr_u64(p: usize, off: usize, v: u64) { + core::ptr::write_volatile((p + off) as *mut u64, v); +} + +// Unaligned-safe read so the base scan can step byte-by-byte. +#[inline(always)] +unsafe fn rd_u16_bytes(p: usize) -> u16 { + rd_u8(p, 0) as u16 | ((rd_u8(p, 1) as u16) << 8) +} + +#[inline(always)] +unsafe fn copy_bytes(dst: usize, src: usize, len: usize) { + for i in 0..len { + core::ptr::write_volatile((dst + i) as *mut u8, core::ptr::read_volatile((src + i) as *const u8)); + } +} + +// ---- Immediate materializers: build byte strings without .rodata ---- + +#[inline(always)] +unsafe fn fill_u64(dst: usize, lit: u64) { + wr_u8(dst, 0, (lit & 0xFF) as u8); + wr_u8(dst, 1, ((lit >> 8) & 0xFF) as u8); + wr_u8(dst, 2, ((lit >> 16) & 0xFF) as u8); + wr_u8(dst, 3, ((lit >> 24) & 0xFF) as u8); + wr_u8(dst, 4, ((lit >> 32) & 0xFF) as u8); + wr_u8(dst, 5, ((lit >> 40) & 0xFF) as u8); + wr_u8(dst, 6, ((lit >> 48) & 0xFF) as u8); + wr_u8(dst, 7, ((lit >> 56) & 0xFF) as u8); +} + +#[inline(always)] +unsafe fn fill_u32(dst: usize, lit: u32) { + wr_u8(dst, 0, (lit & 0xFF) as u8); + wr_u8(dst, 1, ((lit >> 8) & 0xFF) as u8); + wr_u8(dst, 2, ((lit >> 16) & 0xFF) as u8); + wr_u8(dst, 3, ((lit >> 24) & 0xFF) as u8); +} + +// ---- Position-independent runtime resolution ---- + +/// Current instruction pointer, obtained with a RIP-relative LEA so it is +/// valid before the image is relocated. +#[inline(never)] +fn rip_here() -> usize { + let ip: usize; + unsafe { + asm!( + "lea {}, [rip]", + out(reg) ip, + options(nomem, nostack, preserves_flags), + ); + } + ip +} + +/// x64 Process Environment Block via `gs:[0x60]`. +#[inline(never)] +unsafe fn peb_pointer() -> usize { + let peb: usize; + unsafe { + asm!( + "mov {}, qword ptr gs:[0x60]", + out(reg) peb, + options(nostack, preserves_flags), + ); + } + peb +} + +/// Scan backwards from `start` for the MZ/PE header of the running image. +/// Equivalent to the C loader's `caller()` walk. +unsafe fn find_image_base(start: usize) -> usize { + let mut p = start; + loop { + if p == 0 { + return 0; + } + if rd_u16_bytes(p) == 0x5A4D { + let lfanew = rd_u32(p, 0x3C) as usize; + if (0x40..1024).contains(&lfanew) { + let nt = p + lfanew; + if rd_u32(nt, 0) == 0x0000_4550 { + return p; + } + } + } + p = p.wrapping_sub(1); + } +} + +/// Rotate `v` right by one bit. +#[inline(always)] +fn ror1(v: u32) -> u32 { + v.wrapping_shr(1) | v.wrapping_shl(31) +} + +/// ror hash of a UTF-16 code-unit buffer (case-normalized as in the C loader: +/// `a`..`z` map to `A`..`Z`, everything else keeps its value). +unsafe fn hash_wide(ptr: usize, nchars: usize) -> u32 { + let mut h: u32 = 0; + let mut i = 0; + while i < nchars { + let c = rd_u16(ptr, i * 2); + h = ror1(h); + if (0x61..=0x7A).contains(&c) { + h = h.wrapping_add((c - 0x20) as u32); + } else { + h = h.wrapping_add(c as u32); + } + i += 1; + } + h +} + +/// ror hash of a NUL-terminated ASCII string, case-normalized as above. +unsafe fn hash_ascii(ptr: usize) -> u32 { + let mut h: u32 = 0; + let mut i = 0; + loop { + let c = rd_u8(ptr, i) as u32; + if c == 0 { + return h; + } + h = ror1(h); + if (0x61..=0x7A).contains(&c) { + h = h.wrapping_add(c - 0x20); + } else { + h = h.wrapping_add(c); + } + i += 1; + } +} + +/// Walk the loaded-module list for the module whose base-name rotates to +/// `want`; returns its base address or 0. +unsafe fn module_base_by_hash(peb: usize, want: u32) -> usize { + let ldr = rd_u64(peb, 0x18) as usize; + if ldr == 0 { + return 0; + } + let head = rd_u64(ldr, 0x20) as usize; + if head == 0 { + return 0; + } + let mut cur = head; + loop { + if cur == 0 { + return 0; + } + let entry = cur.wrapping_sub(0x10); + let name_len = rd_u16(entry, 0x58) as usize; + if name_len > 0 { + let name_ptr = rd_u64(entry, 0x60) as usize; + if name_ptr != 0 && hash_wide(name_ptr, name_len / 2) == want { + return rd_u64(entry, 0x30) as usize; + } + } + let next = rd_u64(entry, 0x10) as usize; + if next == head || next == cur { + break; + } + cur = next; + } + 0 +} + +/// Resolve an export of `base` by its ror-hashed name; returns its VA or 0. +unsafe fn export_by_hash(base: usize, want: u32) -> usize { + let lfanew = rd_u32(base, 0x3C) as usize; + let dd = base + lfanew + 4 + 20 + 112; + let ed_rva = rd_u32(dd, 0) as usize; + if ed_rva == 0 { + return 0; + } + let ed = base + ed_rva; + let num_names = rd_u32(ed, 24) as usize; + let addr_of_funcs = rd_u32(ed, 28) as usize; + let addr_of_names = rd_u32(ed, 32) as usize; + let addr_of_ord = rd_u32(ed, 36) as usize; + if addr_of_funcs == 0 || addr_of_names == 0 || addr_of_ord == 0 { + return 0; + } + for i in 0..num_names { + let name_rva = rd_u32(base + addr_of_names, i * 4) as usize; + if hash_ascii(base + name_rva) == want { + let ordinal = rd_u16(base + addr_of_ord, i * 2) as usize; + let fn_rva = rd_u32(base + addr_of_funcs, ordinal * 4) as usize; + if fn_rva == 0 { + return 0; + } + return base + fn_rva; + } + } + 0 +} + +/// Resolve an export by ordinal; returns its VA or 0. +unsafe fn export_by_ordinal(base: usize, ordinal: u16) -> usize { + let lfanew = rd_u32(base, 0x3C) as usize; + let dd = base + lfanew + 4 + 20 + 112; + let ed_rva = rd_u32(dd, 0) as usize; + if ed_rva == 0 { + return 0; + } + let ed = base + ed_rva; + let export_base = rd_u32(ed, 16) as usize; + let num_funcs = rd_u32(ed, 20) as usize; + let addr_of_funcs = rd_u32(ed, 28) as usize; + if ordinal < export_base as u16 || addr_of_funcs == 0 { + return 0; + } + let idx = ordinal as usize - export_base; + if idx >= num_funcs { + return 0; + } + let fn_rva = rd_u32(base + addr_of_funcs, idx * 4) as usize; + if fn_rva == 0 { + return 0; + } + base + fn_rva +} + +// ---- The loader ---- + +/// Thread-start routine invoked by `inject_reflective` on the copied, +/// un-relocated image. Returns the address of the newly loaded DLL's entry +/// point, or 0 on failure. +#[unsafe(no_mangle)] +pub extern "system" fn ReflectiveLoader(lpParameter: usize) -> usize { + unsafe { + // STEP 0: locate our own (un-relocated) image base. + let ui_lib = find_image_base(rip_here()); + if ui_lib == 0 { + return 0; + } + + // STEP 1: resolve the APIs we need by name hash. + let peb = peb_pointer(); + let k32 = module_base_by_hash(peb, KERNEL32_HASH); + let ntdll = module_base_by_hash(peb, NTDLL_HASH); + if k32 == 0 || ntdll == 0 { + return 0; + } + let p_load = export_by_hash(k32, LOADLIBRARYA_HASH); + let p_get_proc = export_by_hash(k32, GETPROCADDRESS_HASH); + let p_alloc = export_by_hash(k32, VIRTUALALLOC_HASH); + let p_flush = export_by_hash(ntdll, NTFLUSH_HASH); + if p_load == 0 || p_get_proc == 0 || p_alloc == 0 { + return 0; + } + let f_load: LoadLibraryFn = core::mem::transmute(p_load); + let f_get_proc: GetProcAddressFn = core::mem::transmute(p_get_proc); + let f_alloc: VirtualAllocFn = core::mem::transmute(p_alloc); + let f_flush: NtFlushFn = core::mem::transmute(p_flush); + + // Register .pdata so unwinding through our hooks does not crash. + // We already have ntdll's base from the module walk; the proc-name + // string is materialized from immediates so no .rodata access is made. + let mut name_space = core::mem::MaybeUninit::<[u8; 20]>::uninit(); + let name_ptr = name_space.as_mut_ptr() as *mut u8 as usize; + fill_u64(name_ptr, 0x7546_6464_416C_7452); // "RtlAddFu" + fill_u64(name_ptr + 8, 0x6154_6E6F_6974_636E); // "nctionTa" + fill_u32(name_ptr + 16, 0x0065_6C62); // "ble\0" + let p_add_table = f_get_proc(ntdll, name_ptr as *const u8); + + // STEP 2: load the image into a fresh permanent location. + let lfanew = rd_u32(ui_lib, 0x3C) as usize; + if lfanew == 0 { + return 0; + } + let opt = ui_lib + lfanew + 4 + 20; + let image_base = rd_u64(opt, 24) as usize; + let size_of_image = rd_u32(opt, 56) as usize; + let ui_base = f_alloc(0, size_of_image, MEM_RESERVE_COMMIT, PAGE_EXECUTE_READWRITE); + if ui_base == 0 { + return 0; + } + + // Copy the headers. + copy_bytes(ui_base, ui_lib, rd_u32(opt, 60) as usize); + + // STEP 3: copy all sections. + let coff = ui_lib + lfanew + 4; + let num_sections = rd_u16(coff, 2) as usize; + let opt_size = rd_u16(coff, 16) as usize; + let sec = coff + 20 + opt_size; + let mut si = 0; + while si < num_sections { + let s = sec + si * 40; + let vaddr = rd_u32(s, 12) as usize; + let raw_size = rd_u32(s, 16) as usize; + let raw_ptr = rd_u32(s, 20) as usize; + copy_bytes(ui_base + vaddr, ui_lib + raw_ptr, raw_size); + si += 1; + } + + // STEP 4: fix up imports. + let dd = opt + 112; + let imp_rva = rd_u32(dd, 8) as usize; + if imp_rva != 0 { + let imp = ui_base + imp_rva; + let mut di = 0; + loop { + let desc = imp + di * 20; + let name_rva = rd_u32(desc, 12); + if name_rva == 0 { + break; + } + let hlib = f_load((ui_base + name_rva as usize) as *const u8); + let oft_rva = rd_u32(desc, 0) as usize; + let ft_rva = rd_u32(desc, 16) as usize; + let mut iat = ui_base + ft_rva; + let mut oft = if oft_rva != 0 { ui_base + oft_rva } else { 0 }; + loop { + let thunk = rd_u64(iat, 0); + if thunk == 0 { + break; + } + if oft != 0 && (thunk >> 63) == 1 { + let ordinal = (thunk & 0xFFFF) as u16; + wr_u64(iat, 0, export_by_ordinal(hlib, ordinal) as u64); + } else { + let name_rva2 = (thunk as u32) as usize; + let by_name = ui_base + name_rva2; + wr_u64(iat, 0, f_get_proc(hlib, (by_name + 2) as *const u8) as u64); + } + iat += 8; + if oft != 0 { + oft += 8; + } + } + di += 1; + } + } + + // STEP 5: apply relocations. + let reloc_dd = dd + 0x28; + let reloc_size = rd_u32(reloc_dd, 4); + if reloc_size != 0 { + let reloc_rva = rd_u32(reloc_dd, 0) as usize; + let delta = ui_base.wrapping_sub(image_base); + let mut r = ui_base + reloc_rva; + loop { + let block_size = rd_u32(r, 4); + if block_size == 0 { + break; + } + let target = ui_base + rd_u32(r, 0) as usize; + let mut count = (block_size as usize - 8) / 2; + let mut e = r + 8; + while count > 0 { + let word = rd_u16(e, 0) as usize; + let typ = (word >> 12) & 0xF; + let off = word & 0xFFF; + // Only DIR64 (10) needs applying; ABSOLUTE (0) is a no-op + // and HIGHLOW/HIGH/LOW never occur in an x64 GNU build. + // A single comparison is used deliberately: a multi-case + // dispatch lets LLVM emit a jump table in `.rodata`, whose + // RIP-relative address would be wrong in the raw copy. + if typ == 10 { + let v = rd_u64(target, off).wrapping_add(delta as u64); + wr_u64(target, off, v); + } + e += 2; + count -= 1; + } + r = r + block_size as usize; + } + } + + // STEP 5b: register the exception table (.pdata) with the OS. + let exc_dd = dd + 0x30; + let exc_rva = rd_u32(exc_dd, 0) as usize; + let exc_size = rd_u32(exc_dd, 4) as usize; + if p_add_table != 0 && exc_rva != 0 && exc_size != 0 { + let f_add: RtlAddFunctionTableFn = core::mem::transmute(p_add_table); + let _ = f_add(ui_base + exc_rva, (exc_size / 12) as u32, ui_base as u64); + } + + // Flush the instruction cache so relocated code is used. + let _ = f_flush(-1, 0, 0); + + // STEP 6: invoke the DLL entry point and return its address. + let entry = ui_base + rd_u32(opt, 16) as usize; + let f_entry: DllMainFn = core::mem::transmute(entry); + let _ = f_entry(ui_base, DLL_PROCESS_ATTACH, lpParameter); + entry + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn hash_matches_derived_constants() { + unsafe { + assert_eq!(hash_ascii(b"kernel32.dll\0".as_ptr() as usize), KERNEL32_HASH); + assert_eq!(hash_ascii(b"ntdll.dll\0".as_ptr() as usize), NTDLL_HASH); + assert_eq!(hash_ascii(b"LoadLibraryA\0".as_ptr() as usize), LOADLIBRARYA_HASH); + assert_eq!(hash_ascii(b"GetProcAddress\0".as_ptr() as usize), GETPROCADDRESS_HASH); + assert_eq!(hash_ascii(b"VirtualAlloc\0".as_ptr() as usize), VIRTUALALLOC_HASH); + assert_eq!( + hash_ascii(b"NtFlushInstructionCache\0".as_ptr() as usize), + NTFLUSH_HASH + ); + } + assert_eq!(ror1(0x0000_0001), 0x8000_0000); + assert_eq!(ror1(0x8000_0000), 0x4000_0000); + } + + #[test] + fn peb_walk_finds_system_modules() { + unsafe { + let peb = peb_pointer(); + assert_ne!(peb, 0, "PEB must be readable from gs:[0x60]"); + assert_ne!(module_base_by_hash(peb, KERNEL32_HASH), 0, "kernel32.dll"); + assert_ne!(module_base_by_hash(peb, NTDLL_HASH), 0, "ntdll.dll"); + } + } + + #[test] + fn base_scan_finds_current_image() { + unsafe { + let ip = rip_here(); + let base = find_image_base(ip); + assert_ne!(base, 0, "could not find our own image base"); + assert_eq!(rd_u16_bytes(base), 0x5A4D, "base must start with MZ"); + } + } + + #[test] + fn exports_resolve_by_hash() { + unsafe { + let peb = peb_pointer(); + let k32 = module_base_by_hash(peb, KERNEL32_HASH); + let ntdll = module_base_by_hash(peb, NTDLL_HASH); + assert_ne!(k32, 0); + assert_ne!(ntdll, 0); + assert_ne!(export_by_hash(k32, LOADLIBRARYA_HASH), 0); + assert_ne!(export_by_hash(k32, GETPROCADDRESS_HASH), 0); + assert_ne!(export_by_hash(k32, VIRTUALALLOC_HASH), 0); + assert_ne!(export_by_hash(ntdll, NTFLUSH_HASH), 0); + } + } +} \ No newline at end of file diff --git a/scripts/build-backstage-dll.bat b/scripts/build-backstage-dll.bat index f2811a6..5dd504b 100644 --- a/scripts/build-backstage-dll.bat +++ b/scripts/build-backstage-dll.bat @@ -3,9 +3,9 @@ setlocal EnableDelayedExpansion REM Build the BackstageInjection DLL for Windows x64 using cargo (GNU target). REM The GNU toolchain is required: the reflective loader manually maps the image REM without the MSVC CRT bootstrapping, and the MSVC CRT's TLS/CFG machinery -REM fast-fails (0xC0000409) under a manual map. See -REM BackstageInjection-Rust/build.rs and protocol/README.md. Requires the -REM x86_64-pc-windows-gnu target and a mingw-w64 gcc for the cc crate. +REM fast-fails (0xC0000409) under a manual map. The loader itself is pure Rust +REM (BackstageInjection-Rust/src/reflective.rs). Requires the +REM x86_64-pc-windows-gnu target. set "SCRIPT_DIR=%~dp0" for %%I in ("%SCRIPT_DIR%..") do set "ROOT=%%~fI\"