PWN^:Exploitation

Classic DLL Injection

Writing a DLL path into a remote process and calling LoadLibrary on it — the oldest injection primitive, and the one every EDR expects.

beginnerupdated 2026-10-03process-hacker · procmon · sysmon · process-explorer

What this covers

The baseline technique: get a handle to a process, allocate a few bytes in it, write the path of a DLL on disk, and start a thread at LoadLibraryA with that path as the argument. The target’s own loader does the rest — mapping the DLL, resolving its imports, and calling DllMain.

It is the easiest injection to write and the easiest to catch. It is worth knowing properly anyway, because every more sophisticated technique on this list exists to avoid one of its three giveaways: the DLL on disk, the CreateRemoteThread, and the module appearing in the target’s module list.

Prerequisites

  • A handle with PROCESS_CREATE_THREAD, PROCESS_VM_OPERATION and PROCESS_VM_WRITE. Against a process in another session or owned by another user, that means SeDebugPrivilege — so in practice, local administrator.
  • Architecture must match: a 64-bit process cannot load a 32-bit DLL.
  • The DLL must exist on disk at a path the target process can read. This is the technique’s defining weakness — there is a file to find, hash and submit.

Walkthrough

#CallPurposeArtefact
1OpenProcesshandle to the targetSysmon event 10, GrantedAccess 0x1F3FFF or similar
2VirtualAllocEx(..., MEM_COMMIT, PAGE_READWRITE)space for the path stringsmall RW private allocation — not suspicious alone
3WriteProcessMemorythe DLL pathcross-process write
4GetModuleHandle("kernel32") + GetProcAddress("LoadLibraryA")the start routine—
5CreateRemoteThread(..., LoadLibraryA, pPath, ...)the loader runs in the targetSysmon event 8 — the loudest part
6target’s loader maps the DLL, calls DllMainexecutionSysmon event 7 (ImageLoad) naming your DLL

Note that the allocation here is PAGE_READWRITE, not RWX: you are writing a string, not code. Scanners hunting private executable memory do not find this one — the module shows up properly mapped, image-backed, in the module list. That is the trade: it is quiet in memory and loud everywhere else.

What it looks like from the outside

The injected DLL executes with the host’s identity and token, so whatever it spawns inherits the host’s lineage. A payload that runs a shell produces a process tree that makes no sense for the host application.

Process tree showing notepad.exe as the parent of rundll32.exe, which in turn parents a cmd.exe
The giveaway in the tree: notepad parents rundll32, which parents cmd.exe. ired.team · Mantvydas Baranauskas
Procmon event list showing the injected DLL being opened and loaded by the target process
The same thing in Procmon — the injected DLL is read from disk by the host. ired.team · Mantvydas Baranauskas

Detection

This is the technique detection was built for, so there are several independent catches and you do not need all of them.

Sysmon event 8, CreateRemoteThread. A remote thread whose StartAddress resolves to kernel32!LoadLibraryA or LoadLibraryW is close to conclusive. Legitimate uses of CreateRemoteThread exist — debuggers, some injectors in AV products themselves — so baseline them rather than alerting blind.

Sysmon event 7, ImageLoad. The DLL is a real mapped module, so it is named in full. Flag modules loaded from user-writable paths (%TEMP%, %APPDATA%, C:\Users\Public), unsigned modules in signed processes, and any module whose load is not explained by the host’s import table or a known plugin directory.

Sysmon event 10, ProcessAccess. GrantedAccess containing PROCESS_CREATE_THREAD (0x0002) together with PROCESS_VM_WRITE (0x0020) is the access combination this technique needs and little else does.

Module-list review. Process Hacker → Modules, or listdlls, shows the DLL plainly. Compare each loaded module against the expected set for that binary; a rundll32 or an unknown DLL inside notepad.exe is not ambiguous.

On disk. There is a file. Hash it, and check its path against application allowlists. Of the techniques on this page, this is the only one where the payload is recoverable from the filesystem after the fact — which makes it the best case for the responder and the worst for the operator.

References

On this page

☧ CPTS · ordered posts

Locked

Unlock with your Main_Key through local GPG.

Start the local GPG helper

In your cheatsheet checkout, run:

npm run unlock:helper

Open the link it prints, or paste its pairing code below. Keep the terminal open until GPG finishes.

Import an index you already decrypted

Download the encrypted index, then decrypt with GPG:

gpg --output report-index.json --decrypt report-index.gpg

Your private key stays in GPG. The decrypted index is saved in this browser so you can reopen it after navigation, refreshes and restarts. Lock this browser or clear this site's browser data to remove the saved index. Nothing is uploaded.