Repository Notice: This GitHub repository is dedicated exclusively to the open-source codebase, tracking commits, code review, pull requests, and technical issue/bug reports. Precompiled binary releases, setup tutorials, usage discussion, and community support are hosted on the forum: hackvshack.net (Official Release Thread: BlackBox - Advanced Windows DLL Injector).
Native Windows DLL injection and diagnostic tool written in pure Rust. Supports native x64 and 32-bit WOW64 processes with 6 injection methods, in-memory cloaking, automated crash diagnostics, and scriptable CLI automation.
- Windows 10 / 11 (x64)
- Administrator privileges (for
SeDebugPrivilegeand cross-process handle acquisition) - Rust 1.70+ and MSVC C++ Build Tools (only required to compile from source)
- Internet access for the first static-TLS manual map, or a pre-populated
%TEMP%\BlackBoxInjector\symbolsPDB cache; the matching ntdll PDB is reused afterwards.
Launch BlackBoxInjector.exe with no arguments:
- Accept the UAC prompt (requests
requireAdministratorvia embedded manifest). - Click Select Process to filter running processes by name or PID (auto-detects x86 / x64 architecture).
- Click + Add DLL to queue one or multiple modules.
- Configure execution order, delays, and methods directly in the queue table.
- Click Inject to dispatch the queue.
BlackBox automatically detects command-line flags and attaches directly to the calling parent console:
# Print every command and option
BlackBoxInjector.exe --help
# List every running process (PID, architecture, name)
BlackBoxInjector.exe --ps
# Inject queue into target by process name
BlackBoxInjector.exe --proc target.exe --inject
# Inject using Manual Map (method 2) into a specific PID
BlackBoxInjector.exe --pid 1337 --method 2 --inject
# Add a module to the persistent registry queue with a 500ms delay
BlackBoxInjector.exe --add C:\modules\hook.dll --delay 500
# Inspect active injection queue
BlackBoxInjector.exe --list
# Validate the queue against a target before injecting
BlackBoxInjector.exe --proc target.exe --validate- Allocates memory for the DLL path in the target process via
VirtualAllocEx(PAGE_READWRITE). - Writes the UTF-16 path via
WriteProcessMemory. - Resolves
kernel32.dll!LoadLibraryWfrom the target's export table and spawns execution viaCreateRemoteThread. - Captures the returned HMODULE through a remote stub and verifies the PE at that base, so a DLL that unlinks itself from the PEB is still reported correctly with its real base.
- Bypasses user-mode hooks placed on
kernel32!LoadLibraryWby interacting directly with the native NT loader. - Formats a remote
UNICODE_STRINGbuffer pointing to the module path. - Spawns a remote thread invoking
ntdll.dll!LdrLoadDllto load the DLL directly into the process loader list.
- Fully maps PE32 (x86) and PE32+ (x64) binaries in memory without calling the Windows module loader:
- Validation first:
SizeOfImage, every section's raw range, the entry point and the import/TLS tables are bounds-checked before a single byte reaches the target. A malformed file is a clean error, never a crash. - Memory Allocation: Computes
SizeOfImageand allocates target virtual memory withPAGE_EXECUTE_READWRITE. - Section Mapping: Copies headers and iterates
IMAGE_SECTION_HEADERentries, copying section raw data to their respective virtual addresses. Sections with no raw data are skipped, never silently truncated. - Base Relocations: Traverses
IMAGE_DIRECTORY_ENTRY_BASERELOC, applying delta fixups (IMAGE_REL_BASED_DIR64/HIGHLOW) locally in memory before writing. - Import Resolution: Parses
IMAGE_DIRECTORY_ENTRY_IMPORT, walks Import Address Tables (IAT), recursively resolves forwarded exports (ntdll.RtlXxx), and maps dependencies via remote export directory inspection and PEBApiSetMaplookup (api-ms-win-*-> host module). Imports are always resolved against the target's export tables. - Protections: Each section receives its final protection (RX/RW/RO/NOACCESS) before any of the image runs; the header page ends up read-only.
- Exception data (x64): The image's
.pdatais registered withRtlAddFunctionTable, and the image is also inserted intontdll!KiUserInvertedFunctionTable(the tableRtlPcToFileHeaderand the MSVC C++ runtime read). That makes both__try/__exceptand C++throw/catchwork inside a manually mapped module - without it the C++ runtime cannot resolve its FuncInfo RVAs and athrowterminates the target. - TLS: The mapped image's TLS directory is registered with the loader's own TLS subsystem: a synthetic
LDR_DATA_TABLE_ENTRYis passed tontdll!LdrpHandleTlsDataunder the loader lock. The loader assigns a real_tls_index, inserts anLDRP_TLS_ENTRYintoLdrpTlsList, writesAddressOfIndex, and fills blocks for existing threads throughNtSetInformationProcess(ProcessTlsInformation); new threads get blocks throughLdrpInitializeThread/LdrpAllocateTls. No image file is opened, soNtOpenFile-based image filters are bypassed. The internal function is resolved by name from the matching ntdll PDB, downloaded once to%TEMP%\BlackBoxInjector\symbolsand reused. If the PDB is neither cached nor downloadable, the injection fails with an explicit error through the normal failure dialog - there is no seed fallback. - Entry Point: Dispatches the module's
DllMain(DLL_PROCESS_ATTACH)via a native remote stub.AddressOfEntryPoint == 0(resource-only DLL) is skipped, and aFALSEreturn is treated as a failed load (the image is freed again).
- Validation first:
- Enumerates candidate threads via
NtQueryInformationThread, preferring ones started in the main executable, and tries them in order. - Suspends a candidate, captures its context via
GetThreadContext, redirects the instruction pointer to a loader stub and resumes it. - The stub executes
LoadLibraryW, sets a completion flag and spins (both bitnesses); the injector then suspends the thread and restores the saved context before freeing the stub. That is what keeps the target from replaying 100+ ms of application code. - A thread parked in a kernel wait never runs the stub: its context is restored and the next candidate is tried. If a timeout happens while the thread is already inside
LoadLibrary, the thread is left untouched and a hard error is reported instead of abandoning the loader lock.
- Invokes undocumented native syscall
ntdll!NtCreateThreadEx. - Passes the
THREAD_CREATE_FLAGS_HIDE_FROM_DEBUGGERcreation flag to shield injection threads from attached debuggers and instrumentation engines. - Uses the same HMODULE capture and PE verification as Standard, so a self-unlinking DLL is not reported as a failed load.
- Threadless injection: enumerates existing threads in the target process.
- Queues a call to
LoadLibraryWwith the remote path pointer usingQueueUserAPC. - Payload executes when the target thread enters an alertable wait state (
SleepEx,WaitForSingleObjectEx).
Legend: ✅ supported and verified |
| Capability | 0 Standard |
1 LdrLoadDll |
2 Manual Map |
3 Hijack |
4 NtCreateThreadEx |
5 APC |
|---|---|---|---|---|---|---|
| x64 target | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| 32-bit (WOW64) target | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ ¹ |
Static TLS (__declspec(thread)) |
✅ | ✅ | ✅ ² | ✅ | ✅ | ✅ |
TLS callbacks (.CRT$XLB) |
✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
SEH (__try / __except) inside the DLL |
✅ | ✅ | ✅ | ✅ | ✅ | |
C++ exceptions (throw / catch) |
✅ | ✅ | ✅ | ✅ | ✅ | |
| Delay-loaded imports | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
API-set imports (api-ms-win-*) |
✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| Forwarded exports | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
CFG-instrumented DLL (/guard:cf) |
✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
No entry point (/NOENTRY, resource-only) |
✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
DllMain returns FALSE -> reported as a failed load |
✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
Slow DllMain (> 5 s) |
✅ | ✅ | ✅ ⁴ | ✅ | ✅ | |
Forwarded imports (kernel32!InitOnceExecuteOnce -> api-ms-win-core-*) |
✅ | ✅ | ✅ ¹² | ✅ | ✅ | ✅ |
| Two DLLs with static TLS manual-mapped in one process | ✅ | ✅ | ✅ ¹³ | ✅ | ✅ | ✅ |
Manual map on CS2: the mapped image itself never enters the loader's module lists. For static TLS, current builds use a second, loader-internal path: the mapped image's TLS directory is registered directly with the loader through a synthetic
LDR_DATA_TABLE_ENTRYandLdrpHandleTlsData. That call opens no image file, so it does not touch the Trusted ModeNtOpenFilegate that blocked the old seed module. This path is verified against a target withNtOpenFileandLoadLibraryExWblocked; live VAC verification is still required.
| Option | 0 Standard |
1 LdrLoadDll |
2 Manual Map |
3 Hijack |
4 NtCreateThreadEx |
5 APC |
|---|---|---|---|---|---|---|
| Erase PE headers | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| Hide module (PEB unlink + name blank) | ✅ | ✅ | ➖ ⁶ | ✅ | ✅ | ✅ |
| ThreadHideFromDebugger | ✅ | ✅ | ✅ ⁷ | ✅ ⁸ | ✅ | ➖ ⁹ |
| Stealth inject (erase + hide + hide thread) | ✅ | ✅ | ✅ | ✅ | ✅ | |
| Unload / eject | ✅ | ✅ | ❌ ¹⁰ | ✅ | ✅ | ✅ |
| PE Scrambler (13 options) | ✅ | ✅ | ✅ | ✅ ¹¹ | ✅ | ✅ |
| Queue options (delay, per-file method, conditions, retries) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
- APC on WOW64 is not possible: A 64-bit process cannot queue a 32-bit routine onto a WOW64 thread; the 32-bit APC dispatcher faults (verified). 32-bit targets receive a clean error instead of a crashing target - use methods 0-4. APC also requires the target thread to enter an alertable wait state (
SleepEx,WaitForSingleObjectEx). - Manual Map + Static TLS (
__declspec(thread)): On x64 the injector registers the mapped image's TLS directory with the loader directly. It builds a syntheticLDR_DATA_TABLE_ENTRYfor the image, takes the loader lock, and calls the internalLdrpHandleTlsData. The loader allocates a real static index, inserts anLDRP_TLS_ENTRYintoLdrpTlsList, writesAddressOfIndex, and installs the block for every existing thread throughNtSetInformationProcess(ProcessTlsInformation). Future threads allocate a block throughLdrpInitializeThread->LdrpAllocateTls. No image file is opened and noLoadLibraryW/LdrLoadDllcall is made, so filters that inspect file opens (CS2 Trusted Mode'sNtOpenFile) are not invoked.LdrpHandleTlsDatais resolved by name from the matching ntdll PDB, downloaded once from the Microsoft symbol server and cached in%TEMP%\BlackBoxInjector\symbols(LOCALAPPDATAand the injector directory are fallbacks), so it covers Windows 10 and 11 ntdll builds. If the PDB is neither cached nor downloadable, the injection fails withTLS-PDB-01through the normal failure dialog; there is no seed fallback. The synthetic LDR entry is not added to the PEB module list, so the payload stays unlisted; the trade-off is that the loader's thread-init loop does not deliver the payload'sDLL_THREAD_ATTACH/DLL_THREAD_DETACHcallbacks (orDllMainthread attach) on future threads, although the TLS data block itself is allocated and valid (verified with a worker thread readingthread_local). On 32-bit targets manual map still refuses static TLS. - Manual Map + Exceptions (x64 vs WOW64): On x64, SEH and C++ exceptions work:
.pdatais registered viaRtlAddFunctionTableand the image is added tontdll!KiUserInvertedFunctionTable(used byRtlPcToFileHeaderand the MSVC C++ runtime). On WOW64, the 32-bit dispatcher only accepts exception handlers from loader-registered modules; manual map refuses 32-bit images with exception directories. Use loader methods (0, 1, or 4) for 32-bit DLLs using exceptions. - Slow
DllMain(> 60 s): ADllMainexceeding the 60 s wait is reported as a failure and the mapped image is kept in target memory - it is never freed under a thread that may still be running inside it. - Hijack Stub Timeout (10 s): Hijack waits 10 s for its stub. If
DllMainblocks longer than that, context cannot be restored without risking loader lock deadlock; the thread is left untouched, a hard error is returned, and the target must be restarted. - HideModule PEB Unlinking: Manually mapped modules are not registered in PEB loader lists, so
HideModulehas nothing to unlink (ErasePEstill applies). For loader methods,HideModuleunlinksInLoadOrderModuleList,InMemoryOrderModuleList, andInInitializationOrderModuleListand blanks module names (defeatingEnumProcessModulesand name walks); the loader internal hash table still contains the entry. - Debugger Masking (ThreadHide): The injector's initialization threads (including TLS callback threads) are created with
THREAD_CREATE_FLAGS_HIDE_FROM_DEBUGGER. - Hijack Thread Debugger Masking: Applied to the hijacked thread via
NtSetInformationThread(ThreadHideFromDebugger)once original execution context is restored. - APC Thread Masking: APC queues onto existing target threads and creates no thread of its own, so thread hiding is not applicable.
- Unload / Eject: Manually mapped modules register no OS loader bookkeeping, so
FreeLibrarycannot eject the image; BlackBox reports this directly instead of failing silently. - PE Scrambler: Hijack loads the temporary scrambled copy.
ShiftSectionMemoryis intentionally bypassed with a clear warning because section memory shifting requires complete PE RVA rebasing. - Forwarded imports: manual map resolves API-set contracts through the target's
ApiSetMap(schema v6: 28-byte header,EntryOffset0x1C, name compared up to the last hyphen because the schema stores revision-l1-2-1while binaries reference-l1-2-0, host DLL taken from_API_SET_VALUE_ENTRY_V6.ValueOffset). Verified in live CS2 withkernel32!InitOnceExecuteOnce,InitOnceBeginInitializeandSleepConditionVariableCS. - Multiple manual-mapped DLLs with static TLS: every map registers its own loader TLS entry, so each manually mapped module receives a distinct loader-assigned
_tls_index(verified: two loader-registered TLS DLLs mapped into one process got indices 8 and 9 and both kept working). Loader methods are unaffected. - CS2 Trusted Mode and static TLS:
cs2.exereadscsgo.signatures,steam.signatures,system.signaturesandforeign.signatures, then installs inline hooks onntdll!NtOpenFile,kernelbase!LoadLibraryExWanduser32!PeekMessageWfor the whole game session. TheNtOpenFilehook verifies image opens that requestFILE_EXECUTE | FILE_READ_DATA(the mask the native loader uses) and returnsSTATUS_OBJECT_NAME_NOT_FOUNDfor files that are not in the manifests. The loader-registration path never opens an image file: it callsLdrpHandleTlsDataon the already-mapped image, so the gate is bypassed. It still adds loader-internal TLS state, so live VAC behaviour must be verified separately.-allow_third_party_softwarestill installs the same hooks; only signed/allowlisted modules pass.-insecureskips the hooks but also disables VAC-secure play. - CS2 verification history: the earlier live CS2 TLS-seed results predate the current
NtOpenFilegate and are no longer applicable. The seed fallback was removed; the loader-registration path was verified against a target withNtOpenFileandLoadLibraryExWblocked, not against a live VAC-secured CS2 session. - User/environment errors: a target that is no longer running reports
E2003 TARGET_NOT_RUNNING, and a missing/undownloadable ntdll PDB reportsTLS-PDB-01. Both use the normal error dialog and do not write a failure report - they are user/environment problems, not injector defects.
Verified on Windows 10 IoT Enterprise LTSC 2024 (build 26100.7462) x64 with WOW64, against a purpose-built matrix of test DLLs (plain, TLS variables + callbacks, a DLL whose DllMain spawns a worker thread that reads thread_local, SEH + C++ exceptions, static CRT/STL, delay-load, API-set imports, CFG, /NOENTRY, DllMain FALSE, 8 s and 12 s DllMain, crash-on-attach) and both 64-bit and 32-bit targets. The loader-registration TLS path was verified with a target whose ntdll!NtOpenFile was blocked, a target whose NtOpenFile and LoadLibraryExW were both blocked, two TLS DLLs receiving indices 8 and 9, a pre-existing worker thread reading thread_local through an APC, and a payload-created thread reading it normally. The earlier live CS2 TLS-seed results predate the current NtOpenFile gate; see notes 14-15.
- PE Header Zeroing (
ErasePE): Makes the header page writable withVirtualProtectEx, zeroes the DOS signature (MZ), NT headers and section table, restores the protection and verifies the write. Works for loader-mapped and manually mapped modules. - PEB Loader Unlinking (
HideModule): Traverses the remote Process Environment Block (PEB->Ldr), unlinks the module'sLDR_DATA_TABLE_ENTRYfrom all three doubly-linked lists (InLoadOrderModuleList,InMemoryOrderModuleList,InInitializationOrderModuleList) and blanks its name lengths so name-based lookups miss it. The loader's internal hash table still references the entry, so this defeats enumeration (EnumProcessModules, module walks), not a direct hash-table walk. - Debugger Masking (
ThreadHide): PassesTHREAD_CREATE_FLAGS_HIDE_FROM_DEBUGGERto new threads and callsNtSetInformationThread(ThreadHideFromDebugger)on the hijacked thread (hijacking creates no new thread of its own). - PE Scrambler Engine: Applies local binary transformations before memory transfer (section names, header fields, timestamps, debug data, import order, padding bytes, decoy sections, entry trampoline), keeping the result loadable through both the loader and the manual mapper.
ShiftSectionMemoryneeds a full PE rebase, so it is skipped with an explicit warning instead of producing a corrupt image.
Example: Injecting a 64-bit module (AyywareCS2.dll) into Counter-Strike 2 (cs2.exe) generates an automated failure report via Manual Map because an API-set forwarder dependency (api-ms-win-core-synch-l1-2-0.dll) is not loaded in the remote process.
- Automated Crash Diagnostics (WER Event Analysis): When a target process terminates unexpectedly after injection, BlackBox maintains an independent surviving process query handle (
PROCESS_QUERY_LIMITED_INFORMATION | SYNCHRONIZE), reads the NT exit code, queries the Windows Application Event Log (Event ID 1000 Application Errorand1001 Windows Error Reporting), and generates an automated triage report (%TEMP%\BlackBoxInjector\reports\crash-*.txt):- Fault Attribution: Distinguishes whether the crash occurred inside the host executable (e.g.
target.exe), one of the injected payloads (e.g.payload.dll), or an external system/overlay runtime (ntdll,kernelbase, graphics hook). - Disassembly Triage Hints: Translates fault offsets into Relative Virtual Addresses (RVA) from the module base, providing developers with the exact address (
ImageBase + RVA) to jump to in IDA Pro, Ghidra, or x64dbg. - Exception Code Decoding: Translates raw NTSTATUS codes into actionable failure causes:
0xC0000005(Access Violation): Identifies invalid memory read/write operations; automatically flags offsets< 0x1000as NULL page dereferences (uninitialized pointers or virtual function calls).0xC0000409(Fail-Fast / Stack Buffer Overrun): Identifies Control Flow Guard (CFG) violations, corrupted/GSsecurity cookies (__security_check_cookie), or explicit__fastfail()invocations.0xC00000FD(Stack Overflow): Flags thread stack exhaustion caused by recursive hooks or large stack allocations.0xC0000142(DLL Initialization Failed): FlagsDllMainreturningFALSEor unhandled exceptions thrown during TLS callbacks or static CRT initialization.0xC0000374(Heap Corruption): Flags heap metadata corruption, double-frees, or cross-allocator mismatches.0xC0000135/0xC0000139: Identifies missing dependent DLLs or missing ordinal/named exports.
- Timing & Lifetime Analysis: Differentiates immediate initialization failures (
< 1500 ms) inDllMainfrom post-injection crashes triggered during standard application execution.
- Fault Attribution: Distinguishes whether the crash occurred inside the host executable (e.g.
- Pre-Injection PE & Dependency Scanner:
- Validates PE architecture, machine type, image base, entry point, TLS callback count, and section boundaries.
- Inspects base relocations (
IMAGE_DIRECTORY_ENTRY_BASERELOC), warning developers if relocations are stripped (which causes manual map failure if the preferred image base is occupied). - Audits binary security mitigations (
ASLR,DEP,Control Flow Guard,No SEH). - Scans the Import Address Table (IAT) against the target address space, resolving API sets (
api-ms-win-*) and System32 availability.
- VC++ Runtime Detection & 1-Click Installer:
- Injected DLLs compiled with MSVC often require Visual C++ Redistributable runtime DLLs (
vcruntime140.dll,msvcp140.dll). - BlackBox inspects both registry views (
HKLM\SOFTWARE\Microsoft\VisualStudio\14.0\VC\Runtimesin the 64-bit view and inWOW6432Node, where the x86 redistributable registers itself) and falls back to the runtime DLLs inSystem32/SysWOW64. - When injecting a DLL that imports missing VC++ dependencies, an interactive prompt allows users to install the official Microsoft runtime in 1 click or cancel and proceed. Every subsequent injection attempt without the runtime installed prompts again. In headless CLI mode,
--install-redistis the scriptable path.
- Injected DLLs compiled with MSVC often require Visual C++ Redistributable runtime DLLs (
- Real-Time Event Stream: Built-in virtual console displaying color-coded status prefixes (
[+]success,[*]info,[-]warning,[!]error) with multi-line selection and clipboard copy.
- Changed: manual-map static TLS now registers the mapped image directly with
LdrpHandleTlsData; the loader-visible seed DLL fallback was removed. The internal function is resolved from the matching ntdll PDB, cached in%TEMP%\BlackBoxInjector\symbols. - Fixed: a target that is already gone, or a missing/undownloadable ntdll PDB, now uses the normal error dialog (
E2003 TARGET_NOT_RUNNING/TLS-PDB-01) and no longer writes a failure report.
- Fixed: Standard and NtCreateThreadEx capture the 64-bit HMODULE through a remote stub and verify the PE at that base. DLLs that unlink themselves no longer produce a false "not in the module list" error.
- Fixed: crash reports map the WER exception code and fault offset correctly; the module timestamp is no longer shown as the exception code.
- Fixed:
--cli ... > log.txtand pipes keep the caller's standard handles.
- Initial release: six injection methods, manual map with TLS and C++ exception support, cloaking, crash diagnostics, CLI.
Full version history: CHANGELOG.md
Bug reports and technical issues are tracked via GitHub Issues. Please do not report bugs on forum threads - GitHub keeps crashes and diagnostic dumps together with the source.
Click New issue and choose Bug Report: GitHub loads .github/ISSUE_TEMPLATE/bug_report.md automatically, including the environment, diagnostic-report and activity-log fields.
Before submitting, attach the newest report from %TEMP%\BlackBoxInjector\reports\ and the log from the UI console or terminal. Both make triage much faster.


