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" name = "backstage-injection-rs"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"cc",
"minhook", "minhook",
] ]
-3
View File
@@ -10,9 +10,6 @@ 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
@@ -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 //! from the shared-section bytes, or falls back to Injecting the module's
//! own on-disk path via LoadLibraryW when no section is present. //! own on-disk path via LoadLibraryW when no section is present.
//! //!
//! The cdylib also exports `ReflectiveLoader` (compiled from the vendored C //! The cdylib also exports `ReflectiveLoader` (implemented in pure Rust in
//! loader in reflective/) so the initial injection itself can be reflective. //! `reflective.rs`) 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.
@@ -23,6 +23,7 @@ mod config;
mod hooks; mod hooks;
mod inject; mod inject;
mod log; mod log;
mod reflective;
mod util; mod util;
use core::ffi::c_void; 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 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 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 without the MSVC CRT bootstrapping, and the MSVC CRT's TLS/CFG machinery
REM fast-fails (0xC0000409) under a manual map. See REM fast-fails (0xC0000409) under a manual map. The loader itself is pure Rust
REM BackstageInjection-Rust/build.rs and protocol/README.md. Requires the REM (BackstageInjection-Rust/src/reflective.rs). Requires the
REM x86_64-pc-windows-gnu target and a mingw-w64 gcc for the cc crate. REM x86_64-pc-windows-gnu target.
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\"