Are you familiar with the feature of NTFS called Alternate Data Streams? Our typical usage of files is pretty simple. We double click it and it opens. But by default we are only accessing the “default” data stream. We can write to multiple data streams, effectively storing multiple files in a single file. These alternate streams are generally hidden, but we can see them and even write to them. I’ll show you how to do it from the command line and from C#.
Even if you have never heard of this feature, you have probably used it. On Windows, when you download a file the OS automatically writes the Internet Zone that the file came from to an alternate stream. This is what you manipulate with the “Unblock” checkbox:
You can view the details of the stream with a simple dir command that shows the stream name and number of bytes it contains:
dir /R
And, you can view the data with notepad by specifying the file name with its Alternate Data Stream:
notepad "DotNetCore.1.0.0-VS2015Tools.Preview2.exe:Zone.Identifier"
Writing text to an Alternate Data Stream is pretty simple. The command line supports this, all you have to do is provide the name you want to give the stream:
echo I'm writing a new stream ! > someExistingFile.txt:YourNameChoiceHere
That’s pretty interesting in itself, but we are not limited to writing text. We can write any data. The following C# program will write text to the default stream, an image to one alternate stream and finally a PDF to another. Unfortunately C# doesn’t have native support for this so we have to p/Invoke a bit:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using Microsoft.Win32.SafeHandles; | |
using System; | |
using System.IO; | |
using System.Runtime.InteropServices; | |
namespace AlternateDataStreams | |
{ | |
class Program | |
{ | |
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)] | |
public static extern SafeFileHandle CreateFile( | |
string lpFileName, | |
EFileAccess dwDesiredAccess, | |
EFileShare dwShareMode, | |
IntPtr lpSecurityAttributes, | |
ECreationDisposition dwCreationDisposition, | |
EFileAttributes dwFlagsAndAttributes, | |
IntPtr hTemplateFile); | |
static void Main(string[] args) | |
{ | |
string basePath = @"c:\Users\tekhe\temp\"; | |
string baseFile = "funwithfiles.txt"; | |
//First create a vanilla text file | |
File.WriteAllText(Path.Combine(basePath, baseFile), "This is the normal, unnamed data stream."); | |
//Write an image to the ADS | |
CreateFileWithAlternateDataStream(basePath, baseFile, ":TheKitten", "kitten.jpg"); | |
//Write a PDF to the ADS | |
CreateFileWithAlternateDataStream(basePath, baseFile, ":PDFSample", "pentest.pdf"); | |
Console.WriteLine("Done"); | |
Console.ReadKey(); | |
} | |
static void CreateFileWithAlternateDataStream(string basePath, string baseFile, string streamName, string fileToWrite) | |
{ | |
var sfh = CreateFile(basePath + baseFile + streamName, | |
EFileAccess.GenericRead | EFileAccess.GenericWrite, | |
EFileShare.Read, | |
IntPtr.Zero, | |
ECreationDisposition.CreateAlways, | |
EFileAttributes.Normal, | |
IntPtr.Zero); | |
if (sfh.IsInvalid) | |
{ | |
Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error()); | |
} | |
using (FileStream fs = new FileStream(sfh, FileAccess.Write)) | |
{ | |
byte[] filebytes = File.ReadAllBytes(Path.Combine(basePath, fileToWrite)); | |
fs.Write(filebytes, 0, filebytes.Length); | |
} | |
sfh.Close(); | |
} | |
} | |
} | |
[Flags] | |
enum EFileAccess : uint | |
{ | |
GenericRead = 0x80000000, | |
GenericWrite = 0x40000000, | |
GenericExecute = 0x20000000, | |
GenericAll = 0x10000000 | |
} | |
[Flags] | |
public enum EFileShare : uint | |
{ | |
None = 0x00000000, | |
Read = 0x00000001, | |
Write = 0x00000002, | |
Delete = 0x00000004 | |
} | |
public enum ECreationDisposition : uint | |
{ | |
New = 1, | |
CreateAlways = 2, | |
OpenExisting = 3, | |
OpenAlways = 4, | |
TruncateExisting = 5 | |
} | |
[Flags] | |
public enum EFileAttributes : uint | |
{ | |
Normal = 0x00000080 | |
} |
After running the program (be sure to edit you paths and files appropriately), the following 3 commands ….
c:\Users\tekhe\temp>"C:\Program Files (x86)\Foxit Software\Foxit Reader\FoxitReader.exe" funwithfiles.txt:PDFSample c:\Users\tekhe\temp>notepad funwithfiles.txt c:\Users\tekhe\temp>mspaint funwithfiles.txt:TheKitten
… open the following files, read from the default and Alternate Data Streams: