Added support for NtCreate/OpenFile, NtClose and hooks uninstalling

This commit is contained in:
Berserker 2019-05-05 19:44:18 +03:00
parent 360fddaa46
commit 21c4b1df1c
6 changed files with 176 additions and 67 deletions

View File

@ -0,0 +1 @@
It was a pleasure to override you, friend!

View File

@ -4,9 +4,9 @@ unit VfsIntegratedTest;
uses uses
SysUtils, TestFramework, Windows, SysUtils, TestFramework, Windows,
Utils, WinUtils, ConsoleApi, Utils, WinUtils, ConsoleApi, Files,
VfsUtils, VfsBase, VfsDebug, VfsUtils, VfsBase, VfsDebug,
VfsControl; VfsOpenFiles, VfsControl, DlgMes;
type type
TestIntegrated = class (TTestCase) TestIntegrated = class (TTestCase)
@ -22,8 +22,10 @@ type
published published
procedure TestGetFileAttributes; procedure TestGetFileAttributes;
procedure TestGetFileAttributesEx; procedure TestGetFileAttributesEx;
procedure TestFilesOpenClose;
end; end;
(***) implementation (***) (***) implementation (***)
@ -34,7 +36,7 @@ end;
function TestIntegrated.GetRootDir: string; function TestIntegrated.GetRootDir: string;
begin begin
result := SysUtils.ExtractFileDir(WinUtils.GetExePath) + '\Tests\Fs'; result := VfsUtils.NormalizePath(SysUtils.ExtractFileDir(WinUtils.GetExePath) + '\Tests\Fs');
end; end;
procedure TestIntegrated.SetUp; procedure TestIntegrated.SetUp;
@ -42,22 +44,20 @@ var
RootDir: string; RootDir: string;
begin begin
if not Inited then begin RootDir := Self.GetRootDir;
Inited := true; VfsBase.ResetVfs();
RootDir := Self.GetRootDir; VfsBase.MapDir(RootDir, RootDir + '\Mods\FullyVirtual_2', DONT_OVERWRITE_EXISTING);
VfsBase.ResetVfs(); VfsBase.MapDir(RootDir, RootDir + '\Mods\FullyVirtual', DONT_OVERWRITE_EXISTING);
VfsBase.MapDir(RootDir, RootDir + '\Mods\FullyVirtual', DONT_OVERWRITE_EXISTING); VfsBase.MapDir(RootDir, RootDir + '\Mods\B', DONT_OVERWRITE_EXISTING);
VfsBase.MapDir(RootDir, RootDir + '\Mods\B', DONT_OVERWRITE_EXISTING); VfsBase.MapDir(RootDir, RootDir + '\Mods\A', DONT_OVERWRITE_EXISTING);
VfsBase.MapDir(RootDir, RootDir + '\Mods\A', DONT_OVERWRITE_EXISTING); VfsBase.MapDir(RootDir, RootDir + '\Mods\Apache', DONT_OVERWRITE_EXISTING);
VfsBase.MapDir(RootDir, RootDir + '\Mods\Apache', DONT_OVERWRITE_EXISTING); VfsDebug.SetLoggingProc(LogSomething);
VfsDebug.SetLoggingProc(LogSomething); VfsControl.RunVfs(VfsBase.SORT_FIFO);
VfsControl.RunVfs(VfsBase.SORT_FIFO);
end;
end; end;
procedure TestIntegrated.TearDown; procedure TestIntegrated.TearDown;
begin begin
VfsBase.PauseVfs(); VfsBase.ResetVfs();
VfsDebug.SetLoggingProc(nil); VfsDebug.SetLoggingProc(nil);
end; end;
@ -112,11 +112,58 @@ var
begin begin
RootDir := Self.GetRootDir; RootDir := Self.GetRootDir;
CheckEquals(-1, GetFileSize(RootDir + '\non-existing.non'), '{1}'); CheckEquals(-1, GetFileSize(RootDir + '\non-existing.non'), '{1}');
CheckEquals(47, GetFileSize(RootDir + '\Hobbots\mms.cfg'), '{2}'); CheckEquals(42, GetFileSize(RootDir + '\Hobbots\mms.cfg'), '{2}');
CheckEquals(22, GetFileSize(RootDir + '\503.html'), '{3}'); CheckEquals(22, GetFileSize(RootDir + '\503.html'), '{3}');
CheckEquals(318, GetFileSize(RootDir + '\default'), '{4}'); CheckEquals(318, GetFileSize(RootDir + '\default'), '{4}');
end; // .procedure TestIntegrated.TestGetFileAttributesEx; end; // .procedure TestIntegrated.TestGetFileAttributesEx;
procedure TestIntegrated.TestFilesOpenClose;
var
CurrDir: string;
RootDir: string;
FileData: string;
hFile: integer;
function OpenFile (const Path: string): integer;
begin
result := SysUtils.FileOpen(Path, fmOpenRead or fmShareDenyNone);
end;
begin
CurrDir := SysUtils.GetCurrentDir;
RootDir := Self.GetRootDir;
try
Check(SysUtils.SetCurrentDir(RootDir), 'Setting current directory to real path must succeed');
Check(OpenFile(RootDir + '\non-existing.non') <= 0, 'Opening non-existing file must fail');
hFile := OpenFile(RootDir + '\Hobbots\mms.cfg');
Check(hFile > 0, 'Opening fully virtual file must succeed');
CheckEquals(RootDir + '\Hobbots\mms.cfg', VfsOpenFiles.GetOpenedFilePath(hFile), 'There must be created a corresponding TOpenedFile record for opened file handle with valid virtual path');
SysUtils.FileClose(hFile);
CheckEquals('', VfsOpenFiles.GetOpenedFilePath(hFile), 'TOpenedFile record must be destroyed on file handle closing {1}');
hFile := OpenFile('Hobbots\mms.cfg');
Check(hFile > 0, 'Opening fully virtual file using relative path must succeed');
CheckEquals(RootDir + '\Hobbots\mms.cfg', VfsOpenFiles.GetOpenedFilePath(hFile), 'There must be created a corresponding TOpenedFile record for opened file handle with valid virtual path when relative path was used');
SysUtils.FileClose(hFile);
CheckEquals('', VfsOpenFiles.GetOpenedFilePath(hFile), 'TOpenedFile record must be destroyed on file handle closing {2}');
Check(SysUtils.SetCurrentDir(RootDir + '\Hobbots'), 'Setting current durectory to fully virtual must succeed');
hFile := OpenFile('mms.cfg');
Check(hFile > 0, 'Opening fully virtual file in fully virtual directory using relative path must succeed');
CheckEquals(RootDir + '\Hobbots\mms.cfg', VfsOpenFiles.GetOpenedFilePath(hFile), 'There must be created a corresponding TOpenedFile record for opened file handle with valid virtual path when relative path was used for fully virtual directory');
SysUtils.FileClose(hFile);
CheckEquals('', VfsOpenFiles.GetOpenedFilePath(hFile), 'TOpenedFile record must be destroyed on file handle closing {3}');
Check(Files.ReadFileContents('mms.cfg', FileData), 'File mms.cfg must be readable');
CheckEquals('It was a pleasure to override you, friend!', FileData);
finally
SysUtils.SetCurrentDir(CurrDir);
end; // .try
end; // .procedure TestIntegrated.TestFilesOpenClose;
begin begin
RegisterTest(TestIntegrated.Suite); RegisterTest(TestIntegrated.Suite);
end. end.

View File

@ -530,15 +530,10 @@ begin
try try
DisableVfsForThread; DisableVfsForThread;
result := Func(Arg); result := Func(Arg);
except finally
on E: Exception do begin RestoreVfsForThread;
RestoreVfsForThread; end;
raise E; end;
end;
end; // .try
RestoreVfsForThread;
end; // .with
end; // .function CallWithoutVfs end; // .function CallWithoutVfs
begin begin

View File

@ -32,6 +32,13 @@ var
NativeNtClose: WinNative.TNtClose; NativeNtClose: WinNative.TNtClose;
NativeNtQueryDirectoryFile: WinNative.TNtQueryDirectoryFile; NativeNtQueryDirectoryFile: WinNative.TNtQueryDirectoryFile;
NtQueryAttributesFilePatch: VfsPatching.TAppliedPatch;
NtQueryFullAttributesFilePatch: VfsPatching.TAppliedPatch;
NtOpenFilePatch: VfsPatching.TAppliedPatch;
NtCreateFilePatch: VfsPatching.TAppliedPatch;
NtClosePatch: VfsPatching.TAppliedPatch;
NtQueryDirectoryFilePatch: VfsPatching.TAppliedPatch;
(* There is no 100% portable and reliable way to get file path by handle, unless file creation/opening (* There is no 100% portable and reliable way to get file path by handle, unless file creation/opening
was tracked. Thus we rely heavily on VfsOpenFiles. was tracked. Thus we rely heavily on VfsOpenFiles.
@ -66,7 +73,7 @@ begin
result := DirPath + '\' + FilePath; result := DirPath + '\' + FilePath;
end else begin end else begin
result := DirPath + FilePath; result := DirPath + FilePath;
end; end;
end; end;
end else begin end else begin
result := FilePath; result := FilePath;
@ -209,11 +216,9 @@ var
ReplacedObjAttrs: WinNative.TObjectAttributes; ReplacedObjAttrs: WinNative.TObjectAttributes;
HadTrailingDelim: boolean; HadTrailingDelim: boolean;
FileInfo: Windows.TWin32FindDataW;
begin begin
if VfsDebug.LoggingEnabled then begin if VfsDebug.LoggingEnabled then begin
WriteLog('NtCreateFile', ObjectAttributes.ObjectName.ToWideStr()); WriteLog('[ENTER] NtCreateFile', Format('Access: 0x%x. CreateDisposition: 0x%x'#13#10'Path: "%s"', [Int(DesiredAccess), Int(CreateDisposition), ObjectAttributes.ObjectName.ToWideStr()]));
end; end;
ReplacedObjAttrs := ObjectAttributes^; ReplacedObjAttrs := ObjectAttributes^;
@ -222,7 +227,7 @@ begin
RedirectedPath := ''; RedirectedPath := '';
if (ExpandedPath <> '') and ((DesiredAccess and WinNative.DELETE) = 0) and (CreateDisposition = WinNative.FILE_OPEN) then begin if (ExpandedPath <> '') and ((DesiredAccess and WinNative.DELETE) = 0) and (CreateDisposition = WinNative.FILE_OPEN) then begin
RedirectedPath := VfsBase.GetVfsItemRealPath(StrLib.ExcludeTrailingDelimW(ExpandedPath, @HadTrailingDelim), @FileInfo); RedirectedPath := VfsBase.GetVfsItemRealPath(StrLib.ExcludeTrailingDelimW(ExpandedPath, @HadTrailingDelim));
end; end;
if RedirectedPath = '' then begin if RedirectedPath = '' then begin
@ -231,25 +236,33 @@ begin
RedirectedPath := RedirectedPath + '\'; RedirectedPath := RedirectedPath + '\';
end; end;
if (RedirectedPath <> '') and (RedirectedPath[1] <> '\') then begin if RedirectedPath <> '' then begin
RedirectedPath := '\??\' + RedirectedPath; if RedirectedPath[1] <> '\' then begin
RedirectedPath := '\??\' + RedirectedPath;
end;
ReplacedObjAttrs.RootDirectory := 0;
ReplacedObjAttrs.Attributes := ReplacedObjAttrs.Attributes or WinNative.OBJ_CASE_INSENSITIVE;
ReplacedObjAttrs.ObjectName.AssignExistingStr(RedirectedPath);
end; end;
ReplacedObjAttrs.RootDirectory := 0; with VfsOpenFiles.OpenFilesCritSection do begin
ReplacedObjAttrs.Attributes := ReplacedObjAttrs.Attributes or WinNative.OBJ_CASE_INSENSITIVE; Enter;
ReplacedObjAttrs.ObjectName.AssignExistingStr(RedirectedPath);
result := OrigFunc(FileHandle, DesiredAccess, @ReplacedObjAttrs, IoStatusBlock, AllocationSize, FileAttributes, ShareAccess, CreateDisposition, CreateOptions, EaBuffer, EaLength); result := OrigFunc(FileHandle, DesiredAccess, @ReplacedObjAttrs, IoStatusBlock, AllocationSize, FileAttributes, ShareAccess, CreateDisposition, CreateOptions, EaBuffer, EaLength);
if (result = WinNative.STATUS_SUCCESS) and Utils.HasFlag(WinNative.FILE_SYNCHRONOUS_IO_NONALERT, CreateOptions) and Utils.HasFlag(WinNative.SYNCHRONIZE, DesiredAccess) then begin if (result = WinNative.STATUS_SUCCESS) and (ExpandedPath <> '') then begin
VfsOpenFiles.SetOpenedFileInfo(FileHandle^, TOpenedFile.Create(FileHandle^, ExpandedPath)); VfsOpenFiles.SetOpenedFileInfo(FileHandle^, TOpenedFile.Create(FileHandle^, ExpandedPath));
end; end;
Leave;
end;
if VfsDebug.LoggingEnabled then begin if VfsDebug.LoggingEnabled then begin
if ExpandedPath <> StripNtAbsPathPrefix(RedirectedPath) then begin if ExpandedPath <> StripNtAbsPathPrefix(RedirectedPath) then begin
WriteLog('NtCreateFile', Format('Access: 0x%x. Handle: %x. Status: %x. Redirected "%s" => "%s"', [DesiredAccess, FileHandle^, result, StrLib.WideToAnsiSubstitute(ExpandedPath), StrLib.WideToAnsiSubstitute(StripNtAbsPathPrefix(RedirectedPath))])); WriteLog('[LEAVE] NtCreateFile', Format('Handle: %x. Status: %x.'#13#10'Expanded: "%s"'#13#10'Redirected: "%s"', [FileHandle^, result, ExpandedPath, StripNtAbsPathPrefix(RedirectedPath)]));
end else begin end else begin
WriteLog('NtCreateFile', Format('Access: 0x%x. Handle: %x. Status: %x. Path: "%s"', [DesiredAccess, FileHandle^, result, StrLib.WideToAnsiSubstitute(ExpandedPath)])); WriteLog('[LEAVE] NtCreateFile', Format('Handle: %x. Status: %x.'#13#10'Expanded: "%s"', [FileHandle^, result, ExpandedPath]));
end; end;
end; end;
end; // .function Hook_NtCreateFile end; // .function Hook_NtCreateFile
@ -257,11 +270,12 @@ end; // .function Hook_NtCreateFile
function Hook_NtClose (OrigFunc: WinNative.TNtClose; hData: HANDLE): NTSTATUS; stdcall; function Hook_NtClose (OrigFunc: WinNative.TNtClose; hData: HANDLE): NTSTATUS; stdcall;
begin begin
if VfsDebug.LoggingEnabled then begin if VfsDebug.LoggingEnabled then begin
WriteLog('NtClose', Format('Handle: %x', [integer(hData)])); WriteLog('[ENTER] NtClose', Format('Handle: %x', [integer(hData)]));
end; end;
with OpenFilesCritSection do begin with VfsOpenFiles.OpenFilesCritSection do begin
Enter; Enter;
result := OrigFunc(hData); result := OrigFunc(hData);
if WinNative.NT_SUCCESS(result) then begin if WinNative.NT_SUCCESS(result) then begin
@ -272,7 +286,7 @@ begin
end; end;
if VfsDebug.LoggingEnabled then begin if VfsDebug.LoggingEnabled then begin
WriteLog('NtClose', Format('Status: %x', [integer(result)])); WriteLog('[LEAVE] NtClose', Format('Status: %x', [integer(result)]));
end; end;
end; // .function Hook_NtClose end; // .function Hook_NtClose
@ -526,36 +540,41 @@ begin
NativeNtQueryAttributesFile := VfsPatching.SpliceWinApi NativeNtQueryAttributesFile := VfsPatching.SpliceWinApi
( (
VfsApiDigger.GetRealProcAddress(NtdllHandle, 'NtQueryAttributesFile'), VfsApiDigger.GetRealProcAddress(NtdllHandle, 'NtQueryAttributesFile'),
@Hook_NtQueryAttributesFile @Hook_NtQueryAttributesFile,
@NtQueryAttributesFilePatch
); );
WriteLog('InstallHook', 'Installing NtQueryFullAttributesFile hook'); WriteLog('InstallHook', 'Installing NtQueryFullAttributesFile hook');
NativeNtQueryFullAttributesFile := VfsPatching.SpliceWinApi NativeNtQueryFullAttributesFile := VfsPatching.SpliceWinApi
( (
VfsApiDigger.GetRealProcAddress(NtdllHandle, 'NtQueryFullAttributesFile'), VfsApiDigger.GetRealProcAddress(NtdllHandle, 'NtQueryFullAttributesFile'),
@Hook_NtQueryFullAttributesFile @Hook_NtQueryFullAttributesFile,
@NtQueryFullAttributesFilePatch
); );
// WriteLog('InstallHook', 'Installing NtOpenFile hook'); WriteLog('InstallHook', 'Installing NtOpenFile hook');
// NativeNtOpenFile := VfsPatching.SpliceWinApi NativeNtOpenFile := VfsPatching.SpliceWinApi
// ( (
// VfsApiDigger.GetRealProcAddress(NtdllHandle, 'NtOpenFile'), VfsApiDigger.GetRealProcAddress(NtdllHandle, 'NtOpenFile'),
// @Hook_NtOpenFile @Hook_NtOpenFile,
// ); @NtOpenFilePatch
);
// WriteLog('InstallHook', 'Installing NtCreateFile hook'); WriteLog('InstallHook', 'Installing NtCreateFile hook');
// NativeNtCreateFile := VfsPatching.SpliceWinApi NativeNtCreateFile := VfsPatching.SpliceWinApi
// ( (
// VfsApiDigger.GetRealProcAddress(NtdllHandle, 'NtCreateFile'), VfsApiDigger.GetRealProcAddress(NtdllHandle, 'NtCreateFile'),
// @Hook_NtCreateFile @Hook_NtCreateFile,
// ); @NtCreateFilePatch
);
// WriteLog('InstallHook', 'Installing NtClose hook'); WriteLog('InstallHook', 'Installing NtClose hook');
// NativeNtClose := VfsPatching.SpliceWinApi NativeNtClose := VfsPatching.SpliceWinApi
// ( (
// VfsApiDigger.GetRealProcAddress(NtdllHandle, 'NtClose'), VfsApiDigger.GetRealProcAddress(NtdllHandle, 'NtClose'),
// @Hook_NtClose @Hook_NtClose,
// ); @NtClosePatch
);
// WriteLog('InstallHook', 'Installing NtQueryDirectoryFile hook'); // WriteLog('InstallHook', 'Installing NtQueryDirectoryFile hook');
// NativeNtQueryDirectoryFile := VfsPatching.SpliceWinApi // NativeNtQueryDirectoryFile := VfsPatching.SpliceWinApi
@ -569,6 +588,28 @@ begin
end; // .with end; // .with
end; // .procedure InstallHooks end; // .procedure InstallHooks
procedure UninstallHooks;
begin begin
with HooksCritSection do begin
Enter;
NtQueryAttributesFilePatch.Rollback;
NtQueryFullAttributesFilePatch.Rollback;
NtOpenFilePatch.Rollback;
NtCreateFilePatch.Rollback;
NtClosePatch.Rollback;
Leave;
end;
end;
initialization
HooksCritSection.Init; HooksCritSection.Init;
finalization
with VfsBase.VfsCritSection do begin
Enter;
VfsBase.ResetVfs;
UninstallHooks;
Leave;
end;
end. end.

View File

@ -4,17 +4,24 @@ unit VfsPatching;
All hooks are thread-safe. All hooks are thread-safe.
*) *)
(***) interface (***) (***) interface (***)
uses uses
Windows, SysUtils, Utils, PatchForge; Windows, SysUtils, Utils, PatchForge;
type
PAppliedPatch = ^TAppliedPatch;
TAppliedPatch = record
Addr: pointer;
Bytes: Utils.TArrayOfByte;
procedure Rollback;
end;
(* Replaces original STDCALL function with the new one with the same prototype and one extra argument. (* Replaces original STDCALL function with the new one with the same prototype and one extra argument.
The argument is callable pointer, used to execute original function. The pointer is passed as THE FIRST The argument is callable pointer, used to execute original function. The pointer is passed as THE FIRST
argument before other arguments. *) argument before other arguments. *)
function SpliceWinApi (OrigFunc, HandlerFunc: pointer): pointer; function SpliceWinApi (OrigFunc, HandlerFunc: pointer; {n} AppliedPatch: PAppliedPatch = nil): pointer;
(***) implementation (***) (***) implementation (***)
@ -68,7 +75,7 @@ begin
end; end;
end; // .function WritePatchAtCode end; // .function WritePatchAtCode
function SpliceWinApi (OrigFunc, HandlerFunc: pointer): pointer; function SpliceWinApi (OrigFunc, HandlerFunc: pointer; {n} AppliedPatch: PAppliedPatch = nil): pointer;
const const
CODE_ADDR_ALIGNMENT = 8; CODE_ADDR_ALIGNMENT = 8;
@ -121,9 +128,23 @@ begin
// Create and apply hook at target function start // Create and apply hook at target function start
p.Clear(); p.Clear();
p.Jump(PatchForge.JMP, SpliceBridge); p.Jump(PatchForge.JMP, SpliceBridge);
if AppliedPatch <> nil then begin
AppliedPatch.Addr := OrigFunc;
SetLength(AppliedPatch.Bytes, p.Size);
Utils.CopyMem(p.Size, OrigFunc, @AppliedPatch.Bytes[0]);
end;
WritePatchAtCode(p.PatchMaker, OrigFunc); WritePatchAtCode(p.PatchMaker, OrigFunc);
// * * * * * // // * * * * * //
p.Release; p.Release;
end; end;
procedure TAppliedPatch.Rollback;
begin
if Self.Bytes <> nil then begin
WriteAtCode(Length(Self.Bytes), @Self.Bytes[0], Self.Addr);
end;
end;
end. end.

View File

@ -5,6 +5,10 @@ Move copyright to single file license?
SetCurrentDirectoryW(GetCurrentDirectoryW) SetCurrentDirectoryW(GetCurrentDirectoryW)
System.IsMultiThread for DLL and exported API System.IsMultiThread for DLL and exported API
In order to prevent crashing on application exit we can disable logging to console
in VFS in ExitProcess, RtlTerminateProcess or LdrShutdownProcess.
But maybe source of crashes lies somewhere else.
(* Trying to turn off DEP *) (* Trying to turn off DEP *)
SetProcessDEPPolicyAddr := Windows.GetProcAddress(Kernel32Handle, 'SetProcessDEPPolicy'); SetProcessDEPPolicyAddr := Windows.GetProcAddress(Kernel32Handle, 'SetProcessDEPPolicy');