daemon-sec-cheatsheet

The cheatsheet vault for operators: AD, enumeration, exploitation, priv-esc, web, DFIR
git clone https://git.daemon-sec.xyz/daemon-sec-cheatsheet.git
Log | Files | Refs | README | LICENSE

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`