Dziś temat, który pojawił się podczas mojej ostatniej sesji na portalu VirtualStudy o plikach (niedługo powinna być dostępna pod tym adresem). Pojawiło się pytanie od słuchaczy, czy za pomocą ogólnodostępnych klas w .NET można dobrać się do Alternate Data Stream. Pytanie to zadałem jako zagadkę dla dociekliwych w zamian za konto VIP. Jako, że konkurs już został rozwiązany można przedstawić to dla szerszego grona.

Czym są Alternate Data Stream?

Jest to mechanizm systemu plików NTFS pozwalający tworzyć dodatkowe strumienie danych związane z danym plikiem (stąd nazwa :)). Jak tego użyć?
ads_cmd
Co tu się dzieje? Na początku wrzucamy treść do pliku plik.txt i wypisujemy na konsolę jego zawartość. Następnie sprawdzamy poleceniem dir aby się upewnić czy udało się zapisać. Następnie wprowadzamy nową zawartość a jako nazwy pliku używamy plik.txt:ads.txt. W ten sposób określamy, że będzie to Alternate Data Stream. Następnie sprawdzamy rozmiar pliku i wypisujemy ADS na ekran. Widzimy, że mimo wprowadzenia tam dodatkowych danych rozmiar nie uległ zmianie!

A jak w .NET?

Niestety użycie w ADS w .NET nie jest takie proste. Niestety nie wystarczy użyć klasy File jak poniżej

File.WriteAllText(“plik.txt:ukryta.txt”, “ukryta zawartość pliku”);

Taki kod da nam wyjątek z informacją, że dany format nie jest wspierany w .NET. A jak z FileStream?

using (FileStream stream = new FileStream(“plik.txt:ukryta.txt”, FileMode.Open))
{
    using (StreamWriter writer = new StreamWriter(stream))
    {
        writer.Write(“ukryta zawartośc pliku”);
    }
}

Podobnie jak poprzednio poinformuje, że nie jest on wspierany! Co zatem nam pozostaje? P/Invoke! Za pomocą strony pinvoke.net importujemy definicje metod CreateFile, WriteFile oraz ReadFile.

[DllImport(“kernel32.dll”)]
static extern bool WriteFile(IntPtr hFile, byte[] lpBuffer,
  uint nNumberOfBytesToWrite, out uint lpNumberOfBytesWritten,
  [In] ref NativeOverlapped lpOverlapped);
[DllImport(“Kernel32.dll”, SetLastError = true, CharSet = CharSet.Auto)]
static extern IntPtr CreateFile(
    string fileName,
    [MarshalAs(UnmanagedType.U4)] FileAccess fileAccess,
    [MarshalAs(UnmanagedType.U4)] FileShare fileShare,
    int securityAttributes,
    [MarshalAs(UnmanagedType.U4)] FileMode creationDisposition,
    int flags,
    IntPtr template);
[DllImport(“kernel32.dll”, SetLastError = true)]
static extern bool ReadFile(IntPtr hFile, [Out] byte[] lpBuffer, uint nNumberOfBytesToRead,
  out uint lpNumberOfBytesRead, [In] ref NativeOverlapped lpOverlapped);

Mając te definicje wystarczy nam z nich tylko skorzystać!

File.WriteAllText(“plik.txt”, “normalna zawartość pliku”);
IntPtr file = CreateFile(“plik.txt:ukryta.txt”, FileAccess.ReadWrite, FileShare.ReadWrite, 0, FileMode.OpenOrCreate, 0, IntPtr.Zero);
byte[] bytes = Encoding.ASCII.GetBytes(“ukryta zawartość pliku”);
uint howMany;
var overlapped = new NativeOverlapped();
WriteFile(file, bytes, (uint)bytes.Length, out howMany, ref overlapped);
byte[] read = new byte[21];
ReadFile(file, read, 20, out howMany, ref overlapped);
Console.WriteLine(Encoding.ASCII.GetString(read));

I voila! Miłego używania Alternate Data Stream!
PS. Zapraszam na swoją kolejną sesję na portalu VirtualStudy podczas, której opowiem o atrybutach i reflection.