AGB  ·  Datenschutz  ·  Impressum  







Anmelden
Nützliche Links
Registrieren
Thema durchsuchen
Ansicht
Themen-Optionen

Small library for Alternative Data Streams (ADS)

Ein Thema von Kas Ob. · begonnen am 21. Aug 2024 · letzter Beitrag vom 21. Aug 2024
 
Kas Ob.

Registriert seit: 3. Sep 2023
386 Beiträge
 
#1

Small library for Alternative Data Streams (ADS)

  Alt 21. Aug 2024, 12:04
Hi,

Having my fun with ADS i wrote this, and hope you find it useful
Delphi-Quellcode:
unit lcNTFSFileStreams;

interface

uses
  Windows, Classes;

const
  // known StreamID values
  BACKUP_INVALID = 0;
  BACKUP_DATA = 1; // the file data/content itself
  BACKUP_EA_DATA = 2;
  BACKUP_SECURITY_DATA = 3; // security descriptor of the file
  BACKUP_ALTERNATE_DATA = 4; // attached/alternative data stream, must be have a name
  BACKUP_LINK = 5;
  BACKUP_PROPERTY_DATA = 6;
  BACKUP_OBJECT_ID = 7;
  BACKUP_REPARSE_DATA = 8;
  BACKUP_SPARSE_BLOCK = 9;
  BACKUP_TXFS_DATA = 10;
  BACKUP_GHOSTED_FILE_EXTENTS = 11;

  // known StreamAttributes bits
  STREAM_NORMAL_ATTRIBUTE = 0;
  STREAM_MODIFIED_WHEN_READ = 1;
  STREAM_CONTAINS_SECURITY = 2;
  STREAM_CONTAINS_PROPERTIES = 4;
  STREAM_SPARSE_ATTRIBUTE = 8;
  STREAM_CONTAINS_GHOSTED_FILE_EXTENTS = 16;

type
  TFileStreamRec = record
    StreamID: Cardinal;
    StreamAttribute: Cardinal;
    Size: Int64;
    StreamName: string;
  end;

  TFileStreamArr = array of TFileStreamRec;

// will enumerate and append to the list any streams with names ( streams with ID = ALTERNATE_DATA )
// return True on success enumeration in full
// return False on failure, but will not remove what already being added before failing
function FileStreamsEnum(const FileName: string; var StreamNameList: TStringList): Boolean;


// same as EnumFileStreams for the result, but with all stream details, and can return all streams not just named
function FileStreamsEnumEx(const FileName: string; var StreamsArr: TFileStreamArr; OnlyNamed: Boolean = True): Boolean;


// delete named stream(s) (Id = BACKUP_ALTERNATE_DATA)
// when StreamName is empty '' , all named streams will be deleted
function FileStreamsDelete(const FileName: string; const StreamName: string = ''): Boolean;

// not implemented yet, better to be with streamId with the name to suport all, another version is needed to get TBytes in memory
//function FileStreamsExtract(const SourceFile,DestFile,StreamName: string):Integer;

implementation

type
  PWIN32_STREAM_ID = ^TWIN32_STREAM_ID;

  TWIN32_STREAM_ID = packed record // Delphi XE8 doesn't have this record packed, i prefer packed
    dwStreamId: Cardinal;
    dwStreamAttributes: Cardinal;
    Size: TLargeInteger;
    dwStreamNameSize: Cardinal;
    cStreamName: array[0..1] of WChar; // we compensate with extra 1 char (2 bytes) for 4 bytes, just for alignment
   end;

function FileStreamsEnumEx(const FileName: string; var StreamsArr: TFileStreamArr; OnlyNamed: Boolean = True): Boolean;
var
  Handle: THandle;
  StreamRec: TWIN32_STREAM_ID;
  StreamContext: Pointer;
  BytesRead: Cardinal;
  dwLowByteSeeked, dwHighByteSeeked: Cardinal;
  SeekResult: Boolean;
  StreamName: string;

  procedure AddStreamToList;
  var
    CurrIndex: Integer;
  begin
    CurrIndex := Length(StreamsArr);
    SetLength(StreamsArr, CurrIndex + 1);
    StreamsArr[CurrIndex].StreamID := StreamRec.dwStreamId;
    StreamsArr[CurrIndex].StreamAttribute := StreamRec.dwStreamAttributes;
    StreamsArr[CurrIndex].Size := StreamRec.Size;
  end;

begin
  Result := False;
  if FileName = 'then
    Exit;

  Handle := CreateFile(Pchar(FileName), GENERIC_READ, FILE_SHARE_READ, nil, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0);

  if Handle = INVALID_HANDLE_VALUE then
    Exit;

  StreamContext := nil; // must be intialized to nil
  while BackupRead(Handle, @StreamRec, SizeOf(StreamRec) - SizeOf(StreamRec.cStreamName), BytesRead, False, True, StreamContext) do
  begin
    if (BytesRead = 0) or (BytesRead < SizeOf(StreamRec) - SizeOf(StreamRec.cStreamName)) then // reached the end
      Break;

    // invalid or unknown StreamId, safer to not handle
    if (StreamRec.dwStreamId = BACKUP_INVALID) or (StreamRec.dwStreamId > BACKUP_GHOSTED_FILE_EXTENTS) then
      Break;

    // per specification defined in [MS-BKUP]-210625
    // The value of this field (dwStreamId) MUST be 0 for all dwStreamId values other than
    // ALTERNATE_DATA. For StreamID ALTERNATE_DATA, the value of this field MUST be in the range
    // 0–65536, and it MUST be an integral multiple of two.
    if (StreamRec.dwStreamNameSize > 0) and (StreamRec.dwStreamId <> BACKUP_ALTERNATE_DATA) then // corrupted header or we lost position
      Break;

    if not OnlyNamed then
      AddStreamToList;

    if (StreamRec.dwStreamNameSize > 0) and (StreamRec.dwStreamNameSize < $10000) and (StreamRec.dwStreamNameSize and 1 = 0) then
    begin
      SetLength(StreamName, StreamRec.dwStreamNameSize div SizeOf(Char));
      if (not BackupRead(Handle, @StreamName[1], StreamRec.dwStreamNameSize, BytesRead, //
        False, True, StreamContext)) or (BytesRead <> StreamRec.dwStreamNameSize) then
        Break;
      if OnlyNamed then
        AddStreamToList;
      StreamsArr[Length(StreamsArr) - 1].StreamName := StreamName;
    end;

    if StreamRec.Size > 0 then
    begin
      // BackUpSeek will not seek across stream headers, so we can use one of the following two method
      // 1) we do BackupSeek with maximum range without result check, letting BackupRead fail later
      //BackupSeek(Handle, Cardinal(-1), Cardinal(-1), dwLowByteSeeked, dwHighByteSeeked, StreamContext);

      // 2) or we do it with exact size and checking for seek result then break now
      SeekResult := BackupSeek(Handle, Cardinal(StreamRec.Size), Cardinal(StreamRec.Size shr 32), //
        dwLowByteSeeked, dwHighByteSeeked, StreamContext);
      if (not SeekResult) or (dwLowByteSeeked + dwHighByteSeeked shl 32 = 0) then
        Break;
      // the difference is : which one we prefer to fail as end of streams for this file, BackupRead or BackupSeek
    end;
  end;

  Result := True;

  if StreamContext <> nil then // cleanup and release stream context
    BackupRead(Handle, nil, 0, BytesRead, True, True, StreamContext); // bAbort = True
  CloseHandle(Handle);
end;

function FileStreamsEnum(const FileName: string; var StreamNameList: TStringList): Boolean;
var
  Streams: TFileStreamArr;
  i: Integer;
begin
  Result := FileStreamsEnumEx(FileName, Streams, True);
  if Length(Streams) > 0 then
    for i := 0 to Length(Streams) - 1 do
      StreamNameList.Add(Streams[i].StreamName);
end;

function FileStreamsDelete(const FileName: string; const StreamName: string = ''): Boolean;
var
  Streams: TFileStreamArr;
  i: Integer;
begin
  Result := FileStreamsEnumEx(FileName, Streams, True);

  i := 0;
  while i < Length(Streams) do
  begin
    if (StreamName = '') or (StreamName = Streams[i].StreamName) then
      Result := DeleteFile(PChar(FileName + string(Streams[i].StreamName)));// just concatenate, DeleteFile support Names Streams
    if StreamName = Streams[i].StreamName then
      Break;
    Inc(i);
  end;
end;

end.
example to use
Delphi-Quellcode:
uses
  lcNTFSFileStreams;

procedure TForm10.Button1Click(Sender: TObject);
const
  FILE_NAME = 'D:\Projects Delphi\NTFSStreams\1.zip';
var
  StreamArr: TFileStreamArr;

  procedure DumpList;
  var
    i: Integer;
  begin
    if Length(StreamArr) > 0 then
    begin
      for i := 0 to Length(StreamArr) - 1 do
        Memo1.Lines.Append(#9'ID: ' + IntToStr(StreamArr[i].StreamID) + ' Size: ' + IntToStr(StreamArr[i].Size) + ' '#9 + StreamArr[i].StreamName);
    end
    else
      Memo1.Lines.Add(#9'file has no alternative data streams');
  end;

begin
  Memo1.Lines.Add('list all');
  FileStreamsEnumEx(FILE_NAME, StreamArr, False);
  DumpList;

  Memo1.Lines.Add('deleting one');
  if FileStreamsDelete(FILE_NAME, ':URL:$DATA') then
    Memo1.Lines.Add('Stream deleted')
  else
    Memo1.Lines.Add('Stream wasn''t deleted');

  SetLength(StreamArr, 0); // clear
  FileStreamsEnumEx(FILE_NAME, StreamArr);
  DumpList;

  Memo1.Lines.Add('deleting all');
  FileStreamsEnumEx(FILE_NAME, StreamArr);
  FileStreamsDelete(FILE_NAME, '');

  Memo1.Lines.Add('list named');
  SetLength(StreamArr, 0); // clear
  FileStreamsEnumEx(FILE_NAME, StreamArr);
  DumpList;

  Memo1.Lines.Add('list all');
  FileStreamsEnumEx(FILE_NAME, StreamArr, False);
  DumpList;
end;
Memo result
Code:
list all
   ID: 3 Size: 92     
   ID: 1 Size: 1681665     
   ID: 4 Size: 935     :Delphi_File:$DATA
   ID: 4 Size: 19     :URL:$DATA
   ID: 4 Size: 167     :Zone.Identifier:$DATA
deleting one
Stream deleted
   ID: 4 Size: 935     :Delphi_File:$DATA
   ID: 4 Size: 167     :Zone.Identifier:$DATA
deleting all
list named
   file has no alternative data streams
list all
   ID: 3 Size: 92     
   ID: 1 Size: 1681665
Kas

Geändert von Kas Ob. (21. Aug 2024 um 12:25 Uhr)
  Mit Zitat antworten Zitat
 


Forumregeln

Es ist dir nicht erlaubt, neue Themen zu verfassen.
Es ist dir nicht erlaubt, auf Beiträge zu antworten.
Es ist dir nicht erlaubt, Anhänge hochzuladen.
Es ist dir nicht erlaubt, deine Beiträge zu bearbeiten.

BB-Code ist an.
Smileys sind an.
[IMG] Code ist an.
HTML-Code ist aus.
Trackbacks are an
Pingbacks are an
Refbacks are aus

Gehe zu:

Impressum · AGB · Datenschutz · Nach oben
Alle Zeitangaben in WEZ +1. Es ist jetzt 06:08 Uhr.
Powered by vBulletin® Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz