Skip to content

Latest commit

 

History

9 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Uniject

NOTE: Nearly complete, but still a WIP

Windows-specific command line tool and library for injecting assemblies into Unity games at runtime. (possibly any Mono application, but haven't tested) Useful when you want to load an assembly into a game but don't want to patch the game's files.

Usage

Usage information specific to the library or command line tool found further down. This section describes the options and expectations.

Required:

The following parameters require a value:

  • pid (-p, --pid) - The targeted process ID.
  • assembly_path (positional argument) - The assembly you'd like to load. (not required, but highly recommended that you use the absolute filepath)

You can use whatever assembly/class/method name you'd like, but the signature of the entrypoint method for your assembly is expected to match:

public static void MethodName()
{
    // ...
}

Optional:

The following parameters are optional:

  • mono_path (-M, --mono) (Default: auto-detected) - Filepath of the target's Mono DLL. By default, the injector scans the target process's loaded modules for one exporting mono_init and uses whichever it finds; only override this if that detection picks the wrong module.
  • class_name (-c, --class) (Default: Loader) - The fully qualified name of the class that contains your assembly's entrypoint method.
  • method_name (-m, --method) (Default: Initialize) - The name of your entrypoint method, which is immediately called after the assembly has been loaded.
  • thread_id (-t, --tid) (No Default) - Should only be specified if you require your assembly's entrypoint be invoked on a specific existing thread in the target process (via thread hijacking) instead of a new thread attached to the root app domain.
  • debugging (-g, --debug) (Default: FALSE) - Allows debugging the game in Visual Studio Tools for Unity. See below for more info.
  • log_path (Default: none) - Accepted by the library API (unij_set_log_path), but not currently exposed as a CLI flag, and not yet wired up to an actual logging backend (LogDebug/LogInfo/LogWarning/LogError are all no-ops right now) - setting it currently has no effect.

Unsupported:

The following parameters were either started and scrapped or never made it past "idea".

  • no_wait (Default: FALSE) - Tells the injector not to wait for confirmation that the the loader has completed.

Debugging

I copied the logic for this option from dynity, which documents the setup process. I've never tried setting up the tools and testing this.

Command Line Usage

uniject.exe -l
uniject.exe [OPTION]... -p PID ASSEMBLY
Flag Description
-h, --help Display usage and exit.
-l, --list List running Unity/Mono processes (PID, executable, detected Mono module).
-p, --pid Required (unless -l). Process ID of the target game.
ASSEMBLY Required (unless -l). Filepath of the assembly to inject, given as a positional argument.
-g, --debug Enable Mono soft-debugger support in the target. See Debugging.
-t, --tid Thread ID to hijack for the entrypoint call, instead of attaching a new thread.
-c, --class Fully qualified class name containing the entrypoint (default: Loader).
-m, --method Entrypoint method name (default: Initialize).
-M, --mono Explicit path to the target's Mono DLL (default: auto-detected).

Examples:

uniject.exe -l
uniject.exe -p 12345 C:\path\to\MyMod.dll
uniject.exe -p 12345 -c MyMod.Loader -m Start C:\path\to\MyMod.dll

Library Usage

TODO: Library documentation. In the meantime, include/uniject/*.h is the API surface - injector.h/process.h for driving an injection, params.h for the parameters struct, base.h for the uniject_t context lifecycle (unij_injector_open*/unij_loader_open/unij_inject/unij_close).

How it Works

Probably best to just break this down into the step-by-step:

  • Injector is fed and processes a set of parameters for the current injection.
  • Injector acquires necessary token privileges and opens the process specified by the PID parameter.
  • Injector auto-detects the target's Mono DLL (unless --mono was given), initializes shared memory, and populates it with all the parameters needed for the injection.
  • Injector creates a named event that will be used to signal the completion of the loader process.
  • Injector loads the loader DLL into the target process:
    • uniject-loader-32.dll vs uniject-loader-64.dll - Picked based on whether the target process is 32-bit or 64-bit.
    • Always done via CreateRemoteThread, running a small hand-written shellcode stub that walks the target's own PEB/loader data to resolve LoadLibraryW and calls it - no assumptions about anything already being injected.
  • Loader (DLL) maps the shared memory and reads in the injection instructions.
  • Loader finds the currently loaded Mono DLL and looks up all the API it'll be using.
  • Loader invokes the entrypoint one of two ways, depending on whether a thread ID (--tid) was specified:
    • No thread ID: the loader attaches a brand-new thread to the root app domain, calls the entrypoint on it, then detaches it from the app domain when done.
    • Thread ID specified: the loader hijacks the existing thread via SuspendThread/GetThreadContext/SetThreadContext, running the entrypoint call on it directly (it's already attached to the app domain), then restores the thread's original context.
  • Loader finds the specified assembly and loads it into the current app domain, then searches it for the specified class and method name and calls it, waiting for it to complete.
  • Loader cleans up, signals the named event to notify the injector of its completion, and then unloads itself from the process. (assembly still loaded)
  • Injector cleans up shared memory and event and exits.

About

Runtime assembly injector for Unity-based games

Resources

Stars

3 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages