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