← Back to blog

Alternate Data Streams: the second $DATA attribute everyone forgets

· 5 min read

ADS is the NTFS feature that keeps surprising junior analysts. A file can have more than one $DATA attribute. The unnamed one is what dir shows you the size of. Anything else, anything with a colon in its identifier, is invisible to the tools most people reach for. Attackers know this. Microsoft knows this. The auditor reviewing your IR report should know this too.

The mechanism, briefly

In the MFT record, attribute type 0x80 is $DATA. The header carries an optional name. A single record can carry many $DATA attributes: one unnamed (the primary stream) and any number of named ones. From userland you address them with filename:streamname.

echo payload > readme.txt:nope
type readme.txt:nope

readme.txt is zero bytes by every measure Explorer shows. The named nope stream holds the payload. Same MFT record. Same parent directory index entry. Two independent data attributes.

This is not a bug. It is a 1993 design decision that NTFS inherited from HPFS to let Services for Macintosh attach resource forks. Almost nobody uses ADS for that anymore. Everyone else, Microsoft included, uses it for ad-hoc metadata.

Where Windows itself drops ADS

Zone.Identifier is the famous one. Edge, Chrome, Firefox and Outlook all add it to anything that crossed a security boundary. The contents are an INI fragment with the source URL, the referrer, and a ZoneId=3 line that triggers Mark-of-the-Web protections in Office and SmartScreen. If you are reading an MFT and a binary in Downloads\ has no Zone.Identifier, ask why. Someone may have run Unblock-File or copied the file through a stream-discarding archive.

Other Microsoft-blessed streams you will run into:

  • $KSP on Asian-language keyboard layout files.
  • OECustomProperty and friends on Outlook attachments saved to disk.
  • Wof reparse data on compressed files under WIMBoot and CompactOS.
  • SmartScreen on executables Defender has fingerprinted.
  • encryptable on ConfigMgr cache directories.

These show up constantly. Learn what your baseline looks like before you flag streams as suspicious.

What attackers actually do with ADS

Three patterns repeat:

  1. Payload stash, separate launcher. The visible file is benign (a Word document, a PDF, a license file). The named stream carries the actual PE. WMI, wmic process call create, or rundll32 launches legit.docx:payload.exe directly. Defender does scan named streams these days, but plenty of EDRs still do not when a stream is created by a trusted process.
  2. Living-off-the-land hiding place. PowerShell payloads written into ads:script.ps1 then executed with Get-Content -Stream. Surprisingly common in red-team retainers because it survives a path-only IOC search.
  3. Persistence in alternate streams of system files. C:\Windows\System32\drivers\etc\hosts:backdoor is the textbook example. The host file looks untouched, the timestamp may even be original, and most file-integrity monitoring tools never look past the unnamed stream.

The shared property: you cannot see them with dir, you cannot see them in Explorer, and you cannot see them by hashing the visible file. You can see them by reading the MFT record.

Detecting them on a live host

dir /R enumerates streams on cmd. PowerShell's Get-Item -Stream * does the same with cleaner output. streams.exe from Sysinternals walks a tree. All three work fine on the volume you are on. None of them help if the volume is offline or if you do not know which files to check.

dir /R C:\Users\bob\Documents\
Get-ChildItem -Recurse | ForEach-Object { Get-Item $_.FullName -Stream * } |
  Where-Object Stream -ne ':$DATA'

That second one-liner enumerates every non-default stream on a tree. Run it on a fresh Windows install and you will already have hundreds of hits, mostly Zone.Identifier. Filter to streams larger than a few hundred bytes and the noise drops fast.

Why the MFT is the better lens

The on-host commands enumerate per file. They miss what you did not think to check. The MFT does not. Every $DATA attribute, named or not, is written into the record at attribute type 0x80. Walk the table, list every record's $DATA attributes with their names and sizes, and you have an exhaustive inventory of every stream on the volume in a single pass.

A few things this gives you that per-file enumeration does not:

  • Streams on files in directories you would never have looked in, including C:\Windows\Temp\ and C:\$Recycle.Bin\.
  • Streams on deleted files. The MFT record survives the delete; the ADS is still listed in the attribute stream. fls and istat from Sleuth Kit will show them; so will MFTECmd and the omerbenamram/mft CLI.
  • Resident streams whose contents sit inline in the MFT record. Small payloads (PowerShell loaders, base64 blobs, encoded config) frequently fit in the slack of a 1,024-byte record. You can read them directly from the MFT extract without ever touching the data area. See resident data for the size threshold.
  • Stream names. The MFT preserves the name attackers chose. :payload.exe looks innocuous in a list of file sizes; it is loud when you grep an attribute dump for exe.

The triage filter I actually use

After parsing the MFT with MFTECmd or mft_dump, I export every record with more than one $DATA attribute. Then I drop:

  • Anything whose extra stream name is Zone.Identifier and is under 1 KB.
  • Anything in \Windows\WinSxS\ (the manifest store, full of legitimate streams).
  • Anything under a vendor-signed application directory whose stream is a known thumbnail or metadata key.

What remains is the working set. On a clean workstation it is usually under a hundred records. On a host where something interesting happened, the deltas show up immediately.

Cross-reference the survivors with the USN journal to see when each stream was created or last modified. A named $DATA attribute that appeared at 02:14 and has not been touched since is worth a closer look. Pair with Sysmon and Security event logs if you have them: Sysmon Event ID 15 (FileCreateStreamHash) is the one event ID that captures ADS creation with the hash of the stream contents.

A note on Defender

Modern Defender does scan ADS by default. It is genuinely good at the brute-force "drop a known-bad PE in a stream" pattern. It is less good at small obfuscated PowerShell hidden in streams, and it does not always touch streams on excluded paths. Do not assume Defender's silence is evidence of absence.

Further reading

  • Microsoft, File Streams. The official mechanics, including the BackupRead/BackupWrite API that copies all streams.
  • Sleuth Kit's istat and fls for offline enumeration that surfaces ADS.
  • Mark of the Web docs from Microsoft for what Zone.Identifier actually does and how Office uses it.

External resources