Bartosz Zieliński

Alternate Data Streams

Hidden data in NTFS

Both files and folders in NTFS file system can have additional “forks” of data called alternate data streams (ADS-es in short). If this reminds you of forks on Mac then this is no coincidence: this feature was added to Windows NT explicitly as a support for compatibility with Mac’s HFS. In general, the ability to add additional named attachments to any file of folder is a wonderful idea. Except that on Windows those attachments don’t show up in a standard folder listings (even on command line), and, worse than that, their contents do not contribute to reported file sizes. So, for instance, you can have an innocuous looking small text file with 1GB alternate data stream. Good luck hunting for your missing disk space. The fact that such alternate streams will be dropped when moved/copied out of NTFS is just a topping on a cake. Do not store anything important in an ADS. It is needless to say that ADS-es are also a huge security risk. You can even store hidden executables inside ADS-es.

On the other hand, they do have a lot of legitimate uses and they are actually used by some popular applications and even some Windows services. E.g., SQL Server uses ADS in database file to store snapshot data, Windows attachment manager stores web content zone information in an appropriate ADS, and you can make Dropbox ignore subfolder, or a file inside Dropbox folder, by placing 1 in the com.dropbox.ignored ADS of that file or folder. Thus, the ability to set, query and delete ADS-es may be of some practical value. It is also a great opportunity to learn something new about powershell.

Manipulating ADS-es in powershell

Let us create a new file (test.txt) in some location, and fill it with initial data using powershell command:

Set-Content -Path test.txt -Value 'Hello World'

We can verify that test.txt has size of 13 bytes. Let us now create an alternative data stream of test.txt called aedh, and fill it with some random data:

$yeats = @"
Had I the heavens' embroidered cloths,
Enwrought with golden and silver light,
The blue and the dim and the dark cloths
Of night and light and the half light,
I would spread the cloths under your feet:
But I, being poor, have only my dreams;
I have spread my dreams under your feet;
Tread softly because you tread on my dreams.
"@
Set-Content -Path test.txt -Value $yeats -Stream aedh

When we check the file’s metadata with

Get-Item -Path .\test.txt

we see that the file size did not change (although LastWriteTime was appropriately updated). When we display the contents of `test.txt’ with

Get-Content -Path .\test.txt

it also contains just Hello World. We would see the same if we opened it with notepad. To see the aedh stream metadata or to view its contents we need to add -Stream option:

Get-Item -Path test.txt -Stream aedh
Get-Content -Path test.txt -Stream aedh

Some commands (including commandlets we used so far) may accept file names which include stream name after the colon, e.g.,

Get-Content -Path ./test.txt:aedh

Unfortunately, the support for such “stream aware file names” is not universal. Amazingly, notepad ./test.txt:aedh works, but code ./test.txt:aedh does not. -Stream argument of Get-Item works with globs, e.g., use

Get-Item -Path test.txt -Stream *

to display all streams associated with test.txt. In this case you will see also :$DATA stream, i.e., the ordinary contents of the file. In order to see the number of streams in the file and the total size of file including alternate streams one can use Measure-Object commandlet to count and sum lengths of streams received through pipe from Get-Item (note the backticks to mask new-lines):

Get-Item -Path ./test.txt -Stream * `
     | Measure-Object -Property Length -Sum `
     | select Count, Sum

Alternate data streams do not survive zipping: Compress test.txt, uncompress it, and then display data streams of uncompressed file:

Compress-Archive -Path test.txt -DestinationPath test.zip
Expand-Archive -Path .\test.zip
cd test
Get-Item -Path ./test.txt -Stream *

and you will notice that aedh is no longer there (it is, of course, still attached to the original file).

In order to actually remove ADS from file use Remove-Item with appropriate arguments, e.g.,

Remove-Item -Path test.txt -Stream aedh

Alternate data streams on folders

One can attach alternate data streams to folders as well as to files. One significant difference is that on folders ADS-es are not “alternate”, but the only data streams, and this has consequences. If cat is a folder without any ADS-es attached, then Get-Item cat -Stream * displays nothing. This is one of the reasons why Get-Item * -Stream * is a poor replacement for dir /R which works in cmd displaying in separate lines all alternative data streams for all folders and files in the current directory in addition to ordinary information for those files and folders.

Another bad thing about ADS-es on folders is that until powershell 7.2 Get-Item would not display information about them, -Stream option or not. On the other hand, Set-Content and Get-Content worked fine with streams on folders.

Some applications

Zone.Identifier

Most web browsers under Windows (including Chrome, and, of course, Edge) will attach to every downloaded file an alternative data stream named Zone.Identifier. Long time ago Zone.Identifier would contain just a number denoting web content zone. Web content zone 0 means “downloaded from local computer”, 1 is “downloaded from intranet”, 2 “downloaded from trusted site”, 3 “downloaded from internet”, and, 4 “downloaded from somewhere Windows deems shady”. Nowdays, Zone.Identifier still contains web content zone, but also a lots of other things. For example, I have just downloaded (using Chrome) some sample data from one of pytorch tutorials. The command

 Get-Content .\data.zip -Stream Zone.Identifier

returns

[ZoneTransfer]
ZoneId=3
ReferrerUrl=https://pytorch.org/
HostUrl=https://download.pytorch.org/tutorial/data.zip

Yes, this is INI file format. Above, HostUrl is obviously the site the file was directly downloaded from (with the full path). However, the link I clicked was on page ReferrerUrl. Needlesly to say, this is kind of creepy, though I do understand why all of this info might become useful when tracking provenance of malware. It is therefore somewhat reassuring that when I downloaded the same file from incognito tab, the content of Zone.Identifier was far less intrusive:

[ZoneTransfer]
ZoneId=3
HostUrl=about:internet

The standard Windows unzipper (called from explorer) attaches Zone.Identifier to each file from the uncompressed archive with contents like that (I have censored my username):

[ZoneTransfer]
ZoneId=3
ReferrerUrl=C:\Users\***Censored***\Downloads\data.zip

Thus, it still has the the ZoneId indicating this file comes from the internet. However, the only url is a path to the archive. So this is less concerning, especially if you later delete original zip file. Interestingly, neither Expand-Archive nor tar does the same thing.

In order to run some downloaded exe file which Windows does not like, or simply to remove some privacy violating info, one can use Remove-Item, e.g.,

Remove-Item -Path data.zip -Stream Zone.Identifier

This is, however, such a common task that there is a specialized commandlet provided which does just that: Executing

Unblock-File -Path data.zip

removes Zone.Identifier. Since Unblock-File like Remove-Item accepts file names through the pipe (and by parameter name), we can get rid of all Zone.Identifier’s from all the files in the current folder as follows:

Get-ChildItem -Path . -Recurse -File | Unblock-File

Above, the -File flag causes Get-ChildItem to output only files.

Finally, if for some unfathomable reason you want to copy Zone.Identifier from one file to another you can do it as follows:

Get-Content -Path file.a -Stream Zone.Identifier `
     | Set-Content -Path file.b -Stream Zone.Identifier

Dropbox

Say, you store on Dropbox your node project. And since disk space limit on free Dropbox account is rather small, you obviously do not want Dropbox to save in the cloud hundreds of megabytes of node_modules in addition to your actual sources. Problem is, node_modules must be inside your project, i.e., inside Dropbox. Can you make some files or folders deep inside Dropbox folder on your computer invisible to Dropbox? Yes you can. Dropbox will ignore any file or folder which has an alternate data stream named com.dropbox.ignored with 1 inside. Thus, to avoid syncing to cloud all the external modules downloaded through npm, do the following:

Set-Content -Path ./node_modules -Stream com.dropbox.ignored -Value 1 

Actually, you can do it even less verbosely (it will also work in cmd):

echo 1 > ./node_modules:com.dropbox.ignored

Be warned that the above works in cmd and powershell 7.3. It did not work in powershell 5.1.