full rust works (also AI).

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