dll-injection.md (6204B)
1 --- 2 title: "Classic DLL Injection" 3 description: "Writing a DLL path into a remote process and calling LoadLibrary on it — the oldest injection primitive, and the one every EDR expects." 4 category: exploitation 5 subcategory: "Process Injection" 6 tags: [process-injection, dll, windows, t1055] 7 tools: [process-hacker, procmon, sysmon, process-explorer] 8 difficulty: beginner 9 updated: 2026-10-03 10 upstreamName: "ired.team" 11 upstreamUrl: "https://ired.team/offensive-security/code-injection-process-injection/dll-injection" 12 upstreamAuthor: "Mantvydas Baranauskas" 13 upstreamLicense: none 14 upstreamRelation: derived 15 references: 16 - name: "MITRE ATT&CK T1055.001 — Dynamic-link Library Injection" 17 url: "https://attack.mitre.org/techniques/T1055/001/" 18 license: none 19 relation: inspired 20 note: "Technique ID and the data sources the Detection section is built around." 21 --- 22 23 ## What this covers 24 25 The baseline technique: get a handle to a process, allocate a few bytes in it, write the 26 path of a DLL on disk, and start a thread at `LoadLibraryA` with that path as the argument. 27 The target's own loader does the rest — mapping the DLL, resolving its imports, and calling 28 `DllMain`. 29 30 It is the easiest injection to write and the easiest to catch. It is worth knowing properly 31 anyway, because every more sophisticated technique on this list exists to avoid one of its 32 three giveaways: the DLL on disk, the `CreateRemoteThread`, and the module appearing in the 33 target's module list. 34 35 ## Prerequisites 36 37 - A handle with `PROCESS_CREATE_THREAD`, `PROCESS_VM_OPERATION` and `PROCESS_VM_WRITE`. 38 Against a process in another session or owned by another user, that means 39 `SeDebugPrivilege` — so in practice, local administrator. 40 - Architecture must match: a 64-bit process cannot load a 32-bit DLL. 41 - **The DLL must exist on disk** at a path the target process can read. This is the 42 technique's defining weakness — there is a file to find, hash and submit. 43 44 ## Walkthrough 45 46 | # | Call | Purpose | Artefact | 47 |---|---|---|---| 48 | 1 | `OpenProcess` | handle to the target | Sysmon event 10, `GrantedAccess 0x1F3FFF` or similar | 49 | 2 | `VirtualAllocEx(..., MEM_COMMIT, PAGE_READWRITE)` | space for the path string | small RW private allocation — not suspicious alone | 50 | 3 | `WriteProcessMemory` | the DLL path | cross-process write | 51 | 4 | `GetModuleHandle("kernel32")` + `GetProcAddress("LoadLibraryA")` | the start routine | — | 52 | 5 | `CreateRemoteThread(..., LoadLibraryA, pPath, ...)` | the loader runs in the target | **Sysmon event 8** — the loudest part | 53 | 6 | target's loader maps the DLL, calls `DllMain` | execution | Sysmon event 7 (`ImageLoad`) naming your DLL | 54 55 Note that the allocation here is `PAGE_READWRITE`, not RWX: you are writing a *string*, not 56 code. Scanners hunting private executable memory do not find this one — the module shows up 57 properly mapped, image-backed, in the module list. That is the trade: it is quiet in memory 58 and loud everywhere else. 59 60 ### What it looks like from the outside 61 62 The injected DLL executes with the host's identity and token, so whatever it spawns inherits 63 the host's lineage. A payload that runs a shell produces a process tree that makes no sense 64 for the host application. 65 66 <figure class="shot"> 67 <img src="https://raw.githubusercontent.com/mantvydasb/RedTeaming-Tactics-and-Techniques/8cdbdd60eb4a8997e689649f3911f7c893e59ed9/.gitbook/assets/inject-dll.png" 68 alt="Process tree showing notepad.exe as the parent of rundll32.exe, which in turn parents a cmd.exe" 69 loading="lazy" referrerpolicy="no-referrer"> 70 <figcaption>The giveaway in the tree: notepad parents rundll32, which parents cmd.exe. 71 <span class="shot-credit">ired.team · Mantvydas Baranauskas</span> 72 </figcaption> 73 </figure> 74 75 <figure class="shot"> 76 <img src="https://raw.githubusercontent.com/mantvydasb/RedTeaming-Tactics-and-Techniques/8cdbdd60eb4a8997e689649f3911f7c893e59ed9/.gitbook/assets/inject-dll-procmon.png" 77 alt="Procmon event list showing the injected DLL being opened and loaded by the target process" 78 loading="lazy" referrerpolicy="no-referrer"> 79 <figcaption>The same thing in Procmon — the injected DLL is read from disk by the host. 80 <span class="shot-credit">ired.team · Mantvydas Baranauskas</span> 81 </figcaption> 82 </figure> 83 84 ## Detection 85 86 This is the technique detection was built for, so there are several independent catches and 87 you do not need all of them. 88 89 **Sysmon event 8, `CreateRemoteThread`.** A remote thread whose `StartAddress` resolves to 90 `kernel32!LoadLibraryA` or `LoadLibraryW` is close to conclusive. Legitimate uses of 91 `CreateRemoteThread` exist — debuggers, some injectors in AV products themselves — so 92 baseline them rather than alerting blind. 93 94 **Sysmon event 7, `ImageLoad`.** The DLL is a real mapped module, so it is named in full. 95 Flag modules loaded from user-writable paths (`%TEMP%`, `%APPDATA%`, `C:\Users\Public`), 96 unsigned modules in signed processes, and any module whose load is not explained by the 97 host's import table or a known plugin directory. 98 99 **Sysmon event 10, `ProcessAccess`.** `GrantedAccess` containing `PROCESS_CREATE_THREAD` 100 (`0x0002`) together with `PROCESS_VM_WRITE` (`0x0020`) is the access combination this 101 technique needs and little else does. 102 103 **Module-list review.** `Process Hacker` → Modules, or `listdlls`, shows the DLL plainly. 104 Compare each loaded module against the expected set for that binary; a `rundll32` or an 105 unknown DLL inside `notepad.exe` is not ambiguous. 106 107 **On disk.** There is a file. Hash it, and check its path against application allowlists. 108 Of the techniques on this page, this is the only one where the payload is recoverable from 109 the filesystem after the fact — which makes it the best case for the responder and the worst 110 for the operator. 111 112 ## References 113 114 - [MITRE ATT&CK T1055.001](https://attack.mitre.org/techniques/T1055/001/) 115 - [MITRE ATT&CK T1055.002](https://attack.mitre.org/techniques/T1055/002/) — portable 116 executable injection, the variant with no DLL on disk and no module-list entry 117 - [Thread execution hijacking](/sheets/exploitation/thread-execution-hijacking) — starting 118 code without `CreateRemoteThread`