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
Antwort Antwort
Kas Ob.

Registriert seit: 3. Sep 2023
347 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
Benutzerbild von dummzeuch
dummzeuch

Registriert seit: 11. Aug 2012
Ort: Essen
1.605 Beiträge
 
Delphi 10.2 Tokyo Professional
 
#2

AW: Small library for Alternative Data Streams (ADS)

  Alt 21. Aug 2024, 12:21
Thanks. Interesting.

Could you please edit your post and replace the code tags with Delphi tags, so we get syntax highlighting?

Code:
procedure bla;
begin
end;
vs.
Delphi-Quellcode:
procedure bla;
begin
end;
Thomas Mueller

Geändert von dummzeuch (21. Aug 2024 um 18:55 Uhr)
  Mit Zitat antworten Zitat
Kas Ob.

Registriert seit: 3. Sep 2023
347 Beiträge
 
#3

AW: Small library for Alternative Data Streams (ADS)

  Alt 21. Aug 2024, 12:26
could you please edit your post and replace the code tags with Delphi tags, so we get syntax highlighting?
Thank you, i never saw that button
Kas
  Mit Zitat antworten Zitat
Benutzerbild von DeddyH
DeddyH

Registriert seit: 17. Sep 2006
Ort: Barchfeld
27.624 Beiträge
 
Delphi 12 Athens
 
#4

AW: Small library for Alternative Data Streams (ADS)

  Alt 21. Aug 2024, 13:46
Passing a TStringList as Var-Parameter does not make sense in this context. The underlying instance has to be created and destroyed from outside, so you can even pass it as Const-Parameter.
Detlef
"Ich habe Angst vor dem Tag, an dem die Technologie unsere menschlichen Interaktionen übertrumpft. Die Welt wird eine Generation von Idioten bekommen." (Albert Einstein)
Dieser Tag ist längst gekommen
  Mit Zitat antworten Zitat
Benutzerbild von himitsu
himitsu

Registriert seit: 11. Okt 2003
Ort: Elbflorenz
44.063 Beiträge
 
Delphi 12 Athens
 
#5

AW: Small library for Alternative Data Streams (ADS)

  Alt 21. Aug 2024, 13:48
Die BackupAPI, und dafür die nötigen Rechte, ist garnicht nötig. (Administrator oder ein Nutzer mit Backup&Restore-Privilegien aka SE_BACKUP_NAME)
Seit Vista gibt es auch eine neue API dafür. MSDN-Library durchsuchenFindFirstStreamW.

https://learn.microsoft.com/de-de/wi...ndfirststreamw
https://learn.microsoft.com/en-us/ar...g-ntfs-streams
https://www.codeproject.com/Articles...e-Data-Streams

https://learn.microsoft.com/en-us/wi...o/file-streams



Mußt du dir aber selbt im Delphi implementieren, da die API-Imports im Delphi viel zu alt sind. (Winapi.Windows.pas)

Aber mit etwas Glück, kannst du diesen Teil vielleicht aus dem WinMD nutzen,
auch wenn zu viel Anderes einfach nur Schrott ist. (WinMD selbst ist OK, aber nicht das was Embarcadero daraus gemacht hat)
https://getitnow.embarcadero.com/win...pi-from-winmd/
https://www.delphipraxis.net/214473-...vor-winmd.html



PS: Length(StreamArr) - 1 = High(StreamArr)
Neuste Erkenntnis:
Seit Pos einen dritten Parameter hat,
wird PoSex im Delphi viel seltener praktiziert.

Geändert von himitsu (21. Aug 2024 um 13:59 Uhr)
  Mit Zitat antworten Zitat
Kas Ob.

Registriert seit: 3. Sep 2023
347 Beiträge
 
#6

AW: Small library for Alternative Data Streams (ADS)

  Alt 21. Aug 2024, 13:58
Passing a TStringList as Var-Parameter does not make sense in this context. The underlying instance has to be created and destroyed from outside, so you can even pass it as Const-Parameter.
Right and makes sense, only i prefer this usage (const vs var) with classes to distinguish between functions/procedures that read-only against what might change the content, so yes it is just cosmetic, like with this case and that example, but will makes difference with multithreads usage, easier to notice and remember and that is it, where and when need thread synchronization.

Also: we only can dream of compiler and more advanced Delphi/Pascal language feature to detect and prevent internal changes coming form class internal implementation, that makes const and var more powerful, producing safer memory and logical handling.
Kas
  Mit Zitat antworten Zitat
Antwort Antwort


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 17:56 Uhr.
Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz