Hidden in plain sight: Alternate Data Streams

15 minute read

Have you ever wondered how a file in a file listing is shown with size 0 bytes but can still contain data? Or maybe wondered where all that meta data is stored, how malware can infect files or just how you can “hide” stuff in a file?

Let’s talk about Alternate Data Streams to learn more.

ADS - Alternate Data Streams

When you hear “Alternate Data Streams” you may think about resource forks in Mac OS HFS. But we’re talking about Windows and NTFS. Back in the days of Windows NT 3.1 (ha!), NTFS streams were actually implemented to support the Mac resource forks.

Let’s fire up a cmd prompt and start with a practical example:

> echo Hello World! > hello.txt:hidden
> dir

02/23/2019  12:56 AM  0 hello.txt

As you can see, we redirected our echo to the file, but specified another name after the colon. This is the name of the stream, which now contains our “Hello World!”. Note, though, that our created file is still being displayed as having a file size of 0 bytes.

Seeing the streams

What we just did, was to write data to a data stream other than the original data stream, a.k.a. the file itself.

We could go on and add another stream like this: echo more stuff > hello.txt:mystream.

After creating our streams, how do we actually look at them?

We can (on Win >= 8) use the Get-Item and Get-Content Cmdlets in Powershell:

> Get-Item -path .\hello.txt -stream *
Stream                   Length
------                   ------
:$DATA                        0
hidden                       13
mystream                      9

The above command will show us the streams we’ve created, plus the default one, also called unnamed data stream. When looking at the sizes/lengths, we can also see that the unnamed stream is still only 0 bytes (which can make it hard for users to detect disk usage in case of the presence of some large alternate data streams).

$DATA is actually the stream type. So our unnamed default stream is literally just an unnamed stream, while our “hidden” stream would be an alternate stream with a name, which could also be written like hidden:$DATA.

Fun fact: if we were to create a hash of our hello.txt file (Get-FileHash hello.txt), then added data to an ADS, the hash would not change.

Getting the contents out

To get the content out again, we can use the Get-Content cmdlet:

> Get-Content -path .\hello.txt -stream hidden
Hello World!

What’s so interesting?

So what’s so interesting about the alternate data streams, being old and not very much used?

Well, besides storing simple text strings, we can also store executable code in a stream. The fact that it doesn’t show in the file size, won’t change the hash, doesn’t come up in explorer / in the UI and that ADS can’t be disabled, makes it an ideal hiding place for malicious software (and it has indeed been used by malware in the past).

Nowadays, AV software usually scans these alternate data streams too (just try it out).

Storing an executable in an ADS

Let’s see how it can be done by storing the calc.exe in an ADS called exestream of our hello.txt. We will use the Set-Content Cmdlet and explicitly specify byte encoding and a readcount of 0, to read the file in one read operation. The Get-Command is only used to not type out the path.

> Set-Content -path .\hello.txt -value $(Get-Content $(Get-Command calc.exe).Path -readcount 0 -encoding byte) -encoding byte -stream exestream

If you have some known malware on your system and store it in an ADS, you will notice that it is detected nonetheless. Well, let’s hope so :-).

Launching the hidden code

Now that we have a binary in our exestream, we can launch it, e.g., via wmic (Windows Management Instrumentation). I use Resolve-Path, again, only not to type out the full path.

> wmic process call create $(Resolve-Path .\hello.txt:exestream)

If it works, you should see the calculator pop up.

Finding alternate data streams

Now that we know what a data stream is and what we can do with it, let’s see how we can list all the files in and below a directory that actually have alternate data streams (and maybe check that everything is in order).

> Get-ChildItem -recurse | ForEach { Get-Item $_.FullName -stream * } | Where stream -ne ':$DATA'

Note: one of the more common ADS that you will find is Zone.identifier. These are usually for files that were downloaded and might be used to indicate this fact to the user; see info at the Microsoft docs.

Have fun playing around with Alternate Data Streams!

Leave a Comment