Using NtDeleteFile from Delphi

Post date: Aug 4, 2011 7:27:49 PM

The native API NtDeleteFile performs the same task as the user-mode API DeleteFile, but interestingly enough, the user-mode API does not call the native API to perform it’s task. As explained here, normally files are deleted through calls to NtSetInformationFile. The main difference in behavior comes from the fact that NtDeleteFile does not wait for handles on the file to close before deleting it (note that if the file is “open for normal I/O or as a memory-mapped file”, it still can’t be deleted, so only read-only handles will be ignored).

Source:

http://vtopan.wordpress.com/2009/05/26/using-ntdeletefile-from-delphi/

The required structures (not defined in Delphi) are:

UNICODE_STRING

PUNICODE_STRING = ^UNICODE_STRING;
UNICODE_STRING = packed record
   Length: Word;
   MaximumLength: Word;
   Buffer: PWideChar;
   end;

OBJECT_ATTRIBUTES.

POBJECT_ATTRIBUTES = ^OBJECT_ATTRIBUTES;
OBJECT_ATTRIBUTES = packed record
   Length: Cardinal;
   RootDirectory: THandle;
   ObjectName: PUNICODE_STRING;
   Attributes: Cardinal;
   SecurityDescriptor: Pointer;
   SecurityQualityOfService: Pointer;
   end;

The steps to remove the file are:

    1. Convert the “DOS” path name to an “NT” path name (basically prepend “\??\” to whatever the plain path is) using RtlDosPathNameToNtPathName_U;
    2. Fill in the OBJECT_ATTRIBUTES structure;
    3. Call NtDeleteFile.

Using the afore-linked-to type definitions, we still need to import the native APIs:

function NtDeleteFile(ObjectAttributes:POBJECT_ATTRIBUTES):DWORD; stdcall; external 'ntdll.dll';
function RtlDosPathNameToNtPathName_U(DosName:PWChar; var NtName:UNICODE_STRING; DosFilePath:PPChar; NtFilePath:PUNICODE_STRING):BOOL; stdcall; external 'ntdll.dll';

Do note that this statically links the imported functions, making the whole application unable to load if the undocumented APIs are not present on the system (at least for Windows 2000 and XP they should be).

Converting the DOS path name to a native path is done by the RtlDosPathNameToNtPathName_U API, which takes in a PWChar argument containing the DOS path and a pre-allocated UNICODE_STRING structure of MAX_PATH WideChars and returns True if it has successfully converted the path.

program NativeDelete;
//cswi - www.delphibasics.info
//Vlad Ioan Topan - http://vtopan.wordpress.com
uses
  Windows;
type
  PUnicodeString = ^TUnicodeString;
  TUnicodeString = packed record
    Length: Word;
    MaximumLength: Word;
    Buffer: PWideChar;
  end;
  PObjectAttributes = ^TObjectAttributes;
  TObjectAttributes = packed record
    Length: Cardinal;
    RootDirectory: THandle;
    ObjectName: PUnicodeString;
    Attributes: Cardinal;
    SecurityDescriptor: Pointer;
    SecurityQualityOfService: Pointer;
  end;
procedure RtlInitUnicodeString(DestinationString: PUnicodeString; SourceString: LPWSTR); stdcall; external 'ntdll.dll';
function NtDeleteFile(ObjectAttributes: PObjectAttributes): DWORD; stdcall; external 'ntdll.dll';
function RtlDosPathNameToNtPathName_U(DosName: PWChar; var NtName: TUnicodeString; DosFilePath: PPChar; NtFilePath: PUnicodeString):BOOL; stdcall; external 'ntdll.dll';
procedure InitializeObjectAttributes(var InitializedAttributes: TObjectAttributes;
                                     ObjectName: PUnicodeString;
                                     Attributes: ULONG;
                                     RootDirectory: THandle;
                                     SecurityDescriptor: Pointer;
                                     SecurityQualityOfService : Pointer);
begin
  InitializedAttributes.Length := SizeOf(TObjectAttributes);
  InitializedAttributes.RootDirectory := RootDirectory;
  InitializedAttributes.Attributes := Attributes;
  InitializedAttributes.ObjectName := ObjectName;
  InitializedAttributes.SecurityDescriptor := SecurityDescriptor;
  InitializedAttributes.SecurityQualityOfService := SecurityQualityOfService;
end;
function _DeleteFile(wsFileName: WideString):DWORD;
var
   ObjectAttributes: TObjectAttributes;
   UnicodeString: TUnicodeString;
begin
   Result := $C0000001; // STATUS_UNSUCCESSFUL, "generic" error
   RtlInitUnicodeString(@UnicodeString, @wsFileName);
   RtlDosPathNameToNtPathName_U(@wsFileName[1], UnicodeString, nil, nil);
   InitializeObjectAttributes(ObjectAttributes, @UnicodeString, $40, 0, nil, nil);
   Result := NtDeleteFile(@ObjectAttributes); // pass on the NTSTATUS
end;
const
  wsFileName : PWideChar = 'C:\GoogleLogo.png';
begin
  _DeleteFile(wsFileName);
end.