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");