|
Author
|
Topic: Intro to Alternate Data Streams (virtually invisible files)
|
Wayne Diamond Member
|
posted April 16, 2001 02:19 PM
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!
'ADS_STREAMS.BAS - 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).] IP: Logged |
Wayne Diamond Member
|
posted April 16, 2001 02:27 PM
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 #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
------------------
IP: Logged |
Eric Pearson Member
|
posted April 16, 2001 02:41 PM
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: support@perfectsync.com [This message has been edited by Eric Pearson (edited April 16, 2001).] IP: Logged |
Wayne Diamond Member
|
posted April 16, 2001 02:55 PM
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).] IP: Logged |
Wayne Diamond Member
|
posted April 16, 2001 03:23 PM
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:
'--- 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! '---
#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).] IP: Logged |
mark smit unregistered
|
posted April 16, 2001 08:22 PM
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! IP: Logged |
mark smit unregistered
|
posted April 16, 2001 09:09 PM
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! IP: Logged |
Wayne Diamond Member
|
posted April 16, 2001 11:58 PM
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
#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
------------------
IP: Logged |
Wayne Diamond Member
|
posted April 17, 2001 12:03 AM
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!
#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 | | 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; }
------------------
IP: Logged |
Florent Heyworth Member
|
posted April 18, 2001 01:20 AM
[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) #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).] IP: Logged |
Wayne Diamond Member
|
posted April 18, 2001 02:37 AM
!!!!!!!!!!!!!!!!!!!!!!!! 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!!
------------------
IP: Logged |
Wayne Diamond Member
|
posted April 20, 2001 07:20 AM
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------------------
IP: Logged |
Florent Heyworth Member
|
posted April 20, 2001 08:25 AM
Hi Waynethe 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 ------------------
IP: Logged |