Crash on VirtualPC

Please report only one bug per message!

Moderators: white, Hacker, petermad, Stefan2

User avatar
MarcinW
Power Member
Power Member
Posts: 852
Joined: 2012-01-23, 15:58 UTC
Location: Poland

Crash on VirtualPC

Post by *MarcinW »

This is NOT a problem with Total Commander. It's a problem with a popular, buggy driver from Virtual Machine Additions. However, this looks like a problem with Total Commander, because it crashes. So I write this post here to avoid searching for the cause of this problem by the Author or by someone else in the future. @ghisler(Author): you may consider adding a test for buggy driver, as it is described at the end of this post.



Symptoms:

There is a command "Share Folder..." on VirtualPC. It creates a network drive on a virtualized Windows, the default drive letter is Z:. This network drive links to a folder on the host machine.

On virtualized Windows, after displaying contents of such a shared folder or after copying files from/to that folder:
- Total Commander crashes with message like "Fatal error in copy thread, aborting!" or "Access violation at address 7C910A19. Read of address 00000000.",
- Total Commander disappears rapidly without any error message.

This problem doesn't occur on Windows 9x family: 95, 98 and ME.



Cause and solution:

The cause of this problem is a buggy driver mrxvpc.sys (Virtual Machine Folder Sharing Driver) on virtualized Windows. It is a part of Virtual Machine Additions (menu "Action" -> "Install or Update Virtual Machine Additions" on VirtualPC):
- mrxvpc.sys (ver. 13.306.0.0) that comes with VirtualPC 2004 SP1 is buggy,
- mrxvpc.sys (ver. 13.803.0.0) that comes with VirtualPC 2007 is buggy,
- mrxvpc.sys (ver. 13.820.0.0) that comes with VirtualPC 2007 SP1 is OK.
Default location on virtualized Windows: C:\Windows\System32\Drivers\mrxvpc.sys

If you don't use Virtual Machine Additions, you are safe. But if you use:

1) Migrate from VirtualPC 2004, 2004 SP1 or 2007 to VirtualPC 2007 SP1.

2) After migrating, upgrade Virtual Machine Additions on all your Windows virtual machines (not required for Windows 95, 98 and ME virtual machines).

3) If you migrated to VirtualPC 2007 SP1 some time ago, you might not upgraded Virtual Machine Additions on all your Windows virtual machines after the migration. So do it now.

4) If you for any reason can't upgrade to VirtualPC 2007 SP1, find version 13.820.0.0 of mrxvpc.sys (on some other virtualized Windows machine) and copy it to your own virtualized Windows machines. And reboot them.



Long story:

On my virtualized Windows, during copying files from a folder shared by VirtualPC, Total Commander sometimes disappeared or crashed with errors like this:

Code: Select all

---------------------------
Total Commander 8.01
---------------------------
Fatal error in copy thread, aborting!
Access violation at address 7C911689. Read of address 00000018
Windows XP SP3 5.1 (Build 2600)

Please report this error to the Author, with a description
of what you were doing when this error occurred!

Windows exception: C0000005
Stack trace:
7C911689
421CDD  421D43  6753E0  67B540  67CB9C  67E12D
>417CB8  40362C
Raw:
421CDD  6D6BFB  5DCF24  41E21B  41E224  4020A2
4020A2  402226  402249  6D6B95  6D69F0  416DB0
416D7C  6820BE  68213B  421D43  6753E0  669CF7
6D6BFB  6D6CB7  67B540  67CB9C  6D6E13  6CFB90
6D6FA7  6CFCCD  67E12D  4023C7  4023EF  417CB8
40362C
Press Ctrl+C to copy this report!
---------------------------
OK
---------------------------
or this:

Code: Select all

---------------------------
Total Commander 8.01
---------------------------
Access violation at address 7C910A19. Read of address 00000000.
Access violation at address 7C910A19. Read of address 00000000
Windows XP SP3 5.1 (Build 2600)

Please report this error to the Author, with a description
of what you were doing when this error occurred!

Windows exception: C0000005
Stack trace:
7C910A19
4460A5  446A22  >423F38  445FE3  423F38  402D65
667AF5  444AD1  446A22  423F38  445FE3  423F38
429556  42969C  6D9F34
Raw:
445F51  423F38  446D9B  447A70  444AD1  446CF1
446CF1  4349F5  4460A5  446A22  423F38  445FE3
423F38  44670D  42673A  425234  402D65  668B34
667AF5  4460A5  446A22  446A45  423F38  445F51
423F38  402E4A  444AD1  446CF1  447AA1  425C14
4460A5  446A22  423F38  445FE3  423F38  429556
42969C  429856  6D9F34
Press Ctrl+C to copy this report!
Continue execution?
---------------------------
Tak   Nie
---------------------------
So I carried out a small investigation:

1) I launched the IDA disassembler/debugger, launched Total Commander under IDA and checked addresses from the crash reports: 7C911689 and 7C910A19. Both of them are inside ntdll.dll, inside the same function.

2) I downloaded symbols for the debugger (a set of *.pdb files with an installer) from Microsoft. After loading ntdll.pdb to IDA I realized, that both 7C910A19 and 7C911689 addresses are inside ntdll.dll!RtlpCoalesceFreeBlocks function (used - among others - by ntdll.dll!RtlFreeHeap). I found the following information about RtlpCoalesceFreeBlocks in a great "Heap Corruption: A Case Study" article:

"is called when a block of heap memory is freed and the heap manager detects that there are adjacent blocks that are also free. In this case, the 2 or 3 adjacent blocks are merged into one, larger free block, so as to reduce heap fragmentation. The access violation occurring in ntdll.dll!RtlpCoalesceFreeBlocks() therefore indicates that, while manipulating the heap data structures to merge bocks, we ran into a bad address. This in turn is an indication that some of those data structures became corrupted some time earlier."

So... we have a problem with a heap corruption. How can we generate such a problem? If we get 128 bytes of heap (using kernel32.dll!LocalAlloc API) and write more than 128 bytes there, we will overwrite the next block of heap memory (with its header). This causes a heap corruption.

3) Under the debugger we can observe debugging messages (sent by EXE/DLLs that use kernel32.dll!OutputDebugString API). Luckily, ntdll.dll - which manages the heap - sends debugging messages in case of heap corruption. So I configured IDA to stop on debugger messages, launched Total Commander under IDA and I started to reproduce the crash (by entering into a problematic shared folder). IDA stopped with the following debugging message:

Code: Select all

Debugged application message: HEAP[TOTALCMD.EXE]: .
Debugged application message: Heap block at 00120E98 modified at 00121EA0 past requested size of 1000
I checked the call stack under IDA and I noticed that Total Commander was inside the kernel32.dll!FindClose API call.

4) As Microsoft states, "If [...] the corruption happens in the small fill pattern at end of buffer, for alignment reasons the corruption will be detected only when the block is freed."

So: we detected a heap corruption during freeing a block of heap memory - inside FindClose API call. FindClose finalizes folder reading, so this block of heap memory should have been allocated earlier, inside FindFirstFileW or FindNextFileW API call (Total Commander reads folder contents by calling FindFirstFileW, then FindNextFileW in a loop, and finalizes searching with FindClose). Where exactly was this block allocated? I put a breakpoint at the beginning of kernel32.dll!FindFirstFileW and also at the beginning of kernel32.dll!FindNextFileW and I went into the shared folder in Total Commander again. Debugger stopped at the beginning of FindFirstFileW call - so I put an additional breakpoint inside ntdll.dll!RtlAllocateHeap. Bingo! During first call of FindNextFileW, it allocates 0x1000 bytes of heap (as in our debugging message: "...past requested size of 1000"). So I executed RtlAllocateHeap and I received a pointer to the newly allocated block of heap memory, 0x001013E0:

There is an 8-byte header before this block (see picture in "Heap Corruption: A Case Study"):

Code: Select all

001013D8  03 02 58 00 8F 07 18 00  ..X.Ć...
First word is a size of our block in 8-byte units: 0x03 0x02 -> 0x0203 -> 0x0203 * 8 -> 0x1018 bytes =
= 8-byte header + 0x1000 requested bytes + 16-byte footer


And our 0x1000 of bytes:

Code: Select all

001013E0  0D F0 AD BA 0D F0 AD BA  0D F0 AD BA 0D F0 AD BA  .­ş¦.­ş¦.­ş¦.­ş¦
001013F0  0D F0 AD BA 0D F0 AD BA  0D F0 AD BA 0D F0 AD BA  .­ş¦.­ş¦.­ş¦.­ş¦
00101400  0D F0 AD BA 0D F0 AD BA  0D F0 AD BA 0D F0 AD BA  .­ş¦.­ş¦.­ş¦.­ş¦
00101410  0D F0 AD BA 0D F0 AD BA  0D F0 AD BA 0D F0 AD BA  .­ş¦.­ş¦.­ş¦.­ş¦
...
001023A0  0D F0 AD BA 0D F0 AD BA  0D F0 AD BA 0D F0 AD BA  .­ş¦.­ş¦.­ş¦.­ş¦
001023B0  0D F0 AD BA 0D F0 AD BA  0D F0 AD BA 0D F0 AD BA  .­ş¦.­ş¦.­ş¦.­ş¦
001023C0  0D F0 AD BA 0D F0 AD BA  0D F0 AD BA 0D F0 AD BA  .­ş¦.­ş¦.­ş¦.­ş¦
001023D0  0D F0 AD BA 0D F0 AD BA  0D F0 AD BA 0D F0 AD BA  .­ş¦.­ş¦.­ş¦.­ş¦
Bytes above, 0x0D 0xF0 0xAD 0xBA, written as double word, are 0xBAADF00D - which looks like "bad food" ;) Microsoft supports us in debugging by filling empty buffers with this value (unless user requested a block of memory initialized with zeroes).


And there is also a 16-byte footer at the end of our block of memory:

Code: Select all

001023E0  AB AB AB AB AB AB AB AB  00 00 00 00 00 00 00 00  źźźźźźźź........
Library ntdll.dll checks footer bytes before freeing a heap block - values other than 0xAB mean that a buffer overflow has occurred and ntdll.dll will send us a debugging message.

5) Hardware breakpoints don't work well under virtualized Windows, so I couldn't use them to detect writing to our 0x1000-byte block of heap memory. So I traced program manually and I noticed that the block is being used in the call to ntdll.dll!NtQueryDirectoryFile. This function reads a portion of a folder contents and fills our 0x1000-byte buffer with FILE_FULL_DIR_INFORMATION records - one record for each file in the folder. Records have variable length (which depends on the length of a file name). FindNextFileW calls NtQueryDirectoryFile internally to receive file names existing inside the folder. When consecutive calls to FindNextFileW will return all names from our 0x1000-byte buffer, FindNextFileW calls NtQueryDirectoryFile again to receive the next portion of folder contents.

After few calls to NtQueryDirectoryFile I noticed that the footer of our 0x1000-byte block of memory has been overwritten with four zeroes:

Code: Select all

001023E0  00 00 00 00 AB AB AB AB  00 00 00 00 00 00 00 00  ....źźźź........
(Side note: calling now: HeapValidate(GetProcessHeap,0,nil) will return FALSE, which tells that the heap has been corrupted.)


And, after calling FindClose, the debugger returned a message again:

Code: Select all

Debugged application message: HEAP[TOTALCMD.EXE]: .
Debugged application message: Heap block at 001013D8 modified at 001023E0 past requested size of 1000
Not every call to NtQueryDirectoryFile causes the buffer overflow - this happens only if records inside the buffer occupy exactly 0x1000 bytes. If record chain is shorter than 0x1000 bytes, these four zeroes are still being placed at the end of the record chain, but luckily they are still inside the buffer.

So the cause of the problem is inside the NtQueryDirectoryFile call. NtQueryDirectoryFile simply makes a kernel mode call, so I couldn't trace this call using IDA. This kernel mode call calls a filesystem driver.

6) I was able to reproduce this bug only when listing contents of a folder that has been shared with the host machine by VirtualPC. I copied this folder to the local disk of virtualized Windows and I was unable to reproduce this bug. When I mapped original folder as a network drive manually (I shared this folder on the host machine and I mapped it as a network drive on the virtualized Windows), I was also unable to reproduce the bug. So the conclusion is: the bug is related only to command "Share Folder..." on VirtualPC.

I noticed accidentally, that there are differences in drive properties for a network drive mapped manually and a network drive created by VirtualPC. When I right-click a manually mapped network drive in "My Computer" and choose Properties command, I can see: "Filesystem: NTFS" (NTFS is a filesystem on a host computer). When I check properties of a network drive created by VirtualPC, there is: "Filesystem: Host Filesystem".

So I searched all files inside virtualized Windows folder for string "Host Filesystem" (in unicode) and I found a driver mrxvpc.sys. After replacing it with a version from VirtualPC 2007 SP1 the problem disappeared, what I verified using IDA: NtQueryDirectoryFile returns now identical results, but without these four zeroes at the end of the FILE_FULL_DIR_INFORMATION record chain.

And this is the end of the story. Please note that the buggy driver is called not only by NtQueryDirectoryFile API, but also by other APIs. This may overwrite some memory areas other than a footer of a heap block - for example some local stack variables. So using buggy mrxvpc.sys driver may lead to unpredictable results, including data losses. It's worth to update it.



Checking for existence of buggy driver:

Total Commander x32 could warn the user if he uses a buggy version of the driver. This could prevent the user from blaming Total Commander for crashes and prevent him from data losses. This can be done on Total Commander start or only on accessing a drive shared by VirtualPC. Total Commander x64 will never suffer from a buggy driver - it can run only on 64-bit systems, and VirtualPC doesn't currently support 64-bit guest systems. The code below works from Delphi 2 to Delphi XE2, on all versions of Windows (starting from Windows 95).

Function IsBuggyVirtualPCDriverLoaded checks if the mrxvpc.sys driver is loaded. If so, it checks its version resources.

Function IsDriveSharedOnNTByVirtualPC queries existing devices. When drive Z: is being shared, there is a device "\Device\MicrosoftVMFolderSharing\;Z:\Z\Z". We can't check its existence by opening it using CreateFile API, because it may be inaccessible if drive was mounted, but hasn't been accessed yet. So we query devices using NtOpenSymbolicLinkObject and NtQuerySymbolicLinkObject APIs. Existing devices can be viewed using WinObj tool.


Checking for existence of buggy driver:

Code: Select all

uses
  Windows, SysUtils, PSAPI;

{$IFNDEF WIN32}
function IsBuggyVirtualPCDriverLoaded : Boolean;
begin
  Result:=False;
end;
{$ELSE}
function IsBuggyVirtualPCDriverLoaded : Boolean;

  function GetFileVersion(const FileName : AnsiString) : Cardinal;
  var
    FileNameCopy : AnsiString;
    VersionInfoSize : Cardinal;
    Temp : UINT;
    Buffer : Pointer;
    VSFixedFileInfo : PVSFixedFileInfo;
  begin
    Result:=High(Result);

    {GetFileVersionInfoA/W may modify the filename parameter,
     so we create a local, writable copy of FileName}
    FileNameCopy:=FileName;
    UniqueString(FileNameCopy);

    VersionInfoSize:=GetFileVersionInfoSizeA(PAnsiChar(FileNameCopy),Temp);
    if VersionInfoSize > 0 then
    begin
      GetMem(Buffer,VersionInfoSize);
      try
        if GetFileVersionInfoA(PAnsiChar(FileNameCopy),0,VersionInfoSize,Buffer) then
        if VerQueryValueA(Buffer,'\',Pointer(VSFixedFileInfo),Temp) then
        if VSFixedFileInfo <> nil then
          Result:=VSFixedFileInfo^.dwFileVersionMS;
      finally
        FreeMem(Buffer);
      end;
    end;
  end;

  function Min(A : Cardinal; B : Cardinal) : Cardinal;
  begin
    if A < B then
      Result:=A
    else
      Result:=B;
  end;

{$IFDEF VER90} {Delphi 2}
  {In all places of the AnsiCompareFileName usage:
   1) one of arguments is a non-MBCS string and
   2) we don't need to know an exact result, we must only know if it is zero or not.
   So we can exceptionally use AnsiCompareText instead of AnsiCompareFileName here.}
  function AnsiCompareFileName(const S1 : AnsiString; const S2 : AnsiString) : Integer;
  begin
    Result:=AnsiCompareText(S1,S2);
  end;

  {In all places of the IncludeTrailingBackslash usage:
   1) input string comes from GetSystemDirectoryA call or
   2) input string comes from GetSystemWindowsDirectoryA or GetWindowsDirectoryA call.
   These functions always return path without a backslash at the end, unless the result
   points to the root directory. So we can omit MBCS problems by checking the string
   length instead of checking if the last char is a backslash.}
  function IncludeTrailingBackslash(const S : AnsiString) : AnsiString;
  begin
    if Length(S) > 3 then
      Result:=S+'\'
    else
      Result:=S;
  end;
{$ENDIF}

type
  PPointerArray = ^TPointerArray;
  TPointerArray = packed array[0..High(Integer) div SizeOf(Pointer)-1] of Pointer;
const
  STR_QUESTIONMARKS = '\??\';
  STR_SYSTEMROOT = '\SystemRoot\';
  STR_BACKSLASH = '\';
var
  GetSystemWindowsDirectoryA : function(lpBuffer : PAnsiChar; uSize : UINT) : UINT; stdcall;
  Len : UINT;
  SystemDirectory : AnsiString;
  WindowsDirectory : AnsiString;
  WindowsDrive : AnsiString;
  PSAPIHandle : HINST;
  Buffer : packed array[0..$8000-1] of AnsiChar;
  Needed : UINT;
  Needed2 : UINT;
  ImageBaseArray : PPointerArray;
  I : Integer;
  S : AnsiString;
begin
  Result:=False;

  GetSystemWindowsDirectoryA:=GetProcAddress(GetModuleHandle(kernel32),PAnsiChar('GetSystemWindowsDirectoryA'));
  if not Assigned(GetSystemWindowsDirectoryA) then
  begin
    GetSystemWindowsDirectoryA:=GetProcAddress(GetModuleHandle(kernel32),PAnsiChar('GetWindowsDirectoryA'));
    if not Assigned(GetSystemWindowsDirectoryA) then
      Exit;
  end;

  SetLength(SystemDirectory,GetSystemDirectoryA(nil,0));
  Len:=GetSystemDirectoryA(PAnsiChar(SystemDirectory),Length(SystemDirectory));
  if (Len = 0) or (Len > UINT(Length(SystemDirectory))) then
    Exit;
  SetLength(SystemDirectory,Len);
  SystemDirectory:=AnsiString(IncludeTrailingBackslash(string(SystemDirectory)));

  SetLength(WindowsDirectory,GetSystemWindowsDirectoryA(nil,0));
  Len:=GetSystemWindowsDirectoryA(PAnsiChar(WindowsDirectory),Length(WindowsDirectory));
  if (Len = 0) or (Len > UINT(Length(WindowsDirectory))) then
    Exit;
  SetLength(WindowsDirectory,Len);
  WindowsDirectory:=AnsiString(IncludeTrailingBackslash(string(WindowsDirectory)));

  WindowsDrive:=AnsiString(ExtractFileDrive(string(WindowsDirectory)));

  {Windows NT4 doesn't have PSAPI.dll installed by default, but we try}
  PSAPIHandle:=LoadLibrary('PSAPI.dll');
  if PSAPIHandle = 0 then
  begin
    {Virtual Machine Additions has its own copy of PSAPI.dll. It is always located
     in the VMADD subdirectory of Windows directory, user has no possibility to
     change this location during installation of Virtual Machine Additions}
    PSAPIHandle:=LoadLibraryA(PAnsiChar(WindowsDirectory+'VMADD\PSAPI.dll'));
    if PSAPIHandle = 0 then
      Exit;
  end;
  {Now LoadLibrary('PSAPI.dll') call inside PSAPI.pas will always be successful}
  try
    if EnumDeviceDrivers(nil,0,Needed) then
    if Needed > 0 then
    begin
      GetMem(ImageBaseArray,Needed);
      try
        if EnumDeviceDrivers(PPointer(ImageBaseArray),Needed,Needed2) then
        for I:=0 to (Min(Needed,Needed2) div SizeOf(ImageBaseArray^[0]))-1 do
        begin
          GetDeviceDriverBaseNameA(ImageBaseArray^[I],PAnsiChar(@Buffer),SizeOf(Buffer) div SizeOf(Buffer[0]));
          if AnsiCompareFileName(string(Buffer),'mrxvpc.sys') = 0 then
          begin
            GetDeviceDriverFileNameA(ImageBaseArray^[I],PAnsiChar(@Buffer),SizeOf(Buffer) div SizeOf(Buffer[0]));
            S:=Buffer;

            if AnsiCompareFileName(string(Copy(S,1,Length(STR_QUESTIONMARKS))),STR_QUESTIONMARKS) = 0 then
              S:=Copy(S,1+Length(STR_QUESTIONMARKS),High(Integer));
            if AnsiCompareFileName(string(Copy(S,1,Length(STR_SYSTEMROOT))),STR_SYSTEMROOT) = 0 then
              S:=WindowsDirectory+Copy(S,1+Length(STR_SYSTEMROOT),High(Integer));
            if AnsiCompareFileName(string(Copy(S,1,Length(STR_BACKSLASH))),STR_BACKSLASH) = 0 then
              S:=WindowsDrive+S;
            if Length(ExtractFileDir(string(S))) = 0 then
              S:=SystemDirectory+'Drivers\'+S; {"Drivers" subdirectory name is hardcoded in setupapi.dll (in some
                                                Windows versions) and in ntoskrnl.exe (in all Windows versions,
                                                including 64-bit - ntoskrnl.exe loads drivers during booting)}

            Result:=GetFileVersion(S) < $000D0334;
            Break;
          end;
        end;
      finally
        FreeMem(ImageBaseArray);
      end;
    end;
  finally
    FreeLibrary(PSAPIHandle);
  end;
end;
{$ENDIF}
Checking if drive is being shared by VirtualPC:

Code: Select all

uses
  Windows;

{$IFNDEF WIN32}
function IsDriveSharedOnNTByVirtualPC(Drive : Cardinal) : LongBool;
begin
  Result:=False;
end;
{$ELSE}
{Don't place Boolean as a result type, its size is not 4 bytes, so it may
 cause stack misalignment and - as a result - NTDLL functions to fail}
function IsDriveSharedOnNTByVirtualPC(Drive : Cardinal) : LongBool;
const
  FILE_SHARE_DELETE = $00000004;
var
  NtOpenSymbolicLinkObject : function(LinkHandle : Pointer; DesiredAccess : Integer; ObjectAttributes : Pointer) : Cardinal; stdcall;
  NtQuerySymbolicLinkObject : function(LinkHandle : THandle; LinkTarget : Pointer; ReturnedLength : Pointer) : Cardinal; stdcall;
  UnicodeString : packed record {UNICODE_STRING}
    Length : Word;
    MaximumLength : Word;
    Buffer : PWideChar;
  end;
  ObjectAttributes : packed record {OBJECT_ATTRIBUTES}
    Length : Cardinal;
    RootDirectory : THandle;
    ObjectName : Pointer;
    Attributes : Cardinal;
    SecurityDescriptor : Pointer;
    SecurityQualityOfService : Pointer;
  end;
  UnicodeBuffer : array[0..63] of WideChar;
  Module : HINST;
  Handle : THandle;
  S : AnsiString;
  I : Integer;
begin
  Result:=False;

  if Drive > Ord('Z')-Ord('A') then
    Exit;

  Module:=GetModuleHandle('ntdll.dll');
  if Module = 0 then
    Exit;
  NtOpenSymbolicLinkObject:=GetProcAddress(Module,PAnsiChar('NtOpenSymbolicLinkObject'));
  if not Assigned(NtOpenSymbolicLinkObject) then
    Exit;
  NtQuerySymbolicLinkObject:=GetProcAddress(Module,PAnsiChar('NtQuerySymbolicLinkObject'));
  if not Assigned(NtQuerySymbolicLinkObject) then
    Exit;

  S:=AnsiString('\??\')+AnsiChar(Ord('A')+Drive)+AnsiString(':');
  StringToWideChar(string(S),UnicodeBuffer,SizeOf(UnicodeBuffer) div SizeOf(UnicodeBuffer[0]));

  UnicodeString.Length:=Length(S)*SizeOf(UnicodeBuffer[0]);
  UnicodeString.MaximumLength:=SizeOf(UnicodeBuffer);
  UnicodeString.Buffer:=@UnicodeBuffer;

  ObjectAttributes.Length:=SizeOf(ObjectAttributes);
  ObjectAttributes.RootDirectory:=0;
  ObjectAttributes.ObjectName:=@UnicodeString;
  ObjectAttributes.Attributes:=0;
  ObjectAttributes.SecurityDescriptor:=nil;
  ObjectAttributes.SecurityQualityOfService:=nil;
  if NtOpenSymbolicLinkObject(@Handle,-2147352575{0x80020001 = GENERIC_READ or READ_CONTROL or FILE_READ_DATA},@ObjectAttributes) = 0 then
  try
    S:=AnsiString('\Device\MicrosoftVMFolderSharing\;')+AnsiChar(Ord('A')+Drive)+':\'+AnsiChar(Ord('A')+Drive)+'\'+AnsiChar(Ord('A')+Drive);

    UnicodeString.Length:=0;
    if NtQuerySymbolicLinkObject(Handle,@UnicodeString,nil) = 0 then
    if UnicodeString.Length = Length(S)*SizeOf(UnicodeBuffer[0]) then
    begin
      Result:=True;
      for I:=1 to Length(S) do
      if WideChar(S[I]) <> UnicodeBuffer[I-1] then
      begin
        Result:=False;
        Break;
      end;
    end;
  finally
    CloseHandle(Handle);
  end;
end;
{$ENDIF}
In Delphi 2 there is no PSAPI.pas unit, so here are the required declarations:

Code: Select all

unit PSAPI;

interface

uses
  Windows;

type
  PPointer = ^Pointer;

function EnumDeviceDrivers(lpImageBase: PPointer; cb: DWORD; var lpcbNeeded: DWORD): BOOL;
function GetDeviceDriverBaseNameA(ImageBase: Pointer; lpBaseName: PAnsiChar; nSize: DWORD): DWORD;
function GetDeviceDriverFileNameA(ImageBase: Pointer; lpFileName: PAnsiChar; nSize: DWORD): DWORD;

implementation

var
  hPSAPI: HINST;
  _EnumDeviceDrivers: function (lpImageBase: PPointer; cb: DWORD; var lpcbNeeded: DWORD): BOOL; stdcall;
  _GetDeviceDriverBaseNameA: function (ImageBase: Pointer; lpBaseName: PAnsiChar; nSize: DWORD): DWORD; stdcall;
  _GetDeviceDriverFileNameA: function (ImageBase: Pointer; lpFileName: PAnsiChar; nSize: DWORD): DWORD; stdcall;

function CheckPSAPILoaded: Boolean;
begin
  if hPSAPI = 0 then
  begin
    hPSAPI := LoadLibrary('PSAPI.dll');
    if hPSAPI = 0 then
    begin
      Result := False;
      Exit;
    end;
    @_EnumDeviceDrivers := GetProcAddress(hPSAPI, PAnsiChar('EnumDeviceDrivers'));
    @_GetDeviceDriverBaseNameA := GetProcAddress(hPSAPI, PAnsiChar('GetDeviceDriverBaseNameA'));
    @_GetDeviceDriverFileNameA := GetProcAddress(hPSAPI, PAnsiChar('GetDeviceDriverFileNameA'));
  end;
  Result := True;
end;

function EnumDeviceDrivers(lpImageBase: PPointer; cb: DWORD; var lpcbNeeded: DWORD): BOOL;
begin
  if CheckPSAPILoaded then
    Result := _EnumDeviceDrivers(lpImageBase, cb, lpcbNeeded)
  else Result := False;
end;

function GetDeviceDriverBaseNameA(ImageBase: Pointer; lpBaseName: PAnsiChar; nSize: DWORD): DWORD;
begin
  if CheckPSAPILoaded then
    Result := _GetDeviceDriverBasenameA(ImageBase, lpBaseName, nSize)
  else Result := 0;
end;

function GetDeviceDriverFileNameA(ImageBase: Pointer; lpFileName: PAnsiChar; nSize: DWORD): DWORD;
begin
  if CheckPSAPILoaded then
    Result := _GetDeviceDriverFileNameA(ImageBase, lpFileName, nSize)
  else Result := 0;
end;

end.
Regards
Last edited by MarcinW on 2014-02-04, 13:18 UTC, edited 4 times in total.
umbra
Power Member
Power Member
Posts: 871
Joined: 2012-01-14, 20:41 UTC

Post by *umbra »

Hi,
nice investigation. It reminds me of Mark Russinovich’s "The case of the ..." series.
And to the topic. Network shares (and mapped drives) are generally very unreliable in virtualized environments. You found a crash bug in VirtualPC, which has been deprecated for a few years but is still occasionally used. VirtualBox had a more insidious error for a rather long time - files copied in or out of a shared folder sometimes contained more or less data than they should. With no error message of course. And old versions of VmWare, which still many people use, had their own issues as well.
And while I cannot test VirtualPC right now, these issues affected all applications, not just TC. So is it the right idea to put a bunch of workarounds for old software inside TC?
Windows 7 Pro x64, Windows 10 Pro x64
User avatar
MarcinW
Power Member
Power Member
Posts: 852
Joined: 2012-01-23, 15:58 UTC
Location: Poland

Post by *MarcinW »

Hello,

I know that placing a bunch of workarounds for other products may not be a good idea in general. However, here we have a relatively popular product like VirtualPC, and this product crashes Total Commander. Any checking code, if added, will not slow down Total Commander, and this code isn't required for other versions than the 32-bit. Even Microsoft places workarounds for other products in Windows code. Sources of Windows NT/2000 have leaked some time ago and there are analyzes of the code comments (see here and more generally here).

The problem is that many users have the newest VirtualPC 2007 SP1 on the host machine installed, so they think that they are up-to-date and safe from data losses. But many of them haven't upgraded Virtual Machine Additions on their virtualized machines, so they use the newest VirtualPC on the host with old drivers on guests. And they are unaware of this threat. So the old, buggy software is still widely in use.

There are probably several millions or more of VirtualPC users around the world (one of the popular Polish mirrors has over 220 thousands of downloads now). Most of the VirtualPC users use also Total Commander as I suppose, and this bug crashes Total Commander. So we have a potential of thousands of users that experience crashes and maybe someday will send crash reports to the Author, wasting his time. So, because of the popularity of the problem, this may be worth to be checked by Total Commander - during its start, during accessing shared drives or during generating a crash report (to avoid analyzing crashes not caused by Total Commander). And to save users from data losses by the way.

Regards


P.S. If someone would like to reproduce the bug, here are the steps required:

1) Check version of mrxvpc.sys driver on your guest machine. It must be 13.803.0.0 or lower.
2) On host machine create folder tree: "C:\abc\test\" ("C:\test\" is not enough).
3) Create the following files inside the "test" folder:
- 9 files with 150 chars of name length (for example 150 chars or 146 chars + dot + 3 chars),
- 1 file with 148 chars of name length.
No other files may exist in this folder.
I know that nobody uses such long names, but the problem may also occur for many shorter names. It's easiest to reproduce with a few long names.
Final result: during listing of this folder there will be a folder ".", a folder "..", 9 files with 150-char names and 1 file with 148-char name. This will fill a 0x1000-byte buffer with FILE_FULL_DIR_INFORMATION records entirely.
4) Share folder "C:\abc\" as drive "Z:" using VirtualPC command.
5) On guest machine launch Total Commander and enter into "Z:\test\". This will corrupt the heap of Total Commander. Results are unpredictable - it may hang, it may disappear or it may fail during the next copy operation. But under a debugger you will always see a debugging message telling that the heap has been corrupted.
User avatar
ghisler(Author)
Site Admin
Site Admin
Posts: 48021
Joined: 2003-02-04, 09:46 UTC
Location: Switzerland
Contact:

Post by *ghisler(Author) »

2MarcinW
Nice analysis! Is there a workaround to prevent this heap corruption from happening? There is no alternative to calling FindFirstFileW/FindNextFileW, so what can I do when I detect this faulty driver?
Author of Total Commander
https://www.ghisler.com
User avatar
MarcinW
Power Member
Power Member
Posts: 852
Joined: 2012-01-23, 15:58 UTC
Location: Poland

Post by *MarcinW »

There is a workaround that can be done. Before this I'd like to check something inside the buggy driver, so I'll post a solution here at the end of the next week (I can't check the driver now).
User avatar
MarcinW
Power Member
Power Member
Posts: 852
Joined: 2012-01-23, 15:58 UTC
Location: Poland

Post by *MarcinW »

Here comes a workaround that I promised. I couldn't prepare it earlier because of some other, urgent work.

In the first place some additional info to my previous post:
- there is a small mistake in the text above: there should be FILE_ID_BOTH_DIR_INFORMATION instead of FILE_FULL_DIR_INFORMATION,
- in the code of IsBuggyVirtualPCDriverLoaded function, there is a comment: ""Drivers" subdirectory name is hardcoded in setupapi.dll". It's true, but not in all versions of Windows. However, "Drivers" subdirectory name is hardcoded also in ntoskrnl.exe, in all Windows versions, including 64-bit (ntoskrnl.exe, among other things, loads drivers during Windows booting),
- because the workaround is intended only for 32-bit operating systems (VirtualPC handles only 32-bit OSes), we don't have to worry about the filesystem redirection, that 64-bit Windows performs for 32-bit applications (so we don't have to call functions like Wow64DisableWow64FsRedirection inside the IsBuggyVirtualPCDriverLoaded function). The filesystem redirection applies - among others - to the "Drivers" directory, so this redirection would be the case if the patch would be intended also for 64-bit operating systems.



Well, the workaround below involves some kind of hacking, but very slight and fully safe: hooking of import tables and hooking of an export table. Antivirus software will not complain about this, because all modifications are only in memory and only in our own process. I successfully use these hooking techniques in my own software for a few years, without any problems, with all Windows versions, starting from Win95. We will use these techniques only under WinNT family and only in case of detection of a buggy driver.

Short explanation for those who are not familiar with imports and exports hooking: Almost every EXE/DLL file has in its structure a special table, named import table, and also another table, named export table. Import table informs the operating system that this EXE/DLL needs to call functions, let's say, "A1" and "A2" from a.dll, "B1" and "B2" from b.dll etc. So the operating system, during loading of our EXE/DLL into memory, loads also a.dll and b.dll (if not already loaded for our process), and then fills the import table of our EXE/DLL with pointers (addresses) to functions "A1", "A2", "B1" and "B2". After loading we can modify (hook) this table (in memory, not in file), so our EXE/DLL thinks for example that it calls "B2" function, but it calls now our own function (which normally does some special processing and then calls the original "B2" function, or inversely). And how does the operating system know, that a.dll exports functions "A1" and "A2"? The a.dll has an export table, which has pointers (addresses) to the exported functions (and also their names). After loading a.dll, when we modify (hook) a pointer to the "A1" function in its export table (in memory, not in file), so we can replace original "A1" function with our own - all DLLs, that will be loaded in the future and need "A1" function from a.dll will get address of our own function instead of the original "A1" function (a.dll, when statically linked, is loaded only once by our process, and all DLLs, that will be loaded in the future and need a.dll, will use already loaded and hooked a.dll).



Now some details:

As I checked in the code of mrxvpc.sys, the bug is specific to ntdll.dll!NtQueryDirectoryFile function, no other functions are affected. NtQueryDirectoryFile calls a filesystem driver, in our case - for shared drive letters - the buggy mrxvpc.sys, which may overflow data buffer supplied by NtQueryDirectoryFile.

Normal programs use ntdll.dll library only indirectly - by using kernel32.dll. When we call kernel32.dll!FindFirstFileA/W or kernel32.dll!FindNextFileA/W, the kernel32.dll calls internally the ntdll.dll!NtQueryDirectoryFile function. Beside kernel32.dll, some other system DLLs (for example apphelp.dll, cscdll.dll, ws2help.dll under WinXP) also use ntdll.dll!NtQueryDirectoryFile directly. And some of these DLLs may be mapped into the address space of our process.

So in order to patch all calls to the buggy mrxvpc.sys, we have to:
1) Create a wrapper for ntdll.dll!NtQueryDirectoryFile (named INJECTEDNtQueryDirectoryFile). This wrapper should:
a) allocate its own buffer for the results, which is 4 bytes bigger than the original buffer (these 4 bytes are a reserve for the buggy driver),
b) call the original NtQueryDirectoryFile function, with own, bigger buffer as the parameter,
c) copy contents of the own buffer to the original buffer (of course without these 4 bytes at the end).
2) Hook exports of ntdll.dll (which is always mapped into address space of our process) - entries of the export table, which point to NtQueryDirectoryFile, should be replaced and point to INJECTEDNtQueryDirectoryFile.
Result: all calls to GetProcAddress(GetModuleHandle('ntdll.dll'),'NtQueryDirectoryFile') will return address of our wrapper - so all DLLs that will be loaded in the future, and all DLLs that are already loaded and use delayed imports, will use the INJECTEDNtQueryDirectoryFile wrapper instead of the original ntdll.dll!NtQueryDirectoryFile function.
3) Hook imports of kernel32.dll (which is always mapped into address space of our process) - entries of the import table, which point to ntdll.dll!NtQueryDirectoryFile, should be replaced and point to INJECTEDNtQueryDirectoryFile.
Result: all imports, that has been resolved before we hooked the export table of ntdll.dll, will point to our wrapper instead of the original ntdll.dll!NtQueryDirectoryFile function.
4) Hook imports of all other DLLs, that are mapped into address space of our process and use ntdll.dll!NtQueryDirectoryFile. We can do it by enumerating all DLLs mapped into our address space and hooking their imports. Enumerating is unavailable under WinNT 4.0, but it's not a problem in our case - the only file in whole WinNT 4.0 SP6 operating system, that uses ntdll.dll!NtQueryDirectoryFile function, is kernel32.dll, hooked already in point 3. And Virtual Machine Additions don't work without SP6 under WinNT 4.0, so we don't have to worry about versions below SP6.

There is a theoretical possibility that some DLL will call GetProcAddress(GetModuleHandle('ntdll.dll'),'NtQueryDirectoryFile') before we hook export table of ntdll.dll and use this original address instead of our INJECTEDNtQueryDirectoryFile. But only system DLLs use ntdll.dll library and its NtQueryDirectoryFile function - and there is no need to call GetProcAddress in system DLLs, because they can statically link to ntdll.dll (and they do this).



The patch executes properly under all Windows versions, starting from Win95. It can be compiled from Delphi2 to Delphi XE3, as 32-bit or 64-bit (but this patch applies only to 32-bit operating systems, so conditional compilation will in fact remove this patch for 64-bit code). There are some strange expressions used to maintain compatibility with Delphi2 (for example "Result:=Cardinal(-2147483643)" instead of "Result:=$80000005") and with 64-bit code (for example using THandle in strange contexts, because THandle is 32-bit for 32-bit code and 64-bit for 64-bit code, and we can't use NativeUInt type, because older versions of Delphi don't define it).

Usage of the patch:
- for Delphi 2 only: copy PSAPI.pas from my post above to your project,
- copy PE.pas from this post to your project (see below),
- copy VirtualPCPatch.pas from this post to your project (see below), copy IsBuggyVirtualPCDriverLoaded function from my post above and paste it into the VirtualPCPatch.pas,
- simply add the unit VirtualPCPatch.pas to the "uses" section of the main .dpr file of your project - add this unit at the beginning of the "uses" list, so it will be loaded before any other units, which - during their initialization - may use FindFirstFile/FindNextFile functions or, more likely, may cause loading of some system DLLs, that use ntdll.dll!NtQueryDirectoryFile function.



Structure of the PE.pas unit:
- some type declarations for Delphi 2,
- function SwapImportedFunction,
- function SwapExportedFunction.
These functions allow to hook an import table or an export table of any DLL (or EXE itself - use HInstance as the first parameter). The unit can be compiled as 32-bit or 64-bit, so these functions work - with some limitations - for 32-bit or 64-bit EXEs/DLLs. They enumerate the whole import/export table and don't stop after finding a first entry to replace. Why?
a) Each imported function may be listed many times in the import table. For example, totalcmd.exe imports kernel32.dll!CloseHandle twice.
b) The same function may be exported with different names, so it resides in the export table more than once. For example, ntdll.dll!NtQueryDirectoryFile and ntdll.dll!ZwQueryDirectoryFile point to the same physical function.

Structure of the VirtualPCPatch.pas unit:
- place for pasting IsBuggyVirtualPCDriverLoaded function (from my post above),
- function INJECTEDNtQueryDirectoryFile, which is a wrapper for the original ntdll.dll!NtQueryDirectoryFile function and it solves the problem with a buggy mrxvpc.sys driver,
- procedure ProcesLoadedDLLs - it enumerates all loaded DLLs and hooks their imports,
- initialization section - it checks for the existence of a buggy mrxvpc.sys driver. If it detects a buggy driver, it hooks export table of ntdll.dll, hooks import table of kernel32.dll and hooks import table of all other loaded DLLs. Hooking should be performed as early as possible, it should be performed before a first call to the FindFirstFile/FindNextFile functions. The "initialization" section of the unit is a good place to perform hooking,
- finalization section - in unhooks all DLLs.



For test purposes I created a small DLL, which only uses the VirtualPCPatch.pas unit (and contains a global message hook). I injected this DLL into the address space of all processes, including the Total Commander process (using a small loader, which installs a global message hook from the DLL). Total Commander started to work stably even with a buggy driver (what I verified also with a debugger - no debugging messages about the heap corruption). See the code below (files Inj.dpr and Loader.dpr).



File 1 of 4: PE.pas (part of the patch)

Code: Select all

{This unit works with 32-bit and 64-bit code, with the limitations described below}

unit PE;

interface

uses
  Windows;

type
  PImageImportDescriptor = ^TImageImportDescriptor;
  TImageImportDescriptor = packed record
    case Byte of
      0 : (Characteristics : Cardinal);
      1 : (OriginalFirstThunk : Cardinal;
           TimeDateStamp : Cardinal;
           ForwarderChain : Cardinal;
           Name : Cardinal;
           FirstThunk : Cardinal);
  end;

{$IFDEF VER90} {Delphi 2}
const
  IMAGE_NUMBEROF_DIRECTORY_ENTRIES = 16;
  IMAGE_SIZEOF_SHORT_NAME = 8;
  IMAGE_DOS_SIGNATURE = $5A4D; {MZ}
  IMAGE_NT_SIGNATURE = $00004550; {PE00}
  IMAGE_DIRECTORY_ENTRY_EXPORT = 0;
  IMAGE_DIRECTORY_ENTRY_IMPORT = 1;
  IMAGE_SCN_MEM_SHARED = $10000000;

type
  PImageFileHeader = ^TImageFileHeader;
  TImageFileHeader = packed record
    Machine : Word;
    NumberOfSections : Word;
    TimeDateStamp : Cardinal;
    PointerToSymbolTable : Cardinal;
    NumberOfSymbols : Cardinal;
    SizeOfOptionalHeader : Word;
    Characteristics : Word;
  end;

  PImageDataDirectory = ^TImageDataDirectory;
  TImageDataDirectory = packed record
    VirtualAddress : Cardinal;
    Size : Cardinal;
  end;

  PImageOptionalHeader32 = ^TImageOptionalHeader32;
  TImageOptionalHeader32 = packed record
    {Standard fields}
    Magic : Word;
    MajorLinkerVersion : Byte;
    MinorLinkerVersion : Byte;
    SizeOfCode : Cardinal;
    SizeOfInitializedData : Cardinal;
    SizeOfUninitializedData : Cardinal;
    AddressOfEntryPoint : Cardinal;
    BaseOfCode : Cardinal;
    BaseOfData : Cardinal;
    {NT additional fields}
    ImageBase : Cardinal;
    SectionAlignment : Cardinal;
    FileAlignment : Cardinal;
    MajorOperatingSystemVersion : Word;
    MinorOperatingSystemVersion : Word;
    MajorImageVersion : Word;
    MinorImageVersion : Word;
    MajorSubsystemVersion : Word;
    MinorSubsystemVersion : Word;
    Win32VersionValue : Cardinal;
    SizeOfImage : Cardinal;
    SizeOfHeaders : Cardinal;
    CheckSum : Cardinal;
    Subsystem : Word;
    DllCharacteristics : Word;
    SizeOfStackReserve : Cardinal;
    SizeOfStackCommit : Cardinal;
    SizeOfHeapReserve : Cardinal;
    SizeOfHeapCommit : Cardinal;
    LoaderFlags : Cardinal;
    NumberOfRvaAndSizes : Cardinal;
    DataDirectory : packed array[0..IMAGE_NUMBEROF_DIRECTORY_ENTRIES-1] of TImageDataDirectory;
  end;

  PImageNtHeaders32 = ^TImageNtHeaders32;
  TImageNtHeaders32 = packed record
    Signature : Cardinal;
    FileHeader : TImageFileHeader;
    OptionalHeader : TImageOptionalHeader32;
  end;

  PImageNtHeaders = PImageNtHeaders32;
  TImageNtHeaders = TImageNtHeaders32;

  TISHMisc = packed record
    case Integer of
      0 : (PhysicalAddress : Cardinal);
      1 : (VirtualSize : Cardinal);
  end;

  PImageSectionHeader = ^TImageSectionHeader;
  TImageSectionHeader = packed record
    Name : packed array[0..IMAGE_SIZEOF_SHORT_NAME-1] of Byte;
    Misc : TISHMisc;
    VirtualAddress : Cardinal;
    SizeOfRawData : Cardinal;
    PointerToRawData : Cardinal;
    PointerToRelocations : Cardinal;
    PointerToLinenumbers : Cardinal;
    NumberOfRelocations : Word;
    NumberOfLinenumbers : Word;
    Characteristics : Cardinal;
  end;

  PImageExportDirectory = ^TImageExportDirectory;
  TImageExportDirectory = packed record
    Characteristics : Cardinal;
    TimeDateStamp : Cardinal;
    MajorVersion : Word;
    MinorVersion : Word;
    Name : Cardinal;
    Base : Cardinal;
    NumberOfFunctions : Cardinal;
    NumberOfNames : Cardinal;
    AddressOfFunctions : Cardinal;
    AddressOfNames : Cardinal;
    AddressOfNameOrdinals : Cardinal;
  end;

  PImageDosHeader = ^TImageDosHeader;
  TImageDosHeader = packed record
    e_magic : Word;
    e_cblp : Word;
    e_cp : Word;
    e_crlc : Word;
    e_cparhdr : Word;
    e_minalloc : Word;
    e_maxalloc : Word;
    e_ss : Word;
    e_sp : Word;
    e_csum : Word;
    e_ip : Word;
    e_cs : Word;
    e_lfarlc : Word;
    e_ovno : Word;
    e_res : array[0..3] of Word;
    e_oemid : Word;
    e_oeminfo : Word;
    e_res2 : array[0..9] of Word;
    _lfanew : LongInt;
  end;
{$ENDIF}

function SwapImportedFunction(Instance : HINST; OldFunctionAddr : Pointer; NewFunctionAddr : Pointer) : Boolean;
function SwapExportedFunction(Instance : HINST; OldFunctionAddr : Pointer; NewFunctionAddr : Pointer) : Boolean;

implementation

uses
  SysUtils;

var
  SystemInfo : TSystemInfo;

{$IFDEF VER90} {Delphi 2}
function FindHInstance(Addr : Pointer) : HINST;
var
  MemInfo : TMemoryBasicInformation;
begin
  Result:=0;
  if VirtualQuery(Addr,MemInfo,SizeOf(MemInfo)) = SizeOf(MemInfo) then
  if MemInfo.State = MEM_COMMIT then
    Result:=HINST(MemInfo.AllocationBase);
end;
{$ENDIF}

{$IFDEF WIN32}
function OmitWin9xDebugThunk(P : Pointer) : Pointer;
type
  PWin9xDebugThunk = ^TWin9xDebugThunk;
  TWin9xDebugThunk = packed record
    PUSH : Byte;    {PUSH}
    Addr : Pointer; {The actual address of the DLL routine}
    JMP : Byte;     {JMP}
    Rel : Integer;  {Relative displacement (a kernel32.dll address)}
  end;
begin
  Result:=P;
  try
    if Win32Platform = VER_PLATFORM_WIN32_WINDOWS then
    if HINST(GetProcAddress(GetModuleHandle(kernel32),'GetProcAddress')) < GetModuleHandle(kernel32) then {Win9x uses thunk pointer below the kernel32.dll module when under a debugger}
    if (PWin9xDebugThunk(P)^.PUSH = $68) and (PWin9xDebugThunk(P)^.JMP = $E9) then
      Result:=PWin9xDebugThunk(P)^.Addr;
  except
  end;
end;
{$ENDIF}

{- Under Win9x family this function will not work for system DLLs, which reside
   in the globally shared memory, in the upper 2GB of the address space. Modifying
   memory of these DLLs would affect all processes in the operating system
 - Under Win9x family this function will not work for EXEs/DLLs with import
   table located inside a shared PE section. Modifying import table of these
   EXEs/DLLs would affect all processes in the operating system that are using
   these EXEs/DLLs
 - Under Win9x family there is no "copy on write" functionality for globally
   shared memory or shared PE sections, even by using WriteProcessMemory function}
function SwapImportedFunction(Instance : HINST; OldFunctionAddr : Pointer; NewFunctionAddr : Pointer) : Boolean;
var
  PEHeader : PImageNtHeaders;
  ImageImportDescriptor : PImageImportDescriptor;
  ImportAddr : ^Pointer;
{$IFDEF WIN32}
  ImageSectionHeader : PImageSectionHeader;
  I : Integer;
{$ENDIF}
  ErrorOccured : Boolean;
  ImportFound : Boolean;
  OldProtect : Cardinal;
  OldProtectValid : Boolean;
  NumberOfBytesWritten : THandle{NativeUInt};
begin
  Result:=False;
  try
    if (OldFunctionAddr = nil) or (NewFunctionAddr = nil) then
      Exit;

    {Mainly for Win9x family: we prevent this function from modifying system DLLs,
     which reside in the globally shared memory, in the upper 2GB of the address
     space - WriteProcessMemory call, depending on the PE section characteristics,
     might succeed, but this would affect all processes in the operating system}
    Instance:=FindHInstance(Pointer(Instance)); {LoadLibraryEx may return values greater than AllocationBase}
    if (Instance < HINST(SystemInfo.lpMinimumApplicationAddress)) or
       (Instance > HINST(SystemInfo.lpMaximumApplicationAddress)) then
      Exit;

    if PImageDosHeader(Instance)^.e_magic <> IMAGE_DOS_SIGNATURE then
      Exit;
    PEHeader:=PImageNtHeaders(Instance+HINST(PImageDosHeader(Instance)^._lfanew));
    if PEHeader^.Signature <> IMAGE_NT_SIGNATURE then
      Exit;

    with PEHeader^.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT] do
    if VirtualAddress <> 0 then
      ImageImportDescriptor:=Pointer(Instance+VirtualAddress)
    else
      Exit;

    ErrorOccured:=False;
    ImportFound:=False;
    while True do
    with ImageImportDescriptor^ do
    begin
      Inc(ImageImportDescriptor);

      if (OriginalFirstThunk = 0) and (TimeDateStamp = 0) and (ForwarderChain = 0) and
         (Name = 0) and (FirstThunk = 0) then
        Break;

      if FirstThunk = 0 then
        Continue;
      ImportAddr:=Pointer(Instance+FirstThunk);

{$IFDEF WIN32}
      {Win9x family: we prevent this function from modifying shared PE sections -
       this would affect all processes in the operating system that are using EXE/DLL}
      if Win32Platform = VER_PLATFORM_WIN32_WINDOWS then
      begin
        ImageSectionHeader:=PImageSectionHeader(HINST(@PEHeader^.OptionalHeader)+PEHeader^.FileHeader.SizeOfOptionalHeader);
        for I:=0 to PEHeader^.FileHeader.NumberOfSections-1 do
        with ImageSectionHeader^ do
        begin
          if (HINST(ImportAddr) >= Instance+VirtualAddress) and
             (HINST(ImportAddr) < Instance+VirtualAddress+Misc.VirtualSize) then
          if Characteristics and IMAGE_SCN_MEM_SHARED <> 0 then
            Exit
          else
            Break;
          Inc(ImageSectionHeader);
        end;
      end;
{$ENDIF}

      while ImportAddr^ <> nil do
      begin
{$IFDEF WIN32}
        if OmitWin9xDebugThunk(ImportAddr^) = OmitWin9xDebugThunk(OldFunctionAddr) then
{$ELSE}
        if ImportAddr^ = OldFunctionAddr then
{$ENDIF}
        begin
          ImportFound:=True;

          OldProtectValid:=VirtualProtect(ImportAddr,SizeOf(NewFunctionAddr),PAGE_EXECUTE_READWRITE,@OldProtect);

          {Function WriteProcessMemory executes atomically - it is guaranteed that the
           whole memory area will be written before other threads will be allowed to
           access this area of memory. Moreover, WriteProcessMemory function implements
           "copy on write" under Win9x family, except for globally shared memory (like
           upper 2GB of the address space) and shared PE sections (for example netbios.dll,
           shell32.dll). [MATT PIETREK - "Windows 95 System programming" - MISCELLANEOUS
           FUNCTIONS, "COPY ON WRITE" IN WINDOWS 95 (OR THE LACK THEREOF)].
           WriteProcessMemory does not guarantee that the EIP/RIP of some other thread
           is not inside the memory area that is being modified}
          NumberOfBytesWritten:=0;
          if not WriteProcessMemory(GetCurrentProcess,ImportAddr,@NewFunctionAddr,SizeOf(NewFunctionAddr),NumberOfBytesWritten) then
            ErrorOccured:=True;
          if NumberOfBytesWritten <> SizeOf(NewFunctionAddr) then
            ErrorOccured:=True;

          {Make sure that everything keeps working in a multiprocessor environment.
           "The cache refresh might be a little surprising, but in many executables
           the IAT can be found in the .text section where code is found"}
          FlushInstructionCache(GetCurrentProcess,ImportAddr,SizeOf(NewFunctionAddr));

          if OldProtectValid then
            VirtualProtect(ImportAddr,SizeOf(NewFunctionAddr),OldProtect,@OldProtect);
        end;
        Inc(ImportAddr);
      end;
    end;

    Result:=(not ErrorOccured) and ImportFound;
  except
  end;
end;

{- For 32-bit programs, NewFunctionAddr may have any values different from Instance
 - For 64-bit programs, NewFunctionAddr may have values from the open interval
   (Instance..Instance+$100000000)
 - Under Win9x family this function will not work for system DLLs, which reside
   in the globally shared memory, in the upper 2GB of the address space. Modifying
   memory of these DLLs would affect all processes in the operating system
 - Under Win9x family this function will not work for EXEs/DLLs with export
   table located inside a shared PE section. Modifying export table of these
   EXEs/DLLs would affect all processes in the operating system that are using
   these EXEs/DLLs
 - Under Win9x family there is no "copy on write" functionality for globally
   shared memory or shared PE sections, even by using WriteProcessMemory function}
function SwapExportedFunction(Instance : HINST; OldFunctionAddr : Pointer; NewFunctionAddr : Pointer) : Boolean;
var
  PEHeader : PImageNtHeaders;
  ImageExportDirectory : PImageExportDirectory;
  ExportAddr : ^Cardinal; {not ^Pointer !}
  NewFunctionAddrOfs : Cardinal; {not Pointer !}
{$IFDEF WIN32}
  ImageSectionHeader : PImageSectionHeader;
{$ENDIF}
  I : Integer;
  ErrorOccured : Boolean;
  ExportFound : Boolean;
  OldProtect : Cardinal;
  OldProtectValid : Boolean;
  NumberOfBytesWritten : THandle{NativeUInt};
begin
  Result:=False;
  try
    if (OldFunctionAddr = nil) or (NewFunctionAddr = nil) then
      Exit;

    {Mainly for Win9x family: we prevent this function from modifying system DLLs,
     which reside in the globally shared memory, in the upper 2GB of the address
     space - WriteProcessMemory call, depending on the PE section characteristics,
     might succeed, but this would affect all processes in the operating system}
    Instance:=FindHInstance(Pointer(Instance)); {LoadLibraryEx may return values greater than AllocationBase}
    if (Instance < HINST(SystemInfo.lpMinimumApplicationAddress)) or
       (Instance > HINST(SystemInfo.lpMaximumApplicationAddress)) then
      Exit;

{$IFDEF WIN64}
    if (HINST(NewFunctionAddr) <= Instance) or
       (HINST(NewFunctionAddr)-Instance > High(Cardinal)) then
      Exit;
{$ELSE}
    if HINST(NewFunctionAddr) = Instance then
      Exit;
{$ENDIF}

    if PImageDosHeader(Instance)^.e_magic <> IMAGE_DOS_SIGNATURE then
      Exit;
    PEHeader:=PImageNtHeaders(Instance+HINST(PImageDosHeader(Instance)^._lfanew));
    if PEHeader^.Signature <> IMAGE_NT_SIGNATURE then
      Exit;

    with PEHeader^.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT] do
    if VirtualAddress <> 0 then
      ImageExportDirectory:=Pointer(Instance+VirtualAddress)
    else
      Exit;

    with ImageExportDirectory^ do
    if HINST(AddressOfFunctions) <> 0 then
      ExportAddr:=Pointer(Instance+HINST(AddressOfFunctions))
    else
      Exit;

{$IFDEF WIN32}
    {Win9x family: we prevent this function from modifying shared PE sections -
     this would affect all processes in the operating system that are using EXE/DLL}
    if Win32Platform = VER_PLATFORM_WIN32_WINDOWS then
    begin
      ImageSectionHeader:=PImageSectionHeader(HINST(@PEHeader^.OptionalHeader)+PEHeader^.FileHeader.SizeOfOptionalHeader);
      for I:=0 to PEHeader^.FileHeader.NumberOfSections-1 do
      with ImageSectionHeader^ do
      begin
        if (HINST(ExportAddr) >= Instance+VirtualAddress) and
           (HINST(ExportAddr) < Instance+VirtualAddress+Misc.VirtualSize) then
        if Characteristics and IMAGE_SCN_MEM_SHARED <> 0 then
          Exit
        else
          Break;
        Inc(ImageSectionHeader);
      end;
    end;
{$ENDIF}

    ErrorOccured:=False;
    ExportFound:=False;
    for I:=0 to ImageExportDirectory^.NumberOfFunctions-1 do
    begin
      {We use GetProcAddress below instead of obtaining address directly from
       ExportAddr^, because export may be forwarded, and in this case ExportAddr^,
       instead of function code, points to the text name of the forward}
{$IFDEF WIN32}
      if OmitWin9xDebugThunk(GetProcAddress(Instance,PAnsiChar(Cardinal(I)+ImageExportDirectory^.Base))) = OmitWin9xDebugThunk(OldFunctionAddr) then
{$ELSE}
      if GetProcAddress(Instance,PAnsiChar(Cardinal(I)+ImageExportDirectory^.Base)) = OldFunctionAddr then
{$ENDIF}
      begin
        ExportFound:=True;

        OldProtectValid:=VirtualProtect(ExportAddr,SizeOf(NewFunctionAddrOfs),PAGE_EXECUTE_READWRITE,@OldProtect);

        {Function WriteProcessMemory executes atomically - it is guaranteed that the
         whole memory area will be written before other threads will be allowed to
         access this area of memory. Moreover, WriteProcessMemory function implements
         "copy on write" under Win9x family, except for globally shared memory (like
         upper 2GB of the address space) and shared PE sections (for example netbios.dll,
         shell32.dll). [MATT PIETREK - "Windows 95 System programming" - MISCELLANEOUS
         FUNCTIONS, "COPY ON WRITE" IN WINDOWS 95 (OR THE LACK THEREOF)].
         WriteProcessMemory does not guarantee that the EIP/RIP of some other thread
         is not inside the memory area that is being modified}
        NewFunctionAddrOfs:=HINST(NewFunctionAddr)-Instance;
        NumberOfBytesWritten:=0;
        if not WriteProcessMemory(GetCurrentProcess,ExportAddr,@NewFunctionAddrOfs,SizeOf(NewFunctionAddrOfs),NumberOfBytesWritten) then
          ErrorOccured:=True;
        if NumberOfBytesWritten <> SizeOf(NewFunctionAddrOfs) then
          ErrorOccured:=True;

        {Make sure that everything keeps working in a multiprocessor environment.
         "The cache refresh might be a little surprising, but in many executables
         the EAT can be found in the .text section where code is found"}
        FlushInstructionCache(GetCurrentProcess,ExportAddr,SizeOf(NewFunctionAddrOfs));

        if OldProtectValid then
          VirtualProtect(ExportAddr,SizeOf(NewFunctionAddrOfs),OldProtect,@OldProtect);
      end;
      Inc(ExportAddr);
    end;

    Result:=(not ErrorOccured) and ExportFound;
  except
  end;
end;

initialization
  GetSystemInfo(SystemInfo);
end.

File 2 of 4: VirtualPCPatch.pas (part of the patch)

Code: Select all

unit VirtualPCPatch;

interface

{$IFDEF WIN32}
uses
  Windows;
{$ENDIF}

implementation

{$IFDEF WIN32}

{$IFNDEF VER90} {Delphi 2}
{$IFNDEF VER100} {Delphi 3}
{$IFNDEF VER120} {Delphi 4}
{$IFNDEF VER130} {Delphi 5}
{$WARN UNIT_PLATFORM OFF}
{$WARN SYMBOL_PLATFORM OFF}
{$ENDIF}
{$ENDIF}
{$ENDIF}
{$ENDIF}

uses
  SysUtils, PSAPI, TLHelp32, PE;

{!!!!!!!!!! Place code of IsBuggyVirtualPCDriverLoaded function here !!!!!!!!!!}

var
  ORIGNtQueryDirectoryFile : function(FileHandle : THandle; Event : THandle;
    ApcRoutine : Pointer; ApcContext : Pointer; IoStatusBlock : Pointer;
    FileInformation : Pointer; Length : Cardinal;
    FileInformationClass : Cardinal; ReturnSingleEntry : LongBool;
    FileName : Pointer; RestartScan : LongBool) : Cardinal; stdcall;

function INJECTEDNtQueryDirectoryFile(FileHandle : THandle; Event : THandle;
           ApcRoutine : Pointer; ApcContext : Pointer; IoStatusBlock : Pointer;
           FileInformation : Pointer; Length : Cardinal;
           FileInformationClass : Cardinal; ReturnSingleEntry : LongBool;
           FileName : Pointer; RestartScan : LongBool) : Cardinal; stdcall;
var
  Buffer : Pointer;
begin
  if (Event = 0) and (ApcRoutine = nil) and (ApcContext = nil) then
  begin
    {In practice all calls from all system DLLs are synchronous (they use Event=0,
     ApcRoutine=nil and ApcContext=nil), so we'll most probably get here}
    THandle(Buffer):=LocalAlloc(LMEM_FIXED,Length+4);
    if Buffer <> nil then
    try
      {If operation is synchronous and we successfully allocated our own buffer}
      Result:=ORIGNtQueryDirectoryFile(FileHandle,0,nil,nil,IoStatusBlock,Buffer,
                Length,FileInformationClass,ReturnSingleEntry,FileName,RestartScan);
      try
        Move(Buffer^,FileInformation^,Length); {We copy independently of the Result -
                  for example STATUS_BUFFER_OVERFLOW is also some kind of success}
      except
        Result:=Cardinal(-1073741819); {0xC0000005 = STATUS_ACCESS_VIOLATION}
      end;
      Exit;
    finally
      LocalFree(THandle(Buffer));
    end;
  end;

  {All other cases - we decrease buffer length by 4}
  if Length < 4 then
    Result:=Cardinal(-2147483643) {0x80000005 = STATUS_BUFFER_OVERFLOW}
  else
    Result:=ORIGNtQueryDirectoryFile(FileHandle,Event,ApcRoutine,ApcContext,
              IoStatusBlock,FileInformation,Length-4,FileInformationClass,
              ReturnSingleEntry,FileName,RestartScan);
end;

{CreateToolhelp32Snapshot will fail under WinNT 4.0, but it's not a problem -
 the only file in whole WinNT 4.0 SP6 operating system, that uses ntdll.dll!
 NtQueryDirectoryFile function, is kernel32.dll (which we hook separately).
 We don't have to worry about versions older than SP6, because our patch
 doesn't apply to them - Virtual Machine Additions require SP6 under WinNT 4.0}
procedure ProcesLoadedDLLs(OldFunctionAddr : Pointer; NewFunctionAddr : Pointer);
var
  SnapshotHandle : THandle;
  ModuleEntry32 : TModuleEntry32;
begin
  SnapshotHandle:=CreateToolhelp32Snapshot(TH32CS_SNAPMODULE,0);
  if SnapshotHandle <> 0 then
  try
    ModuleEntry32.dwSize:=SizeOf(ModuleEntry32);
    if Module32First(SnapshotHandle,ModuleEntry32) then
    repeat
      SwapImportedFunction(ModuleEntry32.hModule,OldFunctionAddr,NewFunctionAddr);
    until not Module32Next(SnapshotHandle,ModuleEntry32);
  finally
    CloseHandle(SnapshotHandle);
  end;
end;

var
  BuggyDriverFound : Boolean;

initialization
  @ORIGNtQueryDirectoryFile:=GetProcAddress(GetModuleHandle('ntdll.dll'),'NtQueryDirectoryFile');
  if not Assigned(ORIGNtQueryDirectoryFile) then
    Exit;

  BuggyDriverFound:=IsBuggyVirtualPCDriverLoaded;
  if BuggyDriverFound then
  begin
    SwapExportedFunction(GetModuleHandle('ntdll.dll'),@ORIGNtQueryDirectoryFile,@INJECTEDNtQueryDirectoryFile);
    SwapImportedFunction(GetModuleHandle(kernel32),@ORIGNtQueryDirectoryFile,@INJECTEDNtQueryDirectoryFile);
    ProcesLoadedDLLs(@ORIGNtQueryDirectoryFile,@INJECTEDNtQueryDirectoryFile);
  end;
finalization
  if BuggyDriverFound then
  begin
    SwapExportedFunction(GetModuleHandle('ntdll.dll'),@INJECTEDNtQueryDirectoryFile,@ORIGNtQueryDirectoryFile);
    SwapImportedFunction(GetModuleHandle(kernel32),@INJECTEDNtQueryDirectoryFile,@ORIGNtQueryDirectoryFile);
    ProcesLoadedDLLs(@INJECTEDNtQueryDirectoryFile,@ORIGNtQueryDirectoryFile);
  end;
{$ENDIF}
end.

File 3 of 4: Inj.dpr (test of the patch)

Code: Select all

library Inj;

uses
  Windows, VirtualPCPatch;

function GetMsgProcHook(Code : Integer; wParam : WPARAM; lParam : LPARAM) : LRESULT; stdcall;
begin
  Result:=CallNextHookEx(0,Code,wParam,lParam);
end;

exports
  GetMsgProcHook;

begin
end.

File 4 of 4: Loader.dpr (test of the patch)

Code: Select all

program Loader;

uses
  Windows, Messages;

var
  LibHandle : HINST;
  GetMsgProcHookHandle : HHOOK;
begin
  LibHandle:=LoadLibrary('inj.dll');
  if LibHandle = 0 then
  begin
    MessageBox(0,'Error loading global message hook DLL.','Error',MB_ICONSTOP);
    Exit;
  end;
  GetMsgProcHookHandle:=SetWindowsHookEx(WH_GETMESSAGE,GetProcAddress(LibHandle,'GetMsgProcHook'),LibHandle,0);
  if GetMsgProcHookHandle <> 0 then
  begin
    {'inj.dll' is being injected into all these processes in the system, that
     are allowed to hoA1ok (by our security restrictions). Operating system
     injects (loads) 'inj.dll' into each process - and installs a message hook
     for this process - not immediately, but only when the process receives
     any message for one of its windows. So we post the null message to all
     windows in the system, so we can see results immediately. We are unable
     to inject 'inj.dll' into processes that have no windows}
    PostMessage(HWND_BROADCAST,WM_NULL,0,0);
    MessageBox(0,'Global message hook installed. Press "OK" to uninstall it.','OK',MB_ICONINFORMATION);
    UnHookWindowsHookEx(GetMsgProcHookHandle);
    {Same with uninstalling a message hook and unloading 'inj.dll'}
    PostMessage(HWND_BROADCAST,WM_NULL,0,0);
  end else
    MessageBox(0,'Error installing global message hook.','Error',MB_ICONSTOP);
  FreeLibrary(LibHandle);
end.
Regards
User avatar
MarcinW
Power Member
Power Member
Posts: 852
Joined: 2012-01-23, 15:58 UTC
Location: Poland

Post by *MarcinW »

Here is a more practical example of using SwapImportedFunction from the PE.pas unit.

Let's assume that we don't have a source code of some components (or Delphi itself) or we don't want to modify this code (possibly in many places). We can create a unit: (please note the usage of the "threadvar" instead of "var" declaration - we should be thread-safe!)

Code: Select all

unit HookCreateFile;

interface

threadvar
  ForceSequentialScan : Boolean;

implementation

uses
  Windows, PE;

var
  ORIGCreateFileA : function(lpFileName : PAnsiChar; dwDesiredAccess, dwShareMode : Cardinal;
                             lpSecurityAttributes : Pointer; dwCreationDisposition,
                             dwFlagsAndAttributes : Cardinal; hTemplateFile : THandle) : THandle; stdcall;
  ORIGCreateFileW : function(lpFileName : PWideChar; dwDesiredAccess, dwShareMode : Cardinal;
                             lpSecurityAttributes : Pointer; dwCreationDisposition,
                             dwFlagsAndAttributes : Cardinal; hTemplateFile : THandle) : THandle; stdcall;

function INJECTEDCreateFileA(lpFileName : PAnsiChar; dwDesiredAccess, dwShareMode : Cardinal;
                             lpSecurityAttributes : Pointer; dwCreationDisposition,
                             dwFlagsAndAttributes : Cardinal; hTemplateFile : THandle) : THandle; stdcall;
begin
  if ForceSequentialScan then
    dwFlagsAndAttributes:=dwFlagsAndAttributes or FILE_FLAG_SEQUENTIAL_SCAN;
  Result:=ORIGCreateFileA(lpFileName,dwDesiredAccess,dwShareMode,lpSecurityAttributes,
                          dwCreationDisposition,dwFlagsAndAttributes,hTemplateFile);
end;

function INJECTEDCreateFileW(lpFileName : PWideChar; dwDesiredAccess, dwShareMode : Cardinal;
                             lpSecurityAttributes : Pointer; dwCreationDisposition,
                             dwFlagsAndAttributes : Cardinal; hTemplateFile : THandle) : THandle; stdcall;
begin
  if ForceSequentialScan then
    dwFlagsAndAttributes:=dwFlagsAndAttributes or FILE_FLAG_SEQUENTIAL_SCAN;
  Result:=ORIGCreateFileW(lpFileName,dwDesiredAccess,dwShareMode,lpSecurityAttributes,
                          dwCreationDisposition,dwFlagsAndAttributes,hTemplateFile);
end;

initialization
  @ORIGCreateFileA:=GetProcAddress(GetModuleHandle(kernel32),'CreateFileA');
  SwapImportedFunction(HInstance,@ORIGCreateFileA,@INJECTEDCreateFileA);
  @ORIGCreateFileW:=GetProcAddress(GetModuleHandle(kernel32),'CreateFileW');
  SwapImportedFunction(HInstance,@ORIGCreateFileW,@INJECTEDCreateFileW);
finalization
  SwapImportedFunction(HInstance,@INJECTEDCreateFileA,@ORIGCreateFileA);
  SwapImportedFunction(HInstance,@INJECTEDCreateFileW,@ORIGCreateFileW);
end.
We hooked CreateFileA (for Delphi < 2010) and CreateFileW (for Delphi >= 2010) functions. Now we can force using a FILE_FLAG_SEQUENTIAL_SCAN flag, which may speed up file access in some cases - for example when reading text files in Delphi:

Code: Select all

try
  ForceSequentialScan:=True;
  Reset(F); {This will call CreateFileA or CreateFileW}
finally
  ForceSequentialScan:=False;
end;
Regards
User avatar
ghisler(Author)
Site Admin
Site Admin
Posts: 48021
Joined: 2003-02-04, 09:46 UTC
Location: Switzerland
Contact:

Post by *ghisler(Author) »

Wow, that's quite a bit of work you put into it, thanks a lot!

So is it sufficient to check the mrxvpc.sys for version 13.306.0.0 and 13.803.0.0 and apply the patch only for these versions? Or do I need to use the entire range 13.306.0.0 - 13.803.0.0? Or is there a better way to detect the presence of VirtualPC, and apply the patch only when TC is running within it?
Author of Total Commander
https://www.ghisler.com
User avatar
MarcinW
Power Member
Power Member
Posts: 852
Joined: 2012-01-23, 15:58 UTC
Location: Poland

Post by *MarcinW »

The function IsBuggyVirtualPCDriverLoaded (in my first post) checks loaded drivers (using EnumDeviceDrivers API), and if mrxvpc.sys is loaded, it checks its version (Result:=GetFileVersion(S) < $000D0334). So it's enough to attach the VirtualPCPatch.pas, PE.pas and PSAPI.pas (only for Delphi 2), copy function IsBuggyVirtualPCDriverLoaded to VirtualPCPatch.pas and forget about the problem ;) The 13.820.0.0 version of mrxvpc.sys is the first working properly.

Regards
User avatar
ghisler(Author)
Site Admin
Site Admin
Posts: 48021
Joined: 2003-02-04, 09:46 UTC
Location: Switzerland
Contact:

Post by *ghisler(Author) »

Thanks for the clarification.
Author of Total Commander
https://www.ghisler.com
User avatar
MarcinW
Power Member
Power Member
Posts: 852
Joined: 2012-01-23, 15:58 UTC
Location: Poland

Post by *MarcinW »

There is a minor bug in the code above.

There is:

Code: Select all

  if SnapshotHandle <> 0 then
There should be:

Code: Select all

  if SnapshotHandle <> INVALID_HANDLE_VALUE then
Regards
User avatar
ghisler(Author)
Site Admin
Site Admin
Posts: 48021
Joined: 2003-02-04, 09:46 UTC
Location: Switzerland
Contact:

Post by *ghisler(Author) »

Thanks!
Author of Total Commander
https://www.ghisler.com
User avatar
MarcinW
Power Member
Power Member
Posts: 852
Joined: 2012-01-23, 15:58 UTC
Location: Poland

Post by *MarcinW »

I edited my first post and updated IsBuggyVirtualPCDriverLoaded function. The main difference is that it currently uses GetSystemWindowsDirectoryA function when possible instead of GetWindowsDirectoryA, so it works properly also for server systems. Tested with Delphi 2, Delphi 5, Delphi 2006 and Delphi XE5. Please update your code.

Regards
User avatar
ghisler(Author)
Site Admin
Site Admin
Posts: 48021
Joined: 2003-02-04, 09:46 UTC
Location: Switzerland
Contact:

Post by *ghisler(Author) »

Thanks for your changes. It would be easier for me if you told me what exactly I need to change, because I made some modifications to your code too.
Author of Total Commander
https://www.ghisler.com
User avatar
ghisler(Author)
Site Admin
Site Admin
Posts: 48021
Joined: 2003-02-04, 09:46 UTC
Location: Switzerland
Contact:

Post by *ghisler(Author) »

I just checked my code - currently I have the entire function after
{Windows NT4 doesn't have PSAPI.dll installed by default, but we try}
disabled. This means that I'm currently only checking for the version of the file mrxvpc.sys.

Shouldn't this be sufficient?
Author of Total Commander
https://www.ghisler.com
Post Reply