Announcement

Collapse
No announcement yet.

Intro to Alternate Data Streams (virtually invisible files)

Collapse
X
 
  • Filter
  • Time
  • Show
Clear All
new posts

  • Intro to Alternate Data Streams (virtually invisible files)

    NTFS Alternate Data Streams (ADS, first introduced in WinNT 3.1, and available on WinNT4 and Win2K) is a little known and mostly undocumented NTFS feature that provides a means of modifying file data without actually changing the original image of the file - that is, you can save multiple images of the one file, using just the one filename. In effect it is almost like hiding multiple files behind one parent file.

    The following demo creates three files, although only one is actually visible (even using command prompt "dir" listing). The first file created is the actual parent file, called "stream.txt" in this demo. This is the visible file. The two other files that get created with it are ADS files, and as such they are - for all intensive purposes - completely invisible, even though they hold unique data. Even anti-virus scanners don't (yet) scan in this area (although ones that scan files on disk-writes should detect them).

    How do you create an ADS stream file? Easy - just open it with ":StreamIdentifier" appended to the end of the parent filename (where "StreamIdentifier" can be any unique alphanumeric string). If you open an ADS file that doesn't have a parent, the parent will be created as a 0-byte file.

    Please don't abuse this. It is a fantastic feature to use for protecting your own programs and data files - for example, it makes it very hard for a trojan to corrupt your program files if the files are ADS files, as unless it knows the specific parent filename _AND_ StreamIdentifier the trojan won't be able to find your files. Also, it's kind of cute to look in your program directory and only see the exe and no other files! Another great use of ADS files is multiple backups! You could even write your own file-protection system such as the one used by Win2K - if a file is changed, you just go back to a previous ADS stream and restore that image. It's just too good to abuse! (but also too easy )

    Enjoy!


    Code:
    '[b]ADS_STREAMS.BAS[/b] - NTFS Alternate Data Streams demo, by Wayne Diamond
    'Requires NT3.51, NT4, Win2K or higher.
     
    #COMPILE EXE "streams.exe"
     
    $STREAMDIR = "C:\Streams"
      
    FUNCTION PBMAIN() AS LONG
    ON ERROR RESUME NEXT
    DIM TempStr AS STRING * 6
    '// Create and change directory to $STREAMDIR
     MKDIR $STREAMDIR
     CHDRIVE LEFT$($STREAMDIR,1)
     CHDIR "\"
     CHDIR RIGHT$($STREAMDIR, LEN($STREAMDIR) - 2)
     
     '// Create the PARENT file
     TempStr = "Parent"
     OPEN $STREAMDIR & "\stream.txt" FOR BINARY ACCESS WRITE LOCK SHARED AS #1
      PUT #1, 1, TempStr
     CLOSE #1
     
     '// Create first STREAM file 'under' the parent file
     TempStr = "File 1"
     OPEN $STREAMDIR & "\stream.txt:s1" FOR BINARY ACCESS WRITE LOCK SHARED AS #1
      PUT #1, 1, TempStr
     CLOSE #1
     
     '// Create second stream file
     TempStr = "File 2"
     OPEN $STREAMDIR & "\stream.txt:s2" FOR BINARY ACCESS WRITE LOCK SHARED AS #1
      PUT #1, 1, TempStr
     CLOSE #1
     
     '// Now READ the data from our 3 files (note that only 1 of these files is visible in directory listings)
     OPEN $STREAMDIR & "\stream.txt" FOR BINARY ACCESS READ LOCK SHARED AS #1
      GET #1, 1, TempStr
     CLOSE #1
     IF TempStr <> "Parent" THEN
        STDOUT "This machine does not support NTFS Alternate Data Streams!"
        EXIT FUNCTION
     END IF
     STDOUT "Data in parent file: " & TempStr
     
     OPEN $STREAMDIR & "\stream.txt:s1" FOR BINARY ACCESS READ LOCK SHARED AS #1
      GET #1, 1, TempStr
     CLOSE #1
     STDOUT "Data in 1st stream file: " & TempStr
     
     OPEN $STREAMDIR & "\stream.txt:s2" FOR BINARY ACCESS READ LOCK SHARED AS #1
      GET #1, 1, TempStr
     CLOSE #1
     STDOUT "Data in 2nd stream file: " & TempStr
     
     STDOUT "---"
     STDOUT "Press any key to delete all 3 files ... ";
     WAITKEY$
     KILL $STREAMDIR & "\stream.txt"
     'As this is just a demo and we don't want to leave behind any rubbish, we'll tidy up after ourselves..
     'As the ADS files are invisible they can't really be deleted any other way
     KILL $STREAMDIR & "\stream.txt:s1"
     KILL $STREAMDIR & "\stream.txt:s2"
    END FUNCTION

    [This message has been edited by Wayne Diamond (edited April 16, 2001).]
    -

  • #2
    Here is a simple demo that basically returns a yes or no as to whether or not NTFS Alternate Data Streaming is supported by your system

    Code:
    #COMPILE EXE "ads-test.exe"
     
    $ADS_STREAM_FILE = "c:\autoexec.bat:ADSTest"   'uses c:\autoexec.bat, as that is likely to exist
     
    FUNCTION PBMAIN() AS LONG
    ON ERROR RESUME NEXT
    DIM strTemp AS STRING
     OPEN $ADS_STREAM_FILE FOR OUTPUT AS #1
      PRINT #1, "ADS"
     CLOSE #1
     OPEN $ADS_STREAM_FILE FOR INPUT AS #1
      LINE INPUT #1, strTemp
     CLOSE #1
     KILL $ADS_STREAM_FILE
     IF LEFT$(strTemp,3) = "ADS" THEN
        STDOUT "YES! NTFS Alternate Data Streams is working"
     ELSE
        STDOUT "Nope - NTFS Alternate Data Streams is not supported by this machine"
     END IF
    END FUNCTION

    ------------------
    -

    Comment


    • #3
      I get "Nope" on my NT4, 2000, and 98 test systems.

      [Added later]

      Oops never mind... all of those systems use FAT, not NTFS.

      -- Eric

      ------------------
      Perfect Sync Development Tools
      Perfect Sync Web Site
      Contact Us: mailto:[email protected][email protected]</A>

      [This message has been edited by Eric Pearson (edited April 16, 2001).]
      "Not my circus, not my monkeys."

      Comment


      • #4
        Kevin,
        Yep it's for NTFS only sorry It works on my NT4 workstation, NT4 server, NT4 SBS server, and Win2K workstation -- not the Win98 box though, so naturally if youre going to use ADS streams to hide your data, make sure you initiate a quick test to make sure you can read/write from a stream file first - if you cant, then proceed as you normally would, otherwise just point at the streamed images instead

        Cheers,
        Wayne


        [This message has been edited by Wayne Diamond (edited April 16, 2001).]
        -

        Comment


        • #5
          Last example - putting it to more practical use. Most programs have supporting datafiles - such as configuration files, database files etc, so although you have a standalone program, after running it you may have a config file or two so that your settings are remembered.

          This demo uses ADS streams to hide its config files -- and cheekily uses its own executable as the parent file, so no other files need to be created
          If it fails to open the ADS stream file, it will revert to using the normal filenames, so it is FAT and NTFS (and OS) friendly, even though it will only hide its config files on NTFS.

          The crux of it is this:
          Code:
           '---
             OPEN ExeName & ":" & $CONFIGFILE FOR OUTPUT AS #1   'Try to open ADS stream file
             IF ERR <> 0 THEN OPEN $CONFIGFILE FOR OUTPUT AS #1  'If it failed, simply use normal filename instead - too easy!
           '---
          Code:
          #COMPILE EXE "adsprog.exe"
          #INCLUDE "win32api.inc"
           
          FUNCTION ExeName() AS STRING
          ON ERROR RESUME NEXT
            LOCAL hModule AS LONG
            LOCAL buffer  AS ASCIIZ * 256
            hModule = GetModuleHandle(BYVAL 0&)
            GetModuleFileName hModule, Buffer, 256
            FUNCTION = Buffer
          END FUNCTION
            
          $CONFIGFILE1 = "file1.cfg"
          $CONFIGFILE2 = "file2.cfg"
             
          FUNCTION PBMAIN() AS LONG
            ON ERROR RESUME NEXT
            '// Dummy Config file #1
            OPEN ExeName & ":" & $CONFIGFILE1 FOR OUTPUT AS #1    'Try ADS stream file
            IF ERR <> 0 THEN
               STDOUT "Wrote to normal file " & $CONFIGFILE1
               OPEN $CONFIGFILE1 FOR OUTPUT AS #1   'If failed, use normal file
            ELSE
               STDOUT "Wrote to ADS stream " & ExeName & ":" & $CONFIGFILE1
            END IF
            CLOSE #1
              
            '// Dummy Config file #2
            OPEN ExeName & ":" & $CONFIGFILE2 FOR OUTPUT AS #1
            IF ERR <> 0 THEN
               STDOUT "Wrote to normal file " & $CONFIGFILE2
               OPEN $CONFIGFILE2 FOR OUTPUT AS #1   'If failed, use normal file
            ELSE
               STDOUT "Wrote to ADS stream " & ExeName & ":" & $CONFIGFILE2
            END IF
            CLOSE #1
          END FUNCTION

          [This message has been edited by Wayne Diamond (edited April 16, 2001).]
          -

          Comment


          • #6
            Wow! very nifty posibilities. I just made an ADS file and copied it to another drive and it even worked there. Where can I read up more on this little gem?

            ------------------
            Cheers!

            Comment


            • #7
              One thing I just found is that kill on an ADS file files with error 75. So how do you delete an ADS file from a normal parent file then. Also, how would you get a directory listing of ADS files attached to a parent file?

              ------------------
              Cheers!

              Comment


              • #8
                Mark,

                Use www.google.com, and just do such searches as "NTFS streams" (with speechmarks).

                Thanks for your sharp observation on the err75 when killing stream files.
                There are some special attributes to be aware of with streams:
                - You CANT use DIR$() to detect a stream file
                - You CANT use KILL() to kill a stream file, you must kill it's parent.
                - You CAN use FILECOPY() to copy a stream file to either a normal file, or another stream
                - Killing a parent automatically kills all stream files under that parent.
                - Renaming a parent automatically 'renames' all child stream files under that.
                - The only known way to enumerate streams under a file is by using BackupRead() with SeBackupPrivilege enabled (needless to say, you must be logged in as Admin)

                Here's another demo which demonstrates copying and deleting streams.
                1. It creates a parent file "stream1.txt", with a stream under it - "stream1.txt:Stream".
                2. It then does a simply, standard call to FILECOPY "stream1.txt", "stream2.txt" -- doing this automatically copies the streams of stream1.txt over to stream2.txt, so after this call we have four files:
                stream1.txt, stream1.txt:Stream, stream2.txt, stream2.txt:Stream
                3. It then calls KILL "stream1.txt", which automatically kills stream1.txt and all of it's child streams (which is just "stream1.txt:Stream" in this case)
                4. It then reads from "stream2.txt:Stream" and displays that data

                Code:
                #COMPILE EXE
                #INCLUDE "win32api.inc"
                 
                $PARENTFILE = "stream1.txt"
                 
                FUNCTION PBMAIN() AS LONG
                  ON ERROR RESUME NEXT
                  DIM TempStr AS STRING
                  'Create a parent, and a stream under it
                  OPEN $PARENTFILE  FOR OUTPUT AS #1
                   PRINT #1, "PARENT - Im the parent - kill me and you'll take my children with me!"
                  CLOSE #1
                  OPEN $PARENTFILE & ":Stream" FOR OUTPUT AS #1
                   PRINT #1, "CHILD - Im the stream - you cant kill me, youll have to kill my parent!"
                  CLOSE #1
                   
                  'Now copy the parent file - stream1.txt, to stream2.txt.  Note how its stream(s) are copied across with it, so "stream1.txt:Stream" is copied automatically as "stream2.txt:Stream"
                  FILECOPY $PARENTFILE, "stream2.txt"
                  
                  'Now kill the original. By doing this, we're also killing "stream1.txt:Stream"
                  KILL $PARENTFILE
                
                  'But because we copied the parent across first, we can still access our newly-copied stream!
                  OPEN "stream2.txt:Stream" FOR INPUT AS #1
                   LINE INPUT #1, TempStr
                   STDOUT "TempStr=" & TempStr
                  CLOSE #1
                   
                  STDOUT "Press any key to clean up..."
                  WAITKEY$
                  'Wait for user to press a key (allows them to inspect the directory for changes etc).
                  'Then the two parent files (which automatically kills their child streams)
                  KILL $PARENTFILE
                  KILL "stream2.txt"
                END FUNCTION

                ------------------
                -

                Comment


                • #9
                  This is possibly the only C++ source of enumerating child streams out of a file.
                  It seems fairly simple, and every declaration required is already in win32api.inc, but its too C++ish for me to port -- if anyone is able to port this to PB, I would be forever grateful!

                  Code:
                  #include <windows.h>
                  #include <stdio.h>
                  #pragma hdrstop
                  #define err doerr( __FILE__, __LINE__ )
                   
                  void doerr( const char *file, int line )
                  {
                  	DWORD e;
                  	e = GetLastError();
                  	if ( e == 0 )
                  		return;
                  	printf( "%s(%d): gle = %lu\n", file, line, e );
                  	exit( 2 );
                  }
                   
                  void enableprivs()
                  {
                  	HANDLE hToken;
                  	byte buf[sizeof TOKEN_PRIVILEGES * 2];
                  	TOKEN_PRIVILEGES & tkp = *( (TOKEN_PRIVILEGES *) buf );
                  
                  	if ( ! OpenProcessToken( GetCurrentProcess(),
                  		TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken ) )
                  		err;
                  
                  	// enable SeBackupPrivilege, SeRestorePrivilege
                  
                  	if ( !LookupPrivilegeValue( NULL, SE_BACKUP_NAME, &tkp.Privileges[0].Luid ) )
                  		err;
                  	if ( !LookupPrivilegeValue( NULL, SE_RESTORE_NAME, &tkp.Privileges[1].Luid ) )
                  		err;
                  	tkp.PrivilegeCount = 2;
                  	tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
                  	tkp.Privileges[1].Attributes = SE_PRIVILEGE_ENABLED;
                  	AdjustTokenPrivileges( hToken, FALSE, &tkp, sizeof tkp,
                  		NULL, NULL );
                  }
                   
                  void dumphdr( WIN32_STREAM_ID & wsi )
                  {
                  	const char *p;
                  	printf( "\nstream [%lu] \"%S\":\n", wsi.dwStreamNameSize,
                  		wsi.dwStreamNameSize? wsi.cStreamName: L"" );
                  	switch ( wsi.dwStreamId )
                  	{
                  	case BACKUP_DATA:
                  		p = "data";
                  		break;
                  	case BACKUP_EA_DATA:
                  		p = "extended attributes";
                  		break;
                  	case BACKUP_SECURITY_DATA:
                  		p = "security";
                  		break;
                  	case BACKUP_ALTERNATE_DATA:
                  		p = "other streams";
                  		break;
                  	case BACKUP_LINK:
                  		p = "link";
                  		break;
                  	default:
                  		p = "unknown";
                  		break;
                  	}
                  	printf( "  type: %s\n", p );
                  	printf( "  size: %I64d\n", wsi.Size.QuadPart );
                  }
                   
                  int main( int argc, char *argv[] )
                  {
                  	HANDLE fh;
                  	if ( argc != 2 )
                  	{
                  		printf( "usage: dump_ntfs_streams {file}\n" );
                  		return 1;
                  	}
                  	// SeBackupPrivilege is not necessary to enumerate streams --
                  	// but it helps if you are an admin/backup-operator and need
                  	// to scan files to which you have no permissions
                  	enableprivs();
                  	fh = CreateFile( argv[1], GENERIC_READ, 0, NULL, OPEN_EXISTING,
                  		FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_POSIX_SEMANTICS, NULL );
                  	if ( fh == INVALID_HANDLE_VALUE &#0124; &#0124; fh == NULL )
                  		err;
                  	byte buf[4096];
                  	DWORD numread, numtoskip;
                  	void *ctx = NULL;
                  	WIN32_STREAM_ID & wsi = *( (WIN32_STREAM_ID *) buf );
                  	numtoskip = 0;
                  	while ( 1 )
                  	{
                  		// we are at the start of a stream header. read it.
                  		if ( ! BackupRead( fh, buf, 20, &numread, FALSE, TRUE, &ctx ) )
                  			err;
                  		if ( numread == 0 )
                  			break;
                  		if ( wsi.dwStreamNameSize > 0 )
                  		{
                  			if ( ! BackupRead( fh, buf + 20, wsi.dwStreamNameSize, &numread, FALSE, TRUE, &ctx ) )
                  				err;
                  			if ( numread != wsi.dwStreamNameSize )
                  				break;
                  		}
                  		dumphdr( wsi );
                  		// skip stream data
                  		if ( wsi.Size.QuadPart > 0 )
                  		{
                  			DWORD lo, hi;
                  			BackupSeek( fh, 0xffffffffL, 0x7fffffffL, &lo, &hi, &ctx );
                  		}
                  	}
                  	// make NT release the context
                  	BackupRead( fh, buf, 0, &numread, TRUE, FALSE, &ctx );
                  	CloseHandle( fh );
                  	return 0;
                  }

                  ------------------
                  -

                  Comment


                  • #10
                    [UPDATED 05-Jul-2001 - Added CloseHandle on the handle return from OpenProcessToken]
                    [UPDATED 20-Apr-2001 - For stream directories]
                    Here's the PB translation (made it close to the original C program)

                    Code:
                    #INCLUDE "win32api.inc"
                     
                    TYPE T_TOKEN_PRIVILEGES
                      PrivilegeCount AS DWORD
                      Privileges AS BYTE PTR ' array size may vary
                    END TYPE
                     
                    #IF NOT %DEF(%TOKEN_ADJUST_PRIVILEGES)
                    %TOKEN_ADJUST_PRIVILEGES = &H0020
                    #ENDIF
                     
                    #IF NOT %DEF(%TOKEN_QUERY)
                    %TOKEN_QUERY = &H0008
                    #ENDIF
                     
                    FUNCTION strFromUnicode( BYVAL dwConvert AS DWORD ) AS STRING
                      LOCAL lLength AS LONG
                      LOCAL sBuffer AS STRING
                     
                      lLength = lstrlenW( BYVAL dwConvert )
                      sBuffer = SPACE$( lLength )
                     
                      CALL WideCharToMultiByte ( 0, _
                                          %NULL, _
                                          BYVAL dwConvert, _
                                          lLength, _
                                          BYVAL STRPTR(sBuffer), _
                                          LEN(sBuffer), _
                                          BYVAL %NULL, _
                                          BYVAL %NULL )
                     
                      FUNCTION = sBuffer
                     
                    END FUNCTION
                     
                    FUNCTION enableprivs() AS LONG
                        LOCAL hToken AS LONG
                        LOCAL tToken AS T_TOKEN_PRIVILEGES
                     
                        tToken.Privileges = HeapAlloc( GetProcessHeap(), %HEAP_ZERO_MEMORY, LEN(LUID_AND_ATTRIBUTES) * 2)
                        IF tToken.Privileges = %NULL THEN
                            FUNCTION = %NULL
                            EXIT FUNCTION
                        END IF
                     
                        DIM tLuid(0:1) AS LUID_AND_ATTRIBUTES AT tToken.Privileges
                     
                        IF ISFALSE( OpenProcessToken( GetCurrentProcess(), _
                                                        %TOKEN_ADJUST_PRIVILEGES OR %TOKEN_QUERY, _
                                                        hToken ) )  THEN
                            FUNCTION = %FALSE
                            GOTO Clean_Up
                        END IF
                     
                        ' enable SeBackupPrivilege, SeRestorePrivilege
                        IF ISFALSE( LookUpPrivilegeValue( BYVAL %NULL, $SE_BACKUP_NAME, BYVAL VARPTR(tLuid(0)) ) ) THEN
                            FUNCTION = %FALSE
                            GOTO Clean_Up
                        END IF
                     
                        IF ISFALSE( LookUpPrivilegeValue( BYVAL %NULL, $SE_RESTORE_NAME, BYVAL VARPTR(tLuid(1)) ) ) THEN
                            FUNCTION = %FALSE
                            GOTO Clean_Up
                        END IF
                     
                        tToken.PrivilegeCount = 2
                        tLuid(0).Attributes = %SE_PRIVILEGE_ENABLED
                        tLuid(1).Attributes = %SE_PRIVILEGE_ENABLED
                     
                        FUNCTION = AdjustTokenPrivileges( hToken, %FALSE, BYVAL VARPTR(tToken), 4 + (LEN(LUID_AND_ATTRIBUTES) * 2), _
                                                            BYVAL %NULL, BYVAL %NULL )
                    Clean_Up:
                        CALL HeapFree( GetProcessHeap(), 0, BYVAL tToken.Privileges )
                        IF hToken THEN
                            CALL CloseHandle( hToken )
                        END IF   
                     
                    END FUNCTION
                     
                    SUB dumphdr( BYREF wsi AS WIN32_STREAM_ID )
                        LOCAL p AS STRING
                        LOCAL quadpart AS QUAD PTR
                     
                        STDOUT $CRLF + "stream " + FORMAT$(wsi.dwStreamNameSize) + ": ";
                        IF wsi.dwStreamNameSize THEN
                            STDOUT "'" + strFromUnicode( VARPTR(wsi.cStreamName) ) + "'"
                        ELSE
                            STDOUT "''"
                        END IF
                     
                        SELECT CASE wsi.dwStreamId
                            CASE %BACKUP_DATA
                                p = "data"
                            CASE %BACKUP_EA_DATA
                                p = "extended attributes"
                            CASE %BACKUP_SECURITY_DATA
                                p = "security"
                            CASE %BACKUP_ALTERNATE_DATA
                                p = "other streams"
                            CASE %BACKUP_LINK
                                p = "link"
                            CASE ELSE
                                p = "unknown"
                        END SELECT
                     
                        STDOUT "   type: " + p
                        quadpart = VARPTR(wsi.qSize)
                        STDOUT "   size: " + FORMAT$(@quadpart)
                     
                    END SUB
                      
                    FUNCTION GetCommandPath( szCmdPath AS ASCIIZ, szFileName AS ASCIIZ ) AS LONG
                        'returns Directory in szCmdPath and strips Filename to szFileName
                        LOCAL lPosDir AS LONG
                     
                        lPosDir = INSTR(-1, szCmdPath, "\")
                        IF lPosDir = 0 THEN FUNCTION = %FALSE: szFileName = szCmdPath: EXIT FUNCTION
                     
                        szFileName = MID$( szCmdPath, lPosDir+1 )
                        szCmdPath = MID$( szCmdPath, 1, lPosDir )
                        IF LEN(szFileName) = 0 THEN
                            szFileName = szCmdPath
                        END IF
                     
                        FUNCTION = %TRUE
                    END FUNCTION
                       
                    FUNCTION PBMAIN() AS LONG
                        LOCAL fh AS LONG
                        LOCAL szCurdir AS ASCIIZ * %MAX_PATH
                        LOCAL szCmdDir AS ASCIIZ * %MAX_PATH
                        LOCAL szCmd AS ASCIIZ * %MAX_PATH
                        LOCAL szFileName AS ASCIIZ * %MAX_PATH
                     
                        szCmd = COMMAND$
                        szCurDir = CURDIR$
                     
                        IF LEN(szCmd) = 0 THEN
                            STDOUT "usage: enum_streams <filename>"
                            EXIT FUNCTION
                        END IF
                     
                        IF GetCommandPath( szCmd, szFileName ) THEN
                            CHDIR szCmd
                        END IF
                        ' SeBackupPrivilege is not necessary to enumerate streams --
                        ' but it helps if you are an admin/backup-operator AND need
                        ' to scan files to which you have no permissions
                        IF enableprivs() THEN
                            LOCAL llast AS LONG
                             
                            'dont see any need for %FILE_FLAG_POSIX_SEMANTICS
                            fh = CreateFile( szFileName, %GENERIC_READ, 0, BYVAL %NULL, %OPEN_EXISTING, _
                                                %FILE_FLAG_BACKUP_SEMANTICS, %NULL )
                            IF fh = %INVALID_HANDLE_VALUE THEN
                                STDOUT "Could not open file: " + szFileName
                                EXIT FUNCTION
                            END IF
                     
                            DIM buf(4096) AS BYTE
                            LOCAL numread AS LONG, numtoskip AS LONG
                            LOCAL wsi AS WIN32_STREAM_ID PTR
                     
                            wsi = VARPTR(buf(0))
                            LOCAL ctx AS LONG
                     
                            DO
                                'we are at the start of a stream header - read it
                                IF ISFALSE( BackUpRead( fh, BYVAL VARPTR(buf(0)), 20, numread, %FALSE, %TRUE, ctx ) ) THEN
                                    STDOUT "Error reading file!"
                                    CALL CloseHandle( fh )
                                    GOTO Clean_Up
                                END IF
                     
                                IF numread = 0 THEN EXIT DO
                     
                                IF @wsi.dwStreamNameSize > 0 THEN
                                    IF ISFALSE( BackUpRead( fh, BYVAL VARPTR(buf(0)) + 20, @wsi.dwStreamNameSize, numread, %FALSE, %TRUE, ctx ) ) THEN
                                        CALL CloseHandle( fh )
                                        STDOUT "Error reading file!"
                                        GOTO Clean_Up
                                    END IF
                                    IF numread <> @wsi.dwStreamNameSize THEN EXIT DO
                                END IF
                     
                                CALL dumphdr( @wsi )
                                'skip stream data
                                LOCAL quadpart AS QUAD PTR
                                quadpart = VARPTR(@wsi.qSize)
                                IF @quadpart > 0 THEN
                                    LOCAL lo AS LONG, hi AS LONG
                                    CALL BackUpSeek( fh, &Hffffffff&, &H7fffffff&, lo, hi, ctx )
                                END IF
                            LOOP
                     
                            'make NT release the context
                            CALL BackUpRead( fh, BYVAL VARPTR(buf(0)), 0, numread, %TRUE, %FALSE, ctx)
                     
                            CALL CloseHandle( fh )
                             
                        ELSE
                            STDOUT "Could not enable SE_BACKUP_NAME and SE_RESTORE_NAME privileges"
                        END IF
                     
                    Clean_Up:
                        'restore Current dir
                        CHDIR szCurDir
                    END FUNCTION


                    [This message has been edited by Florent Heyworth (edited July 05, 2001).]

                    Comment


                    • #11
                      !!!!!!!!!!!!!!!!!!!!!!!! FLORENT!!! You are the man! -- I am forever in your debt (although I think I was before anyway )
                      Im absolutely lost for words -- I'll come back here and edit this post in a few hours after a few drinks. Ive been wanting to do this in PB for sooo long but even simple C++ is beyond me - your superb PB port compiled perfectly and ran beauuuutifully, identifying the streams I had set <im so stoked>
                      Happy Easter again!!


                      ------------------
                      -

                      Comment


                      • #12
                        Florent, I've been playing around with your ported source for the last couple of days now and I must say it truly is a beautiful thing! I have one last question... here's another kinky side of ADS - not only can you link NTFS ADS streams to files, but you can also actually link them to Directories. The interesting thing with this is that because you can't delete a stream without deleting the parent that it's linked to, if you link your stream to C:\ it is virtually impossible to 'delete' your stream without taking out all of C:\ ! (in theory anyway). Imagine if a trojan/virus started filling up an ADS stream that was linked to C:\ ? Im not sure what would happen, but because Explorer etc don't reflect disk availability size change from streams, I think it could be quite nasty... :-/

                        Example: from Windows, go Start | Run: notepad C:\:mystream
                        Because Notepad is ADS-friendly you can do that, and you can read/write from that ADS stream with ease. A good place to store passwords perhaps

                        My question - your source works amazingly well for ADS streams linked to FILES, but not for directories... any idea why? I'm thinking it may be something to do with the CreateFile call?
                        Anyway Florent I hope you find some time off to enjoy the weekend!

                        Best regards,
                        Wayne



                        ------------------
                        -

                        Comment


                        • #13
                          Hi Wayne

                          the reason is that the GetCommandPath function assumed it'd work
                          with files and not directories. I've made a small change to
                          the GetCommandPath function in the code - you'll need to copy
                          it again.

                          The code should handle directories as well. Simply call the
                          program with a string argument for a directory omitting the
                          last slash as in enum.exe c:\streams NOT enum.exe c:\streams\

                          Yes the GetCommandPath is simplistic - feel free to substitute
                          a more clever one .

                          Cheers

                          Florent

                          ------------------

                          Comment

                          Working...
                          X