Click here to Skip to main content
Click here to Skip to main content

Manipulate Alternate Data Streams

By , 23 Jan 2005
 

Sample Image - ADS.gif

Introduction

One of the most interesting features of the NTFS file system is that of hidden data streams. Well documented but poorly supported by the .NET framework, alternate data streams (ADS) allow information to be stored with files in ways not visible to traditional file viewing techniques.

I became interested in this feature of NTFS (the default file system for Windows 2000 and available for use by WinXP) when I tried to write a tool to manipulate files created by a third party application. I wanted to write an application that would allow a developer to customize their view of the data in the file, and needed a way to persist the additional metadata of the view state without mucking up the file structure of the original. In my particular case, the raw data is XML, and any extension of it by me to include the view metadata would cause it to fail the original author's validation scheme in the original tool.

I had a couple of choices: add a new file with a different extension to the directory with the original file, or keep a list of viewed files local to my application in a database of some sort (XML file, flat file, Access, whatever) and store the metadata in there.

Neither of the choices is very good. The first case may cause the original third party application to fail if it parses all the files in its data directory on execution and doesn't recognize the metadata file. The second case means a lot of extra infrastructure unrelated to the actual information in which I was interested. In both cases, there was a synchronization issue, where a user could rename one of the original data files in Windows Explorer, and then both my file -> metadata file mapping scheme and database location tracking scheme would fail to find the original file.

ADS is the solution. By writing data to an alternate stream of the original data file, you get:

  • Transparency - the original data is not affected.
  • Synchronization -the metadata stays with the relevant file.

I'll show you a class I wrote to help manage my interactions with ADS.

More Background

You can play with alternate data streams at the DOS prompt without code. Type the following at a DOS prompt:

C:\>notepad c:\test.txt

Click the Yes button when you’re prompted to create a new file. Once Notepad opens, type “obvious data” and save the file. Now type the following back at the DOS prompt:

C:\>notepad c:\test.txt:secret.txt

The colon separates the name of the file from the name of your stream. The stream didn’t exist yet, so you’re prompted to create it. Click Yes again. When Notepad becomes ready, type “secret stuff” and save.

The “secret stuff” text is saved with the text.txt file, but it’s hidden in your new ADS named “secret.txt”. If you double-click on c:\test.txt in Explorer, you’ll only see the “obvious data” text. It’s the same if you use the DOS type command. The only way to view and modify the data is to explicitly name the stream with the file when it gets opened, as you see above in the second DOS command using the colon syntax.

The example above uses text data written to a text file, but there is no restriction on the files to which you can append secret data in an alternate stream. You can read and write secret data to EXE files, DLLs, or any other file type. You can have as many streams in a file as you want, as long as they have different names of course.

Using the code

The basic class is a thin wrapper around a couple of Win32 API calls, CreateFile(), ReadFile() and WriteFile(). The Win32 functions are necessary because the .NET framework won’t let you use colons in file names. If you attempt to open a file with a colon in the name using a StreamReader or TextReader, you get the following error:

System.NotSupportedException: The given path's format is not supported.
   at System.Security.Util.StringExpressionSet.CanonicalizePath(String path, 
                                                       Boolean needFullPath)

I’m only interested in saving and reading text data (specifically, XML encoded metadata), so I wrap the API calls with Write() and Read() methods that accept and return (in addition to the name of the file and stream to use) strings of data to save or read in the ADS. The class is implemented as a couple of static methods in a class that can’t be instantiated because I don’t need any stateful information to be retained. I call it once to read the view metadata and once to write to it for each of the views of the original document.

Despite the big looming changes in Longhorn and beyond, it never hurts to learn more about the Win32 API. Before you can read and write using ReadFile() and WriteFile() functions, you need a file handle. To get a handle, call CreateFile():

[DllImport("kernel32", SetLastError=true)]
static extern  uint CreateFile(string  filename,
                               uint    desiredAccess,
                               uint    shareMode,
                               IntPtr  attributes,
                               uint    creationDisposition,
                               uint    flagsAndAttributes,
                               IntPtr  templateFile);

The fileName parameter takes the fully qualified path to the stream using the colon syntax. Pass the OPEN_EXISTING constant to the creationDisposition parameter to get a handle for reading, and CREATE_ALWAYS to get a handle for writing. You use the unsigned integer returned by this function as a parameter to ReadFile() and WriteFile() to actually do the file IO. The read/write code is pretty straightforward, and is well commented in the downloadable code.

The managed assembly also contains a StreamNotFoundException object that extends FileNotFoundException and is thrown if the requested stream cannot be found on a read attempt. By extending FileNotFoundException, you automatically get the FileName property, and a logical exception to use. It just remains up to this implementation to store the additional information that a consumer of the exception would require, i.e., the name of the stream.

You can use the test project to read and write streams to and from arbitrary files on the file system to exercise the library and get comfortable manipulating alternate streams programmatically. If you run it from the IDE, don’t write to the DLLs and EXEs that your project is currently compiling for execution. You may get concerned that your ADS is disappearing when, in fact, what's happening is that the file is being recreated on every execution. Not that it happened to me of course...

When I use the library in my projects, I use the name of the client namespace for the stream name. That pretty much guarantees that the stream will not be overwritten by some other application.

Points of Interest

I have found the use of ADS to keep metadata with the original data to be very helpful. The code isn’t rocket science, but it is fairly well organized and ready for your consumption.

This CodeProject article shows you how to write a tool to help identify and track alternate streams, which can be used for malicious purposes as well as helpful ones as I’ve demonstrated here.

Understanding the ways information can be encoded surreptitiously in your files can help you identify positive and negative implications.

History

Jan 23 – Initial revision.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

About the Author

Sean Michael Murphy
Product Manager
Canada Canada
Member
I'm a graduate of the University of Toronto with a degree in zoology. I'm currently a software development manager with a large Canadian financial institution, and a passionate squash player.
 
I live with the the three loves of my life; my wife Kim, son Alex and daughter Sarah.
 
I remain fond of my car.

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
QuestionA simple alternative to read and write to ADSmemberefdummy1 Nov '12 - 8:01 
A very simple way to read and write to the Alternate Data Streams (guessing that you only have "normal" characters to write and that performance is not an issue) is to use the command line via a process :
 
        // Read the alternat stream
        static string ReadAlternateStreamToEnd(string fileName, string alternateFileStreamName)
        {
            string IOArg = @"/c more.com <";
            string stdoutString = "";
            AlternateStreamIO(IOArg, fileName, alternateFileStreamName, ref stdoutString);
            return stdoutString;
        }
 
        // Write to the alternate stream
        static void WriteToAlternateStream(string fileName, string alternateFileStreamName, string data)
        {
            string IOArg=@"/c echo " + data + ">";
            string output="";
            AlternateStreamIO(IOArg, fileName, alternateFileStreamName, ref output);
        }
 
        // Process the IO
        static void AlternateStreamIO(string IOArg, string fileName, string alternateStreamName, ref string stdoutString)
        {
            // Throwed Exceptions' messages
            const string MSGEMPTYFILENAME = "*** Empty file name string";
            const string MSGEMPTYSTREAMNAME = "*** Empty stream name";
            const string MSGFILENOTFOUND = "*** File not found";
            const string MSGUNABLETOREADSTREAM = "*** Unable to read alternate stream with the spawned cmd command";
 
            // Params checking
            const string P1NAME = "fileName";
            const string P2NAME = "alternateStreamName";
            if (String.IsNullOrEmpty(fileName)) throw new ArgumentNullException(P1NAME, MSGEMPTYFILENAME);
            if (String.IsNullOrEmpty(alternateStreamName)) throw new ArgumentNullException(P2NAME, MSGEMPTYSTREAMNAME);
            if (!File.Exists(fileName)) throw new FileNotFoundException(MSGFILENOTFOUND + " " + fileName);
 
            // Command that will read or write alternate stream data
            string cmd = "cmd.exe";
            string arg = IOArg + fileName + ":" + alternateStreamName;
 
            // Process that will launch the command
            ProcessStartInfo processStartInfo;
            Process process;
            processStartInfo = new ProcessStartInfo();
            processStartInfo.CreateNoWindow = true;
            processStartInfo.RedirectStandardOutput = true;
            processStartInfo.RedirectStandardInput = true;
            processStartInfo.UseShellExecute = false;
            processStartInfo.Arguments = arg;
            processStartInfo.FileName = cmd;
 
            try
            {
                process = new Process();
                process.StartInfo = processStartInfo;
                process.Start();
                stdoutString = process.StandardOutput.ReadToEnd();
            }
            catch (Exception e)
            {
                throw new ApplicationException(MSGUNABLETOREADSTREAM, e);
            }
        }

GeneralVery nicememberAWdrius11 Sep '09 - 5:35 
That is a really interesting information you gave here. It's one of those times when I'm hit by the enlightenment: "There is still a lot to learn". You have a fiver my friend. All you streams are belong to us!
 
Trust is a weakness.

GeneralADS dissapear...sussbdaniel726 Sep '05 - 21:43 
..if you move the file to different partition..
 
it is only of use for installed applications, in case you don't move them
GeneralRe: ADS dissapear...memberpjd10018 May '06 - 16:27 
What?
 
I move files that have ADS's attached between partitions all the time, and the ADS's do not "disappear"!!!
 
Of course the target has to be an NTFS volume because ADS's are only supported on NTFS partitions.
 

GeneralRe: ADS dissapear...mvpDave Kreskowiak31 Jul '12 - 8:09 
Alternate streams are not supported on FAT32 volumes, so if you copy or move a file to a non-NTFS volume the alternate streams are stripped off.

QuestionDoes Indexing Service see the hidden stream?membergxdata30 Jan '05 - 21:36 
Sean
Your article has come at an opportune time: I'm considering the NTFS streams as the only way to keep metadata with certain types of files, without changing the "main" file stream.
I'm interested in whether IS can "see" this hidden stream for any file, assuming that we use a separate catalog and "instruct" the indexing process to include it (how?), and that the hidden stream file (eg, .XML) has an appropriate iFilter installed.
One can imagine situations where it is useful to hide a "paired" file (or, a stream, in this case) from the users, so that the contents can be accurately maintained - programmatically, by a particular application. It would be awkward if the contents of the (hidden, paired) entity were always visible, within the IS indices and therefore became a thing of interest when a search was done using the Win2K/WinXP/Win2003Server Explorer file search facilities, or one or other of the Desktop Search engines that are becoming popular.
One the other hand - my "HOW?" above - it would of course be useful for the application developer to be able to get IS to index the hidden stream, and then to query the catalog via one of the 3 methods available.
 

 
gxdata
AnswerRe: Does Indexing Service see the hidden stream?memberSean Michael Murphy31 Jan '05 - 2:52 
That is an interesting question to which I do not have a 100% authoritative answer.
 
I am certainly not an Indexing Service guru, but I've run a couple of quick tests that tend to make me think that IS does not index the contents of the hidden streams. I can't speak knowledgeably about whether or not this could be done with more work...
 
gxdata wrote:
One can imagine situations where it is useful to hide a "paired" file (or, a stream, in this case) from the users, so that the contents can be accurately maintained - programmatically, by a particular application. It would be awkward if the contents of the (hidden, paired) entity were always visible, within the IS indices and therefore became a thing of interest when a search was done using the Win2K/WinXP/Win2003Server Explorer file search facilities, or one or other of the Desktop Search engines that are becoming popular.
 
Yes, I can imagine why that is useful, as it was my original problem Smile | :) . Even if the contents of the file were indexed and visible, it doesn't mean the end user could open them for modification (as they could for an externalize Xml/ini file) unless they use unmanaged code and know how to use the ":" syntax. The only way I know of to do discovery of the hidden files is via the CodeProject article I reference near the end of my article.
 
Sean

GeneralRe: Does Indexing Service see the hidden stream?membergxdata31 Jan '05 - 3:07 
Yes, I'm still in the dark about this aspect. I've looked thoroughly through NTFS, file systems, storage, and Index Service 2.0 and 3.0, and have had a quick search of the SharePoint 2003 stuff (all within MSDN Library, January - the MSDN DVD version) and find nothing relevant.
I'm wondering if this is a part of the WinFS...
 
I know this is right off-topic for your article, but it may stimulate someone more knoweldgeable than I, to write a useful article on Indexing Service - there's very little about.
 
As a work-around to achieve what I'm interested in, all I can suggest is to write some sort of Custom property flag to the NTFS Properties stream (and "arrnaged" for it to be indexed by IS), and maintain an extra hidden stream based on that. The latter part uses the techniques that you've described in your article (and I will look more carefully at the other article that you referenced).
 
BTW thanks for the article - I was rude not to say that first off..
 
-- gxdata
GeneralRe: Does Indexing Service see the hidden stream?memberSean Michael Murphy31 Jan '05 - 3:51 
gxdata wrote:
BTW thanks for the article - I was rude not to say that first off..
 
No prob. Thanks for the feedback.
 
gxdata wrote:
As a work-around to achieve what I'm interested in, all I can suggest is to write some sort of Custom property flag to the NTFS Properties stream (and "arrnaged" for it to be indexed by IS), and maintain an extra hidden stream based on that.
 
I fiddled with doing exactly this too, not as a way around indexing service, but as a way to replicate the cool way you can tag files with extra metadata in the Longhorn version of Explorer I saw at PDC '03. It lets you do multi-dimensional organization in a really intuitive way. I was writing it as an extension to the WinXP Explorer, but got sidetracked by work, family, etc. Microsoft will probably have their version out before me...
GeneralRe: Does Indexing Service see the hidden stream?memberpg--az21 Apr '05 - 17:09 
>> I was writing it as an extension to the WinXP Explorer, but got sidetracked by work, family, etc. Microsoft will probably have their version out before me... <<
 
I swear, I'm not a squatter.   My mind is generally in the same rut, and by typing in www.metabag.com you can verify that someone wants $270 for the most obvious name.   OK, so I just bought www.metbag.com from godaddy.   I have actually typed in a few pages of code for C++ class CmetBag::blah etc.  
 
So hopefully YOU have not already typed in several pages of code for YOUR CmetBag !
 
At present, I have these visions of taking over the universe, all over codeproject folks will rant "of course, use a metBag", it IS easy to pronounce, and googling on << metbag NTFS >> fetches no matches.
 
Anyway hopefully this avoids either of us having to go through the painful renaming process.
 
      Thanks,
      PG
 
pg--az

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web04 | 2.6.130523.1 | Last Updated 23 Jan 2005
Article Copyright 2005 by Sean Michael Murphy
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid