diff --git a/Tests/Fs/Mods/FullyVirtual_2/Hobbots/mms.cfg b/Tests/Fs/Mods/FullyVirtual_2/Hobbots/mms.cfg new file mode 100644 index 0000000..3d0319e --- /dev/null +++ b/Tests/Fs/Mods/FullyVirtual_2/Hobbots/mms.cfg @@ -0,0 +1 @@ +It was a pleasure to override you, friend! \ No newline at end of file diff --git a/Tests/VfsIntegratedTest.pas b/Tests/VfsIntegratedTest.pas index 5dfac2b..0af3855 100644 --- a/Tests/VfsIntegratedTest.pas +++ b/Tests/VfsIntegratedTest.pas @@ -4,9 +4,9 @@ unit VfsIntegratedTest; uses SysUtils, TestFramework, Windows, - Utils, WinUtils, ConsoleApi, + Utils, WinUtils, ConsoleApi, Files, VfsUtils, VfsBase, VfsDebug, - VfsControl; + VfsOpenFiles, VfsControl, DlgMes; type TestIntegrated = class (TTestCase) @@ -22,8 +22,10 @@ type published procedure TestGetFileAttributes; procedure TestGetFileAttributesEx; + procedure TestFilesOpenClose; end; + (***) implementation (***) @@ -34,7 +36,7 @@ end; function TestIntegrated.GetRootDir: string; begin - result := SysUtils.ExtractFileDir(WinUtils.GetExePath) + '\Tests\Fs'; + result := VfsUtils.NormalizePath(SysUtils.ExtractFileDir(WinUtils.GetExePath) + '\Tests\Fs'); end; procedure TestIntegrated.SetUp; @@ -42,22 +44,20 @@ var RootDir: string; begin - if not Inited then begin - Inited := true; - RootDir := Self.GetRootDir; - VfsBase.ResetVfs(); - VfsBase.MapDir(RootDir, RootDir + '\Mods\FullyVirtual', 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\Apache', DONT_OVERWRITE_EXISTING); - VfsDebug.SetLoggingProc(LogSomething); - VfsControl.RunVfs(VfsBase.SORT_FIFO); - end; + RootDir := Self.GetRootDir; + VfsBase.ResetVfs(); + VfsBase.MapDir(RootDir, RootDir + '\Mods\FullyVirtual_2', 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\A', DONT_OVERWRITE_EXISTING); + VfsBase.MapDir(RootDir, RootDir + '\Mods\Apache', DONT_OVERWRITE_EXISTING); + VfsDebug.SetLoggingProc(LogSomething); + VfsControl.RunVfs(VfsBase.SORT_FIFO); end; procedure TestIntegrated.TearDown; begin - VfsBase.PauseVfs(); + VfsBase.ResetVfs(); VfsDebug.SetLoggingProc(nil); end; @@ -112,11 +112,58 @@ var begin RootDir := Self.GetRootDir; 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(318, GetFileSize(RootDir + '\default'), '{4}'); 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 RegisterTest(TestIntegrated.Suite); end. \ No newline at end of file diff --git a/VfsBase.pas b/VfsBase.pas index c8a2335..cd29687 100644 --- a/VfsBase.pas +++ b/VfsBase.pas @@ -530,15 +530,10 @@ begin try DisableVfsForThread; result := Func(Arg); - except - on E: Exception do begin - RestoreVfsForThread; - raise E; - end; - end; // .try - - RestoreVfsForThread; - end; // .with + finally + RestoreVfsForThread; + end; + end; end; // .function CallWithoutVfs begin diff --git a/VfsHooks.pas b/VfsHooks.pas index 0de1268..5339a21 100644 --- a/VfsHooks.pas +++ b/VfsHooks.pas @@ -32,6 +32,13 @@ var NativeNtClose: WinNative.TNtClose; 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 was tracked. Thus we rely heavily on VfsOpenFiles. @@ -66,7 +73,7 @@ begin result := DirPath + '\' + FilePath; end else begin result := DirPath + FilePath; - end; + end; end; end else begin result := FilePath; @@ -209,11 +216,9 @@ var ReplacedObjAttrs: WinNative.TObjectAttributes; HadTrailingDelim: boolean; - FileInfo: Windows.TWin32FindDataW; - 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; ReplacedObjAttrs := ObjectAttributes^; @@ -222,7 +227,7 @@ begin RedirectedPath := ''; 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; if RedirectedPath = '' then begin @@ -231,25 +236,33 @@ begin RedirectedPath := RedirectedPath + '\'; end; - if (RedirectedPath <> '') and (RedirectedPath[1] <> '\') then begin - RedirectedPath := '\??\' + RedirectedPath; + if RedirectedPath <> '' then begin + 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; - ReplacedObjAttrs.RootDirectory := 0; - ReplacedObjAttrs.Attributes := ReplacedObjAttrs.Attributes or WinNative.OBJ_CASE_INSENSITIVE; - ReplacedObjAttrs.ObjectName.AssignExistingStr(RedirectedPath); + with VfsOpenFiles.OpenFilesCritSection do begin + Enter; - 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 - VfsOpenFiles.SetOpenedFileInfo(FileHandle^, TOpenedFile.Create(FileHandle^, ExpandedPath)); - end; + if (result = WinNative.STATUS_SUCCESS) and (ExpandedPath <> '') then begin + VfsOpenFiles.SetOpenedFileInfo(FileHandle^, TOpenedFile.Create(FileHandle^, ExpandedPath)); + end; + + Leave; + end; if VfsDebug.LoggingEnabled 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 - 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; // .function Hook_NtCreateFile @@ -257,11 +270,12 @@ end; // .function Hook_NtCreateFile function Hook_NtClose (OrigFunc: WinNative.TNtClose; hData: HANDLE): NTSTATUS; stdcall; begin if VfsDebug.LoggingEnabled then begin - WriteLog('NtClose', Format('Handle: %x', [integer(hData)])); + WriteLog('[ENTER] NtClose', Format('Handle: %x', [integer(hData)])); end; - with OpenFilesCritSection do begin + with VfsOpenFiles.OpenFilesCritSection do begin Enter; + result := OrigFunc(hData); if WinNative.NT_SUCCESS(result) then begin @@ -272,7 +286,7 @@ begin end; if VfsDebug.LoggingEnabled then begin - WriteLog('NtClose', Format('Status: %x', [integer(result)])); + WriteLog('[LEAVE] NtClose', Format('Status: %x', [integer(result)])); end; end; // .function Hook_NtClose @@ -526,36 +540,41 @@ begin NativeNtQueryAttributesFile := VfsPatching.SpliceWinApi ( VfsApiDigger.GetRealProcAddress(NtdllHandle, 'NtQueryAttributesFile'), - @Hook_NtQueryAttributesFile + @Hook_NtQueryAttributesFile, + @NtQueryAttributesFilePatch ); WriteLog('InstallHook', 'Installing NtQueryFullAttributesFile hook'); NativeNtQueryFullAttributesFile := VfsPatching.SpliceWinApi ( VfsApiDigger.GetRealProcAddress(NtdllHandle, 'NtQueryFullAttributesFile'), - @Hook_NtQueryFullAttributesFile + @Hook_NtQueryFullAttributesFile, + @NtQueryFullAttributesFilePatch ); - // WriteLog('InstallHook', 'Installing NtOpenFile hook'); - // NativeNtOpenFile := VfsPatching.SpliceWinApi - // ( - // VfsApiDigger.GetRealProcAddress(NtdllHandle, 'NtOpenFile'), - // @Hook_NtOpenFile - // ); + WriteLog('InstallHook', 'Installing NtOpenFile hook'); + NativeNtOpenFile := VfsPatching.SpliceWinApi + ( + VfsApiDigger.GetRealProcAddress(NtdllHandle, 'NtOpenFile'), + @Hook_NtOpenFile, + @NtOpenFilePatch + ); - // WriteLog('InstallHook', 'Installing NtCreateFile hook'); - // NativeNtCreateFile := VfsPatching.SpliceWinApi - // ( - // VfsApiDigger.GetRealProcAddress(NtdllHandle, 'NtCreateFile'), - // @Hook_NtCreateFile - // ); + WriteLog('InstallHook', 'Installing NtCreateFile hook'); + NativeNtCreateFile := VfsPatching.SpliceWinApi + ( + VfsApiDigger.GetRealProcAddress(NtdllHandle, 'NtCreateFile'), + @Hook_NtCreateFile, + @NtCreateFilePatch + ); - // WriteLog('InstallHook', 'Installing NtClose hook'); - // NativeNtClose := VfsPatching.SpliceWinApi - // ( - // VfsApiDigger.GetRealProcAddress(NtdllHandle, 'NtClose'), - // @Hook_NtClose - // ); + WriteLog('InstallHook', 'Installing NtClose hook'); + NativeNtClose := VfsPatching.SpliceWinApi + ( + VfsApiDigger.GetRealProcAddress(NtdllHandle, 'NtClose'), + @Hook_NtClose, + @NtClosePatch + ); // WriteLog('InstallHook', 'Installing NtQueryDirectoryFile hook'); // NativeNtQueryDirectoryFile := VfsPatching.SpliceWinApi @@ -569,6 +588,28 @@ begin end; // .with end; // .procedure InstallHooks +procedure UninstallHooks; begin + with HooksCritSection do begin + Enter; + + NtQueryAttributesFilePatch.Rollback; + NtQueryFullAttributesFilePatch.Rollback; + NtOpenFilePatch.Rollback; + NtCreateFilePatch.Rollback; + NtClosePatch.Rollback; + + Leave; + end; +end; + +initialization HooksCritSection.Init; +finalization + with VfsBase.VfsCritSection do begin + Enter; + VfsBase.ResetVfs; + UninstallHooks; + Leave; + end; end. \ No newline at end of file diff --git a/VfsPatching.pas b/VfsPatching.pas index 1385180..1a1479f 100644 --- a/VfsPatching.pas +++ b/VfsPatching.pas @@ -4,17 +4,24 @@ unit VfsPatching; All hooks are thread-safe. *) - (***) interface (***) uses 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. The argument is callable pointer, used to execute original function. The pointer is passed as THE FIRST argument before other arguments. *) -function SpliceWinApi (OrigFunc, HandlerFunc: pointer): pointer; +function SpliceWinApi (OrigFunc, HandlerFunc: pointer; {n} AppliedPatch: PAppliedPatch = nil): pointer; (***) implementation (***) @@ -68,7 +75,7 @@ begin end; end; // .function WritePatchAtCode -function SpliceWinApi (OrigFunc, HandlerFunc: pointer): pointer; +function SpliceWinApi (OrigFunc, HandlerFunc: pointer; {n} AppliedPatch: PAppliedPatch = nil): pointer; const CODE_ADDR_ALIGNMENT = 8; @@ -121,9 +128,23 @@ begin // Create and apply hook at target function start p.Clear(); 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); // * * * * * // p.Release; end; +procedure TAppliedPatch.Rollback; +begin + if Self.Bytes <> nil then begin + WriteAtCode(Length(Self.Bytes), @Self.Bytes[0], Self.Addr); + end; +end; + end. \ No newline at end of file diff --git a/_TODO_.txt b/_TODO_.txt index 32d7a87..bc0e61d 100644 --- a/_TODO_.txt +++ b/_TODO_.txt @@ -5,6 +5,10 @@ Move copyright to single file license? SetCurrentDirectoryW(GetCurrentDirectoryW) 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 *) SetProcessDEPPolicyAddr := Windows.GetProcAddress(Kernel32Handle, 'SetProcessDEPPolicy');