Willkommen auf myCSharp.de! Anmelden | kostenlos registrieren
 | Suche | FAQ

Hauptmenü
myCSharp.de
» Startseite
» Forum
» Suche
» Regeln
» Wie poste ich richtig?

Mitglieder
» Liste / Suche
» Wer ist online?

Ressourcen
» FAQ
» Artikel
» C#-Snippets
» Jobbörse
» Microsoft Docs

Team
» Kontakt
» Cookies
» Spenden
» Datenschutz
» Impressum

  • »
  • Community
  • |
  • Diskussionsforum
[Artikel] Alternative NTFS Datenströme
egrath
myCSharp.de - Member

Avatar #avatar-2119.jpg


Dabei seit:
Beiträge: 937
Herkunft: Österreich / Steyr

Themenstarter:

[Artikel] Alternative NTFS Datenströme

beantworten | zitieren | melden

Alternative NTFS Datenströme

Seit mit Windows NT 3.1 das erste mal das NTFS Dateisystem auf den Markt kam, gibt es bei diesem ein Feature welches bis datto aus diversen Gründen nur sehr spärliche Beachtung gefunden hat: Die alternativen Datenströme.

Auf einem NTFS Datenträger besteht eine Datei aus sogenannten Datenströmen (im folgenden nur mehr Streams genannt). Derer kann eine Datei eine beliebige Anzahl besitzen. Nur der erste Stream allerdings ist für den Endbenutzer direkt sichtbar, alle anderen Streams müssen programmatisch angesprochen werden. Auch wird mit normalen Benutzermitteln immer nur die Grösse des ersten Streams angezeigt.

Grafisch gesehen kann man sich eine Datei auf einem NTFS Datenträger also folgendermassen vorstellen:

+----------------------------------------+
| Direkt sichtbar +---------------------+|
|                 | Unnamed Stream      ||
|                 +---------------------+|
|----------------------------------------|
| Indirekt        +---------------------+|
| sichtbar        | Stream 1            ||
|                 +---------------------+|
|                          ...           |
|                 +---------------------+|
|                 | Stream n            ||
|                 +---------------------+|
+----------------------------------------+

Ansprechen der alternativen Streams

Stream werden grundsätzlich dadurch angesprochen, dass an den Namen der Datei ein Doppelpunkt und anschliessend der Name des Streams angegeben wird. Beispielsweise wird mittels "test.txt:MeinStream" auf den benannten Stream "MeinStream" der Datei "test.txt" zugegriffen. Auf der Kommandozeile können wir dies zu Demonstrationszwecken einfach testen:

C:\>echo Hallo > test.txt
C:\>type test.txt
Hallo
C:\>echo Das ist ein test > test.txt:MeinStream

Wenn wir die Datei nun öffnen (beispielsweise mit Wordpad) dann sehen wir nur den Text "Hallo". Öffnen wir allerdings "test.txt:MeinStream", so sehen wir den Inhalt des benannten Streams "MeinStream". Dies ist keine Applikationsspezifische Sache, sondern wird von den grundlegenden Win32-API Funktionen (insbesondere CreateFile) zur verfügung gestellt.

Im .NET Framework (Version 1.1 und 2.0) ist es leider nicht möglich, direkt auf Streams zuzugreifen, da die entsprechenden Methoden dies nicht unterstützen (Es wird überprüft ob der Dateiname gültige Zeichen enthält, der Doppelpunkt gehört anscheinend beim Framework nicht dazu - eine Exception wird geworfen). Wir müssen daher einen umweg gehen und das File mittels der von der Win32-API zur verfügung gestellten CreateFile Funktion öffnen und den zurückgelieferten Handle für das öffnen mit den .NET Methoden benutzen.

Schreiben und Lesen von Streams unter C#


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

namespace StreamTest
{
    class Program
    {
        [DllImport( "kernel32.dll", SetLastError=true )]
        private static extern IntPtr CreateFile( string fileName, FILE_ACCESS_RIGHTS access, FileShare share, int securityAttributes,
                                                 FileMode creation, FILE_FLAGS flags, IntPtr templateFile );

        [DllImport( "kernel32.dll", SetLastError=true )]
        private static extern bool CloseHandle( IntPtr handle );

        public static void Main( string[] args )
        {
            // Schreiben des Streams
            IntPtr fileHandle = CreateFile( "myFile.txt:myStream1", FILE_ACCESS_RIGHTS.GENERIC_WRITE, FileShare.Write, 0, FileMode.Create, 0, IntPtr.Zero );
            TextWriter writer = new StreamWriter( new FileStream( new SafeFileHandle( fileHandle, true ), FileAccess.Write ));

            writer.WriteLine( "Ich bin ein Stream" );
            writer.Close();
            CloseHandle( fileHandle );

            // Lesen des Streams
            fileHandle = CreateFile( "myFile.txt:myStream1", FILE_ACCESS_RIGHTS.GENERIC_READ, FileShare.Read, 0, FileMode.Open, 0, IntPtr.Zero );
            TextReader reader = new StreamReader( new FileStream( new SafeFileHandle( fileHandle, true ), FileAccess.Read ));

            Console.Out.WriteLine( reader.ReadToEnd() );
            reader.Close();
            CloseHandle( fileHandle );
        }

        private enum FILE_ACCESS_RIGHTS : uint
        {
            GENERIC_READ = 0x80000000,
            GENERIC_WRITE = 0x40000000
        }

		private enum FILE_FLAGS : uint
		{
			WriteThrough = 0x80000000,
			Overlapped = 0x40000000,
			NoBuffering = 0x20000000,
			RandomAccess = 0x10000000,
			SequentialScan = 0x8000000,
			DeleteOnClose = 0x4000000,
			BackupSemantics = 0x2000000,
			PosixSemantics = 0x1000000,
			OpenReparsePoint = 0x200000,
			OpenNoRecall = 0x100000
		}
    }
}

Anzeigen der Streams einer Datei

Wenn wir wissen wie die benannten Streams einer Datei heissen, so haben wir kein Problem mit dem ansprechen dieser. Nur das herausfinden ob und welche Streams eine Datei hat ist eine Sache welche mit Bordeigenen Windows-Mitteln nicht so einfach zu bewerkstelligen ist. Grundsätzlich gibt es zwei möglichkeiten:
  • API Funktion NtQueryInformationFile
  • API Funktion BackupRead und BackupSeek

Wobei die erste Methode nicht zu empfehlen ist, da es sich dabei um einen nicht dokumentieren Systemcall handelt. Letzterer hat dafür den Nachteil dass man für das eruieren der Streamnamen die komplette Datei lesen muss. Erst mit Windows Vista wird eine API Funktionalität eingeführt mit der diese Lücke geschlossen wird (FindFirstStreamW und FindNextStreamW)

Für unser Beispiel werden wir auf die BackupRead Methode zurückgreifen und damit folgenden Informationen eruieren:
  • Name des Streams
  • Type des Streams
  • Grösse des Streams

Eruieren der Streams einer Datei unter C#


using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;

namespace AlternateDataStreams
{
    public class Program
    {
        public static void Main( string[] args )
        {
            if( args.Length < 1 ) return;

            List<DataStreamInfo> streams = EnumerateDataStreams.GetFileStreams( args[0] );
            if( streams.Count == 0 )
            {
                Console.Out.WriteLine( "File [{0}] has no alternate Data Streams", args[0] );
            }
            else
            {
                foreach( DataStreamInfo s in streams )
                {
                    Console.Out.WriteLine( "Name: {0}, Length = {1}, Type = {2}", s.StreamName, s.StreamSize, s.StreamType );
                }
            }
        }
    }

    internal static class EnumerateDataStreams
    {
        [DllImport("kernel32.dll")]
        private static extern bool BackupRead(SafeFileHandle handle, IntPtr pBuffer, int lBytes, ref int lRead, bool bAbort, bool security, ref int context);

        [DllImport("kernel32.dll")]
        private static extern bool BackupRead(SafeFileHandle handle, ref WIN32_STREAM_ID pBuffer, int lBytes, ref int lRead, bool bAbort, bool security, ref int context);

        [DllImport( "kernel32.dll" )]
        private static extern bool BackupSeek( SafeFileHandle handle, int dwLowBytesToSeek, int dwHighBytesToSeek, ref int dwLow, ref int dwHigh, ref int context );

        private struct LARGE_INTEGER
        {
            public int Low;
            public int High;

            public long QuadPart()
            {
                return (long)High * 4294967296 + (long)Low;
            }
        }

        private struct WIN32_STREAM_ID
        {
            public int streamId;
            public int streamAttributes;
            public LARGE_INTEGER size;
            public int streamNameSize;
        }

        private enum StreamType : int
        {
            BACKUP_ALTERNATE_DATA = 0x00000004,
            BACKUP_DATA = 0x0000001,
            BACKUP_EA_DATA = 0x00000002,
            BACKUP_LINK = 0x00000005,
            BACKUP_OBJECT_ID = 0x00000007,
            BACKUP_PROPERTY_DATA = 0x00000006,
            BACKUP_REPARSE_DATA = 0x00000008,
            BACKUP_SECURITY_DATA = 0x00000003,
            BACKUP_SPARSE_BLOCK = 0x00000009
        }

        public static List<DataStreamInfo> GetFileStreams( string fileName )
        {
            List<DataStreamInfo> streams = new List<DataStreamInfo>();
            FileStream fileStream = new FileStream( fileName, FileMode.Open );

            try
            {
                WIN32_STREAM_ID streamId = new WIN32_STREAM_ID();
                int streamHeaderSize  = Marshal.SizeOf( streamId );
                int context = 0;
                bool hasMore = true;

                while( hasMore )
                {
                    int bytesRead = 0;
                    hasMore = BackupRead( fileStream.SafeFileHandle, ref streamId, streamHeaderSize, ref bytesRead, false, false, ref context);

                    if( hasMore && bytesRead == streamHeaderSize )
                    {
                        if( streamId.streamNameSize > 0 )
                        {
                            bytesRead = 0;
                            IntPtr pName = Marshal.AllocHGlobal( streamId.streamNameSize );
                            try
                            {
                                BackupRead(fileStream.SafeFileHandle, pName, streamId.streamNameSize, ref bytesRead, false, false, ref context);
                                char[] bName = new char[streamId.streamNameSize];
                                Marshal.Copy( pName, bName, 0, streamId.streamNameSize );

                                string sName = new string(bName);
                                int index = sName.IndexOf( ':', 1);
                                if( index > -1) sName = sName.Substring( 1, index -1 );

                                DataStreamInfo streamInfo = new DataStreamInfo();
                                streamInfo.StreamName = sName;
                                streamInfo.StreamSize = streamId.size.QuadPart();
                                switch( streamId.streamId )
                                {
                                    case ( int ) StreamType.BACKUP_ALTERNATE_DATA:
                                        streamInfo.StreamType = "Alternative Data Stream";
                                        break;

                                    case ( int ) StreamType.BACKUP_DATA:
                                        streamInfo.StreamType = "Standard Data";
                                        break;

                                    case ( int ) StreamType.BACKUP_EA_DATA:
                                        streamInfo.StreamType = "Extended attribute Data";
                                        break;

                                    case ( int ) StreamType.BACKUP_LINK:
                                        streamInfo.StreamType = "Hard link information";
                                        break;

                                    case ( int ) StreamType.BACKUP_OBJECT_ID:
                                        streamInfo.StreamType = "Object identifiers";
                                        break;

                                    case ( int ) StreamType.BACKUP_PROPERTY_DATA:
                                        streamInfo.StreamType = "Property data";
                                        break;

                                    case ( int ) StreamType.BACKUP_REPARSE_DATA:
                                        streamInfo.StreamType = "Reparse points";
                                        break;

                                    case ( int ) StreamType.BACKUP_SECURITY_DATA:
                                        streamInfo.StreamType = "Security descriptor data";
                                        break;

                                    case ( int ) StreamType.BACKUP_SPARSE_BLOCK:
                                        streamInfo.StreamType = "Sparse file";
                                        break;
                                }

                                streams.Add( streamInfo );
                            }
                            finally
                            {
                                Marshal.FreeHGlobal(pName);

                            }
                        }

                        int low = 0;
                        int high = 0;
                        BackupSeek( fileStream.SafeFileHandle, streamId.size.Low, streamId.size.High, ref low, ref high, ref context );
                    }
                    else
                    {
                        break;
                    }
                }
            }
            catch
            {
                return null;
            }
            finally
            {
                fileStream.Close();
            }

            return streams;
        }
    }

    internal struct DataStreamInfo
    {
        private string m_StreamName;
        private long m_StreamSize;
        private string m_StreamType;

        public string StreamType
        {
            get
            {
                return m_StreamType;
            }

            set
            {
                m_StreamType = value;
            }
        }

        public string StreamName
        {
            get
            {
                return m_StreamName;
            }

            set
            {
                m_StreamName = value;
            }
        }

        public long StreamSize
        {
            get
            {
                return m_StreamSize;
            }

            set
            {
                m_StreamSize = value;
            }
        }
    }
}

/edit: Die beiden Sourcen sind als ZIP File im anhang enthalten
/edit: Ein paar Tippfehler beseitigt.
Attachments
private Nachricht | Beiträge des Benutzers
Timur Zanagar
myCSharp.de - Member

Avatar #avatar-3412.jpg


Dabei seit:
Beiträge: 1559

beantworten | zitieren | melden

Hi,

Ich finde deinen Artikel sehr interessant. Möchte aber wissen in welchen Bereichen dies verwendet wird? Welchen nutzen haben die Datenströme?

Vielen Dank im Voraus.
private Nachricht | Beiträge des Benutzers
Fabian
myCSharp.de - Member

Avatar #avatar-1590.jpg


Dabei seit:
Beiträge: 1994
Herkunft: Dortmund

beantworten | zitieren | melden

Hallo zusammen,

ich schließe mich der Frage von burning snow an, da mir das auch noch nicht ganz klar ist. Aber sehr interessanter Artikel!


Gruß,
Fabian
"Eine wirklich gute Idee erkennt man daran, dass ihre Verwirklichung von vornherein ausgeschlossen erscheint." (Albert Einstein)

Gefangen im magischen Viereck zwischen studieren, schreiben, lehren und Ideen umsetzen…

Blog: www.fabiandeitelhoff.de
private Nachricht | Beiträge des Benutzers
egrath
myCSharp.de - Member

Avatar #avatar-2119.jpg


Dabei seit:
Beiträge: 937
Herkunft: Österreich / Steyr

Themenstarter:

beantworten | zitieren | melden

Hallo burning snow,

beispielsweise werden Datenströme dafür benutzt um zusätzliche Metadaten für Dateien zu speichern - Windows nutzt dies unter anderem dafür wenn du Informationen zu einer Datei speicherst (Eigenschaften einer Datei->Summary, dann sachen wie Author usw eintragen). Wird dann als Named Stream "SummaryInformation" zu einer Datei gespeichert.

Desweiteren könnte man es beispielsweise dazu benutzen Informationen in einer Datei zu "verstecken": Neue Datei mit 0 Bytes anlegen und in alternativen Stream was reinschreiben. Mit herkömmlichen Mitteln sieht man nur eine leere Datei wenn man nicht weiss dass da ein Stream dranhängt (Da im Explorer nur die Grösse des ersten, unnamed Streams angezeigt wird)

Grüsse, Egon
private Nachricht | Beiträge des Benutzers
Timur Zanagar
myCSharp.de - Member

Avatar #avatar-3412.jpg


Dabei seit:
Beiträge: 1559

beantworten | zitieren | melden

Vielen Dank für die Aufklärung. Genau das hat mir noch gefehlt an Infos.
private Nachricht | Beiträge des Benutzers
MagicAndre1981
myCSharp.de - Member

Avatar #avatar-2623.jpg


Dabei seit:
Beiträge: 913
Herkunft: Nordhausen

beantworten | zitieren | melden

Kaspersky legt auch ADS an, damit es schneller scannen kann.

btw, kannst du die Sourcen anhängen, ist sonst so umstaänlich mit dem Kopieren.
private Nachricht | Beiträge des Benutzers
Borg
myCSharp.de - Member



Dabei seit:
Beiträge: 1548
Herkunft: Berlin, Germany

beantworten | zitieren | melden

ADS werden auch für das Zonenmodell des IE genutzt. An eine Datei wird in einem ADS die Info gehangen, aus welcher Zone die Datei kommt. Dies verursacht dann die Dialogbox "Datei öffnen - Sicherheitswarnung".

Ansonsten muss man sich jedoch bewußt sein, dass ADS auch eine potentielle Sicherheitslücke ist. So konnten beim letzten Virenscannertest in der c't viele AVScanner nicht mit ADS umgehen.
Desweiteren kann weder der Explorer noch der dir-Befehl ADS anzeigen.
Daher kann ich auch mit einer für den Nutzer scheinbar 1Byte großen Datei die gesamte Festplatte füllen.
Also: acht geben, wofür man die ADS nutzt und dem Nutzer immer (spätestens bei der Deinstallation) die Möglichkeit gewähren, alle erzeugten ADS auch wieder zu entfernen.

Moderationshinweis von herbivore (25.10.2012 - 08:19:12):

Zitat
Desweiteren kann weder der Explorer noch der dir-Befehl ADS anzeigen.
Diese Aussage war zum Zeitpunkt des Beitrags korrekt und bleibt es auch für Windows XP und Versionen davor. Ab Windows Vista gibt es bei beim Befehl dir die Option /r zum Anzeigen der ADS.

private Nachricht | Beiträge des Benutzers
Timur Zanagar
myCSharp.de - Member

Avatar #avatar-3412.jpg


Dabei seit:
Beiträge: 1559

beantworten | zitieren | melden

@Borg:
Da hast du wohl recht. Das kann auch nach hinten losgehen.
private Nachricht | Beiträge des Benutzers
nic4x4
myCSharp.de - Member

Avatar #avatar-1983.jpg


Dabei seit:
Beiträge: 191
Herkunft: NRW

beantworten | zitieren | melden

Was passiert denn eigentlich, wenn man so eine Datei mit winrar packt?
Werden da auch die angehängten Daten mitgepackt oder nur der erste "Stream"?
private Nachricht | Beiträge des Benutzers
Borg
myCSharp.de - Member



Dabei seit:
Beiträge: 1548
Herkunft: Berlin, Germany

beantworten | zitieren | melden

Schau dir mal den Advanced-Tab beim Erstellen von Archiven an...
private Nachricht | Beiträge des Benutzers
svenson
myCSharp.de - Member



Dabei seit:
Beiträge: 8775
Herkunft: Berlin

beantworten | zitieren | melden

Richtig übel: Befindet sich innerhalb einer EXE eine ADS-Exe, wird in den ProcessViewern nur der Hüllenname angezeigt. Hier ein schöner Artikel. Könnte auch erscheinen unter dem Namen: Viren und Trojaner verstecken leicht gemacht (und das nur mit der DOS-Box!):

Hidden Threat: Alternate Data Streams

ADS gehört m.E. aus Windows entfernt oder transparent gemacht (Explorer, leicht zu nutzende API, etc.).
private Nachricht | Beiträge des Benutzers
Borg
myCSharp.de - Member



Dabei seit:
Beiträge: 1548
Herkunft: Berlin, Germany

beantworten | zitieren | melden

Bei mir zeigt der Taskmanager den vollen Namen an (also mit ADS).
private Nachricht | Beiträge des Benutzers
herbivore
myCSharp.de - Experte

Avatar #avatar-2627.gif


Dabei seit:
Beiträge: 52329
Herkunft: Berlin

beantworten | zitieren | melden

Hallo zusammen,

es gibt folgendes Statement von Microsoft zum Support von ADS in Windows 8:
Zitat von Microsoft
The file system that you normally use is NTFS.

Alternate data streams are still supported in NTFS for Windows 8.

The new file system that we will also be providing in Windows 8 is ReFS.

ReFS doesn't support Alternate data streams.

Außerdem sollte man wissen, das ADS beim Kopieren auf FAT32 verloren gehen, also nicht mit kopiert werden, weil FAT32 kein ADS unterstützt. Entsprechendes wird also auch beim Kopieren auf ReFS gelten.

Aus diesen Gründen sollte man wirklich wichtige, unbedingt erforderliche Informationen nicht in ADS speichern, sondern nur entbehrliche oder rekonstruierbare Zusatzinformationen.

Siehe auch Alternativer Datenstrom.

herbivore

PS: Unter OS/2 wurden ADS häufiger genutzt. Zum Beispiel hat mein favorisierter Editor die Cursorposition beim letzten Speichern in einem ADS abgelegt, so dass man beim erneuten Öffnen dort weiter machen konnte, wo man aufgehört hat. Sowas ist ein guter Anwendungsfall für ADS, denn wenn diese Information doch mal verloren geht, ist es auch nicht weiter tragisch.
private Nachricht | Beiträge des Benutzers
vitafit
myCSharp.de - Member

Avatar #avatar-3270.png


Dabei seit:
Beiträge: 25
Herkunft: Hessen

beantworten | zitieren | melden

Zitat von Borg
Desweiteren kann weder der Explorer noch der dir-Befehl ADS anzeigen.
Daher kann ich auch mit einer für den Nutzer scheinbar 1Byte großen Datei die gesamte Festplatte füllen.
Also: acht geben, wofür man die ADS nutzt und dem Nutzer immer (spätestens bei der Deinstallation) die Möglichkeit gewähren, alle erzeugten ADS auch wieder zu entfernen.

Da muss ich dir wiedersprechen - dir /R offenbart dir sofort, ob ein Stream in der Datei versteckt ist - diese lassen sich auch ohne Probleme (eben im Selbsttest Windows 8 ausprobiert) öffnen :)

Moderationshinweis von herbivore (19.10.2012 - 07:46:32):

Die Option /r gibt es ab Windows Vista, welches am am 30. Januar 2007 veröffentlicht wurde. Die Aussage von Borg stammt vom 25.10.2006, war also zu diesem Zeitpunkt richtig und bleibt es auch für Windows Versionen vor Vista, also insbesondere für XP.

private Nachricht | Beiträge des Benutzers