-
Notifications
You must be signed in to change notification settings - Fork 186
Library Injection
Library injection loads your own compiled code into the attached process. Use it when a normal memory edit is not enough and you want code running inside the target itself.
Common reasons to use it:
- Hooking functions from inside the process
- Calling game or engine code from your own helper code
- Logging internal state that is annoying to inspect from outside
- Applying patches that need more logic than a few overwritten bytes
For simpler stuff like changing values, NOPing instructions or making a small code cave, Libpince Engine is usually easier than building an external library.
PINCE supports two injection paths:
-
.soinjection: Linux-sidedlopen, works with native Linux processes and WINE/Proton processes - DLL injection: Windows-side
LoadLibraryW, only works with WINE/Proton processes
Open the Memory Viewer window and use the Tools menu:
-
Inject .so file: injects a Linux shared object into the target -
Inject DLL file: injects a Windows DLL into a WINE/Proton target
Your code runs inside the target process. If your constructor, hook or DllMain crashes, the target probably crashes too.
Keep this in mind:
- Match the target architecture. A 64-bit target needs a 64-bit library. A 32-bit target needs a 32-bit library
- Avoid doing heavy work directly in a
.soconstructor orDllMain. Start a thread or do the minimum setup if you need anything complicated - Make sure the target can access the file path. PINCE can see the file from your desktop session, but the target may be inside a sandbox or a WINE prefix with different path rules
- Do not inject random libraries you do not trust. This is code execution inside the target process
Use this for Linux shared objects.
Basic steps:
- Attach to a process
- Open
Tools -> Inject .so file - Select a
.sofile - PINCE calls
dlopenin the target process
If it succeeds, PINCE shows:
The shared object (.so) file has been injected
If your .so has a constructor like this, it runs when dlopen loads the library:
__attribute__((constructor))
static void init(void) {
/* your setup */
}If it fails, PINCE shows:
Failed to inject the shared object (.so) file
That failure is generic. It can mean dlopen could not be resolved, the file could not be loaded, the wrong architecture was used, a dependency was missing or the constructor failed.
PINCE first asks GDB to call one of these in the target:
dlopen(path, 2)__libc_dlopen_mode(path, 2)
The 2 is RTLD_NOW, meaning symbols are resolved immediately.
If GDB cannot resolve those symbols, PINCE falls back to manual symbol lookup:
- Reads
/proc/<pid>/maps - Looks for
libc,libc-musl,ld-muslorlibdl - Resolves sandboxed mapped paths through
/proc/<pid>/root - Parses the ELF
.dynsym/.dynstrtables on disk - Calls the resolved
dlopenaddress with a temporary interruption
This fallback handles 32-bit and 64-bit ELF files, little endian and big endian.
Use this for Windows DLLs in WINE/Proton targets.
Basic steps:
- Attach to a WINE or Proton process
- Open
Tools -> Inject DLL file - Select a
.dllfile - PINCE converts the Linux path to a WINE path and calls
LoadLibraryW
If you try this on a native Linux process, PINCE shows:
DLL injection is only supported for WINE/Proton processes
If it succeeds, PINCE shows the returned module handle:
The DLL has been injected at 0x7f1234560000
Your DLL's DllMain runs with DLL_PROCESS_ATTACH like a normal Windows DLL load.
DLL injection is more involved than .so injection because GDB cannot resolve Windows PE exports by name.
PINCE does this:
- Finds
kernel32.dll,ntdll.dlland sometimeskernelbase.dllin/proc/<pid>/maps - Parses PE export tables in memory to find
LoadLibraryW - Falls back to
kernelbase.dllifkernel32.dllforwardsLoadLibraryW - Resolves
NtWaitForSingleObjectorNtDelayExecutionfromntdll.dll - Waits for the target to reach one of those hot
ntdll.dllwrapper functions, up to 10 seconds - Allocates memory for the DLL path
- Writes the DLL path as UTF-16LE
- Calls
LoadLibraryWfrom that cleaner Windows-side context
The path conversion is simple:
/home/user/mod.dll -> Z:\home\user\mod.dll
That assumes the WINE Z: drive maps to /, which is the default setup.
For allocation, PINCE uses the path that fits the target:
- Normal 64-bit and pure 32-bit cases use the target's libc
malloc - Newer WoW64-style cases use Windows-side
VirtualAlloc
During DLL injection, PINCE sets an internal driving_inferior flag. This suppresses normal stop/run UI updates and blocks user pause/continue/step controls while PINCE is steering the target.
If injection fails, check these first:
- The target is not attached anymore
- The library architecture does not match the target
- The file exists for PINCE, but not for the target process namespace
- A
.sodependency is missing from the target environment - The
.soconstructor or DLLDllMaincrashed during load -
dlopen,LoadLibraryW,mallocorVirtualAlloccould not be resolved - The WINE
Z:drive does not map to/ - The WINE target did not hit
NtWaitForSingleObjectorNtDelayExecutionwithin 10 seconds - The target is paused in a bad state for making function calls
For WINE/Proton DLL injection, also make sure kernel32.dll and ntdll.dll are mapped. They should be in normal WINE processes, but if they are missing or the export tables cannot be read, PINCE cannot call LoadLibraryW.
Use .so injection when your payload is Linux code. This is often useful even for WINE/Proton games if you want to hook Linux-side libraries, libc calls or WINE itself.
Use DLL injection when your payload is Windows code and should live in the Windows side of the WINE process. This is the path you usually want for Windows game mods or Windows hooks.
They are not interchangeable. A .so is loaded by Linux dlopen. A .dll is loaded by WINE's Windows loader through LoadLibraryW.