How ModuleMgr in YimMenuV2 Handles Manual Mapping vs Normal Injection

The ModuleMgr class in YimMenuV2 implements two distinct DLL loading strategies—normal injection via LoadLibraryA and manual mapping via a custom PE loader—selecting between them at runtime based on the g_manualMapEnabled configuration flag to balance compatibility against stealth.

The ModuleMgr component serves as the centralized module loader for the YimMenuV2 mod menu, providing a unified interface to handle different injection methods depending on the target process security posture. According to the YimMenuV2 source code, this manager implements both standard Windows API injection and advanced manual mapping techniques within the src/core/memory/ directory. Understanding how these two strategies differ is essential for developers working with process injection and anti-detection measures.

Overview of Module Loading Strategies

The implementation resides in src/core/memory/ModuleMgr.cpp with declarations in src/core/memory/ModuleMgr.hpp, while supporting structures reside in src/core/memory/Module.hpp and src/core/memory/Module.cpp. The class exposes two primary loading methods that represent fundamentally different approaches to bringing DLL code into a target process address space.

  • Normal Injection: Uses the Windows OS loader through LoadLibraryA, allowing the system to handle relocations, imports, and execution automatically.
  • Manual Mapping: Implements a custom portable executable (PE) loader that manually allocates memory, copies sections, fixes relocations, resolves imports, and calls the entry point without notifying the OS loader.

Both methods ultimately store the resulting HMODULE or manual-mapped base address in an internal std::unordered_map<std::string, ModuleInfo> registry, allowing the rest of the codebase to retrieve module handles regardless of the loading strategy employed.

Normal Injection: The LoadLibraryA Approach

The Inject method represents the straightforward loading path. When invoked, it calls LoadLibraryA(path.c_str()) to request that the Windows loader map the specified DLL into the target process.

This approach triggers the standard OS loading sequence. The loader parses the PE headers, maps sections into memory, and performs base relocations if necessary. It resolves import address table (IAT) entries against already-loaded modules, executes TLS callbacks, and finally calls DllMain with DLL_PROCESS_ATTACH. Because the OS tracks this module in its internal list, it appears in tools like Process Explorer and can be detected by anti-cheat systems monitoring LoadLibrary calls.

The method returns true if the handle is non-null, registering the resulting HMODULE in the manager's internal map for subsequent lookups.

Manual Mapping: The Custom PE Loader

The ManualMap method provides a stealth alternative that bypasses the Windows loader entirely. This implementation performs all loading steps manually, ensuring the module does not appear in the OS loader lists or standard module enumeration APIs.

The process follows these steps:

  1. File Reading: Reads the DLL file from disk into a local buffer using standard file I/O (std::ifstream).
  2. Memory Allocation: Calls VirtualAllocEx in the target process to reserve memory equal to the image size specified in the PE optional header.
  3. Section Mapping: Copies the PE headers and individual section data (.text, .data, etc.) into the allocated memory at their respective virtual addresses.
  4. Base Relocation: Walks the relocation table (IMAGE_BASE_RELOCATION) and patches addresses using IMAGE_REL_BASED_HIGHLOW or IMAGE_REL_BASED_DIR64 fixups to account for the actual load address differing from the preferred base.
  5. Import Resolution: Enumerates the import descriptor table, locates dependency modules via GetModuleHandleA in the remote process, and resolves function pointers using GetProcAddress.
  6. TLS Execution: Iterates the IMAGE_TLS_DIRECTORY to invoke any thread-local storage callbacks before the main entry point.
  7. Entry Point Invocation: Creates a remote thread or stub that calls DllMain(moduleBase, DLL_PROCESS_ATTACH, nullptr) manually.

Because no LoadLibrary call occurs, this method avoids standard detection vectors that monitor API hooks in kernel32.dll or ntdll.dll.

Key Implementation Details

Relocation Handling

The ApplyRelocations helper function processes the PE relocation directory. For each block, it calculates the delta between the preferred image base and the actual allocated address, then iterates through relocation entries to patch absolute addresses in the loaded image. This step is critical when the preferred base address is already occupied, which is common in heavily populated process address spaces.

Import Resolution

The ResolveImports function handles the import address table reconstruction. For each imported DLL listed in the import directory, it obtains the module handle from already-loaded modules in the target process, then walks the import lookup table to resolve each symbol. This ensures dependencies are satisfied without invoking the Windows loader's import resolution logic.

TLS Callback Execution

Before calling the main entry point, ExecuteTLS processes the thread-local storage directory. It casts the TLS directory virtual address to a callback array and invokes each function pointer with standard parameters. This maintains compatibility with compiler-generated TLS initialization code present in many C++ DLLs.

Code Usage Examples

The following snippets demonstrate how the YimMenuV2 codebase utilizes these loading strategies:

// Normal injection using standard Windows API
bool success = ModuleMgr::Instance().Inject("C:\\Mods\\Helper.dll");
if (success) {
    LOG("Module loaded via standard injection");
}
// Manual mapping for stealth injection
bool success = ModuleMgr::Instance().ManualMap("C:\\Mods\\Stealth.dll");
if (success) {
    LOG("Module manually mapped without OS loader notification");
}
// Unified interface with automatic strategy selection
bool ModuleMgr::Load(const std::string& path) {
    if (g_manualMapEnabled) {
        return ManualMap(path);
    }
    return Inject(path);
}

// Usage
bool loaded = ModuleMgr::Instance().Load("C:\\Mods\\Feature.dll");

The g_manualMapEnabled boolean flag, typically read from the menu configuration, determines which path Load() selects, allowing users to toggle between compatibility and stealth modes without changing calling code.

Summary

  • ModuleMgr serves as the centralized module loader in YimMenuV2, implemented in src/core/memory/ModuleMgr.cpp with interfaces in src/core/memory/ModuleMgr.hpp.
  • Normal injection relies on LoadLibraryA, utilizing the Windows loader for full PE processing but creating detectable OS artifacts in the module list.
  • Manual mapping implements a complete custom PE loader in ManualMap(), bypassing OS notification through manual memory allocation, relocation, import resolution, and entry point execution.
  • Both strategies store results in an internal map accessible via the unified Load() API, which selects the method based on the g_manualMapEnabled configuration flag.
  • Manual mapping requires explicit handling of relocations via ApplyRelocations, imports via ResolveImports, TLS callbacks via ExecuteTLS, and entry point invocation to achieve functional parity with standard loading.

Frequently Asked Questions

What is the difference between ModuleMgr::Inject and ModuleMgr::ManualMap?

Inject calls LoadLibraryA to let the Windows OS loader handle all PE processing steps, resulting in visible module registration and simpler execution. ManualMap implements a custom loader that manually maps the DLL into memory, resolves dependencies, and calls the entry point without OS involvement, effectively hiding the module from standard enumeration APIs.

Why would you choose manual mapping over normal injection?

Manual mapping avoids detection by anti-cheat and anti-virus systems that monitor LoadLibrary calls or scan the OS module list. When the target process blocks standard DLL injection or when stealth is required, manual mapping prevents the module from appearing in standard tool enumerations while still achieving full code execution.

Where does ModuleMgr store information about loaded modules?

The class maintains an internal std::unordered_map<std::string, ModuleInfo> that maps module names to their base addresses and metadata. This registry is populated by both Inject and ManualMap, allowing the rest of YimMenuV2 to retrieve module handles via the manager's lookup methods regardless of which loading strategy was used.

Does manual mapping execute TLS callbacks and DllMain?

Yes. The ManualMap implementation explicitly calls ExecuteTLS to invoke thread-local storage callbacks and CallEntryPoint to manually execute DllMain with DLL_PROCESS_ATTACH. This ensures the manually mapped module initializes correctly, matching the behavior of a normally loaded DLL.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →