    return To index

    Notes: Alternate Data Streams In .NET
    -------------------------------------
    alcopaul/brigada ocho
    june 02, 2011

    "In a computer file system, a fork is metadata associated with a file system object. Depending on the file system, a
    file may have one or more associated forks. Unlike an extended attribute, a similar file system feature which is typically
    limited in size, a fork can be of arbitrary size, possibly even larger than the file's data." - Wikepedia

    So one day I got curious about Alternate Data Streams (Microsoft's version of forks) and how to access them via .NET. I
    searched the internet and found a blogger that posted about it.

    "NTFS streams cannot be opened directly with regular .Net file IO classes, they throw a “System.NotSupportedException: The
    given path’s format is not supported” exception. Here is a C# code sample demonstrating a work-around to the “cannot open
    NTFS streams in .net (using regular classes)” problem…" - Sandeep Datta (http://sandeepdatta.com/?p=19)

    The trick is to create a SafeFileHandle using native api CreateFile, passing to it the full path to the alternate data stream.
    We can then pass the SafeFileHandle to a FileStream then viola, we can now read or write to the alternate data stream. Simple.

    --------------------------------------------------------------------------
    using System.Runtime.InteropServices;

    ...

    [DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
            static extern SafeFileHandle CreateFile(
            string fileName,
            [MarshalAs(UnmanagedType.U4)] FileAccess fileAccess,
            [MarshalAs(UnmanagedType.U4)] FileShare fileShare,
            IntPtr securityAttributes,
            [MarshalAs(UnmanagedType.U4)] FileMode creationDisposition,
            [MarshalAs(UnmanagedType.U4)] FileAttributes flags,
            IntPtr template);

    ...

    private static SafeFileHandle streamzhandle(string filename, FileAccess g)
            {
                SafeFileHandle handlex = CreateFile(filename
                , g
                , FileShare.ReadWrite
                , IntPtr.Zero
                , FileMode.OpenOrCreate
                , FileAttributes.Normal
                , IntPtr.Zero);

                return handlex;

            }
    --------------------------------------------------------------------------

    filename is the full path of the stream (i.e. C:\Documents And Settings\myfile.txt:ggg.exe)
    g is FileAccess type (FileAccess.Read if reading its own contents, FileAccess.ReadWrite if reading or writing to others contents)

    Getting Its Fullpath
    ====================

    So far, we have 2 methods that gets the full path of an executing assembly.

    (1)
    string fullpath = System.Reflection.Assembly.GetExecutingAssembly().GetModules()[0].FullyQualifiedName;

    (2)
    string fullpath = System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName;

    What if we have an executable in an alternate data stream? How can it get its full path when it is executing? Which of the
    2 methods presented above will do the trick?

    Sadly, method (1) will cause the executable to raise an exception. Luckily, method (2) won't.

    So we can use "string fullpath = System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName;" to get the fullpath
    of the executing file inside an alternate data stream. This is useful if we want to read contents from itself.

    Sample Code
    ===========

    Below is a source code that demonstrates reading and writing to alternate data streams.

    1.) First you have to compile this to an executable. "ads.exe"
    2.) Run ads.exe. It will create a data stream within itself "ads.exe:s1.exe"
    3.) Now you can test reading and writing from an alternate data stream by executing "ads.exe:s1.exe"
    4.) In command prompt, type "start [path]\ads.exe:s1.exe" (path is important!!!)
    5.) "ads.exe:s1.exe" will first copy its file to itself (which will raise an exception).
        it will then copy its contents to "ads.exe:s2.exe"
    6.) In command prompt, type "start [path]\ads.exe:s2.exe" (path is important!!!)
    7.) "ads.exe:s2.exe" will first copy its file to itself (which will raise an exception).
        it will then copy its contents to "ads.exe:s1.exe"

    ================================================================================================

    using System;
    using System.IO;
    using Microsoft.Win32.SafeHandles;
    using System.Runtime.InteropServices;
    using System.Diagnostics;

    namespace NTFSStreams
    {

        class Program
        {
            [DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
            static extern SafeFileHandle CreateFile(
            string fileName,
            [MarshalAs(UnmanagedType.U4)] FileAccess fileAccess,
            [MarshalAs(UnmanagedType.U4)] FileShare fileShare,
            IntPtr securityAttributes,
            [MarshalAs(UnmanagedType.U4)] FileMode creationDisposition,
            [MarshalAs(UnmanagedType.U4)] FileAttributes flags,
            IntPtr template);

            private static byte[] Read(FileStream s, int length, int c)
            {
                BinaryReader w33 = new BinaryReader(s);
                w33.BaseStream.Seek(c, SeekOrigin.Begin);
                byte[] bytes2 = new byte[length];
                int numBytesToRead2 = (int)length;
                int numBytesRead2 = 0;
                while (numBytesToRead2 > 0)
                {
                    int n = w33.Read(bytes2, numBytesRead2, numBytesToRead2);
                    if (n == 0)
                        break;
                    numBytesRead2 += n;
                    numBytesToRead2 -= n;
                }
                w33.Close();
                return bytes2;
            }
            private static void WriteX(FileStream s, byte[] g)
            {
                BinaryWriter w = new BinaryWriter(s);
                w.BaseStream.Seek(0, SeekOrigin.Begin);
                w.Write(g);
                w.Flush();
                w.Close();
            }

            private static SafeFileHandle streamzhandle(string filename, FileAccess g)
            {
                SafeFileHandle handlex = CreateFile(filename
                , g
                , FileShare.ReadWrite
                , IntPtr.Zero
                , FileMode.OpenOrCreate
                , FileAttributes.Normal
                , IntPtr.Zero);

                return handlex;

            }

            static void Main(string[] args)
            {

               string curproc = Process.GetCurrentProcess().MainModule.FileName;
               DriveInfo g = new DriveInfo(Path.GetPathRoot(curproc));
               if (g.DriveFormat == "NTFS")
               {
                   FileStream fs54 = new FileStream(streamzhandle(curproc, FileAccess.Read), FileAccess.Read);
                   int iccpx = (int)fs54.Length;
                   byte[] bytes44 = Read(fs54, iccpx, 0);
                   fs54.Close();

                   // check if executing inside streams

                   string readfromfile = "";
                   int count = curproc.Split(':').Length - 1;
                   if (count == 2)
                   {
                       Console.WriteLine("We're in a stream");

                       //try to overwrite itself
                       try
                       {
                           FileStream fs1 = new FileStream(streamzhandle(curproc, FileAccess.ReadWrite), FileAccess.ReadWrite);
                           WriteX(fs1, bytes44);
                           fs1.Close();
                           readfromfile = curproc;
                       }
                       catch
                       {

                           Console.WriteLine("Error writing in current stream. Writing in another stream");
                           if (curproc.IndexOf('1') != -1)
                           {
                               curproc = curproc.Replace('1', '2'); // alternate stream
                           }
                           else if (curproc.IndexOf('2') != -1)
                           {
                               curproc = curproc.Replace('2', '1');

                           }
                           FileStream fs1 = new FileStream(streamzhandle(curproc, FileAccess.ReadWrite), FileAccess.ReadWrite);
                           WriteX(fs1, bytes44);
                           fs1.Close();
                           Console.WriteLine("Self written in : " + curproc);
                           readfromfile = curproc;
                       }
                   }
                   else
                   {
                       string newfilestream = curproc + ":s1.exe";
                       FileStream fs1 = new FileStream(streamzhandle(newfilestream, FileAccess.ReadWrite), FileAccess.ReadWrite);
                       WriteX(fs1, bytes44);
                       fs1.Close();
                       readfromfile = newfilestream;
                   }
                   using (FileStream stream = new FileStream(streamzhandle(readfromfile, FileAccess.Read), FileAccess.Read))
                   {
                       Console.WriteLine("Read from " + readfromfile);
                   }

                   Console.ReadKey();
               }
            }
        }
    }

    =======================================================================================================

    Executing Executables In Alternate Data Stream
    ----------------------------------------------

    1.) in the command prompt, type "start [path]\file.txt:ggg.exe"
    2.) Or in .NET, System.Diagnostics.Process.Start("[path]\file.txt:ggg.exe");