1

I'm trying call the original function in a api hook that prevents dll injections by LdrLoadDll() function, but everytime when i try load a dll diferent of that are filtered, the application crashes and not is possible call original function. Seems that i making something wrong when will save the "original_function" before the hook.

I'm testing on Windows 7 x64, injecting a 32bit dll in a 32bit application (code below).

How fix this?

program Project1;

{$APPTYPE CONSOLE}
{$R *.res}

uses
  Windows,
  SysUtils;

type
  NTSTATUS = Cardinal;
  PUNICODE_STRING = ^UNICODE_STRING;

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

const
  STATUS_ACCESS_DENIED = NTSTATUS($C0000022);

var
  Old_LdrLoadDll: function(szcwPath: PWideChar; dwFlags: DWORD;
    pUniModuleName: PUNICODE_STRING; pResultInstance: PPointer)
    : NTSTATUS; stdcall;

function LdrLoadDll(szcwPath: PWideChar; dwFlags: DWORD;
  pUniModuleName: PUNICODE_STRING; pResultInstance: PPointer)
  : NTSTATUS; stdcall;
begin
  Result := Old_LdrLoadDll(szcwPath, dwFlags, pUniModuleName, pResultInstance);
end;

procedure PatchCode(Address: Pointer; const NewCode; Size: Integer);
var
  OldProtect: DWORD;
begin
  if VirtualProtect(Address, Size, PAGE_EXECUTE_READWRITE, OldProtect) then
  begin
    Move(NewCode, Address^, Size);
    FlushInstructionCache(GetCurrentProcess, Address, Size);
    VirtualProtect(Address, Size, OldProtect, @OldProtect);
  end;
end;

type
  PInstruction = ^TInstruction;

  TInstruction = packed record
    Opcode: Byte;
    Offset: Integer;
  end;

procedure RedirectProcedure(OldAddress, NewAddress: Pointer);
var
  NewCode: TInstruction;
begin
  NewCode.Opcode := $E9;
  NewCode.Offset := NativeInt(NewAddress) - NativeInt(OldAddress) -
    SizeOf(NewCode);
  PatchCode(OldAddress, NewCode, SizeOf(NewCode));
end;

function NewLdrLoadDll(szcwPath: PWideChar; dwFlags: DWORD;
  pUniModuleName: PUNICODE_STRING; pResultInstance: PPointer)
  : NTSTATUS; stdcall;
begin
  if (pos('111', pUniModuleName.Buffer) > 0) or
    (pos('222', pUniModuleName.Buffer) > 0) then

    Result := STATUS_ACCESS_DENIED
  else
    Result := LdrLoadDll(szcwPath, dwFlags, pUniModuleName, pResultInstance);
end;

begin
  @Old_LdrLoadDll := GetProcAddress(GetModuleHandle('ntdll.dll'), 'LdrLoadDll');
  try
    RedirectProcedure(GetProcAddress(GetModuleHandle('ntdll.dll'),
      'LdrLoadDll'), @NewLdrLoadDll);
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
  Readln;

end.
  • 4
    You're hooking it by placing a jump at the start of the original procedure. That also means that when you call that original procedure, you will just encounter that jump again, jump to your new procedure again, and basically get a stack overflow at some point for recursing too deep. – GolezTrol Jan 24 '19 at 14:57
  • I did something like this as well (but for Delphi functions, not library functions, although there may not be a difference). My code is [in this forum post](https://www.nldelphi.com/showthread.php?32977-IsAltGrPressed-of-Hooken-van-functies&p=267993&viewfull=1#post267993) with a bit of explanation in Dutch (comments in code are in English, though). It's pretty straightforward anyway. One unit containing generic code for hooking and unhooking a function, and the other unit containing some example use. – GolezTrol Jan 24 '19 at 15:00
  • The example even shows how to restore the original and call it from within your hook code, so I think that's exactly what you want. Another notable difference is that I used CALL ($E8) rather than JMP. I don't have more time now, but let me know if it did the trick, and I may expand these comments to a full answer with inline code later. – GolezTrol Jan 24 '19 at 15:03
  • @GolezTrol, i saw your code, seems that you remove the hook to call original function. How call original function without remove the hook (similar like i tried save it to a variable)? –  Jan 24 '19 at 15:09
  • 1
    The problem is, you basically destroy the code of the original function by putting your jump there. You actually overwrite the first part of that function. So, you have stored the pointer to the position of the original function, but that doesn't give you the original code. I'm pretty sure you have to remove the hook (that is, restore the original code) to be able to properly call the original function. But the given class makes that trivial to do. It might be an issue though if you need to do this from multiple threads at once... – GolezTrol Jan 24 '19 at 15:12
  • @Davison: I'm sure one could write code that analyzed the code being replaced and then put that somewhere together with a jump to *after* your JMP, but since you don't have that, you *must* replace the original code and then jump to the original address (again) to make things work. Say the original code is at address N. Your jump uses, say 5, 5 bytes. You can't just jump to N+5, because the code originally at address N, e.g. PUSH BP, etc. is then not performed and N+5 might be in the middle of an instruction. So generally, you **must** replace the old code. – Rudy Velthuis Jan 24 '19 at 16:29
  • FWIW, there are some very good detour libraries for Delphi. Search for them and use one of those. These do the right thing and help with trickier problems too. – Rudy Velthuis Jan 24 '19 at 18:04
  • @RudyVelthuis Like [MahdiSafsafi/DDetours](https://github.com/MahdiSafsafi/DDetours)? That seems to be quite a bit more comprehensive than my version. At least it's about 100 times as much code. :) – GolezTrol Jan 24 '19 at 19:47
  • @GolezTrol, you could make a answer including all main parts of your previous comments and a code of example (possible solution) based on that was posted above (on question), please? –  Jan 24 '19 at 20:06
  • @GolezTrol: indeed. I never needed it, but have heard (well... read) nothing but good things about it. – Rudy Velthuis Jan 24 '19 at 20:25

2 Answers2

2

I suggest that you don't use home-made hooking, and get an existing library for this.

Microsoft Detours is now free and open source. And as it builds to DLLs, you can use it with Delphi.

You will need C++ compiler to build it from source, but then again Visual Studio Community is free.

Alex Guteniev
  • 12,039
  • 2
  • 34
  • 79
  • FWIW, C++Builder Community is free too. But an MS project is probably better compiled with an MS compiler. – Rudy Velthuis Jan 24 '19 at 20:18
  • I not like of Microsoft Detours. @GolezTrol is right: *`"That seems to be quite a bit more comprehensive than my version. At least it's about 100 times as much code. :)"`* –  Jan 25 '19 at 02:38
  • That's why I'm suggesting it. Because it is comprehensive, it does everything _right_. Before overwriting target code, t copies old code new location. And it dissembles old function, to copy whole amount of instruction and avoid breaking an instruction in a middle. It also gives up if it fails to do such copy – Alex Guteniev Jan 25 '19 at 04:05
  • Basically, you cannot reliably solve such task with the amount of code provided by OP. Solutions without disassembler will work until next implementation of target function arrive – Alex Guteniev Jan 25 '19 at 04:06
  • My remark was not about Microsoft version, but about a Delphi version I linked to. But indeed, I intended the "100 times as much" remark to emphasize that it takes all kinds of situations in account, which in general would make it a better/safer solution than my own. Although my solution was used for years, without issues, in a (single threaded) production application, to hook some VCL function, so sometimes these couple of lines are all it takes. – GolezTrol Jan 25 '19 at 09:27
  • VCL function is ok. But, if you are intercepting system functions, such as LdrLoadDll, be prepared that it is called not only by you, but by system, and by third-party software injected into your process (such as anti-malware). And there are no single-threaded nowadays in Windows. OS and third-party software will create threads in your application without your consent. So simplistic approach that does not use table-driven disassembler (like detours), will cause crashes. – Alex Guteniev Jan 26 '19 at 05:06
2

Here is one working code made after follow @GolezTrol suggestions:

program Project1;

{$APPTYPE CONSOLE}
{$R *.res}

uses
  Windows,
  SysUtils;

type
  NTSTATUS = Cardinal;
  PUNICODE_STRING = ^UNICODE_STRING;

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

const
  STATUS_ACCESS_DENIED = NTSTATUS($C0000022);

var
  Old_LdrLoadDll: function(szcwPath: PWideChar; dwFlags: DWORD;
    pUniModuleName: PUNICODE_STRING; pResultInstance: PPointer)
    : NTSTATUS; stdcall;

function LdrLoadDll(szcwPath: PWideChar; dwFlags: DWORD;
  pUniModuleName: PUNICODE_STRING; pResultInstance: PPointer)
  : NTSTATUS; stdcall;
begin
  Result := Old_LdrLoadDll(szcwPath, dwFlags, pUniModuleName, pResultInstance);
end;

type
  PInstruction = ^TInstruction;

  TInstruction = packed record
    Opcode: Byte;
    Offset: Integer;
  end;

//======= Structure to store original function ======== 

type
  TSaveOriginal = packed record
    Addr: Pointer;
    Bytes: array [0 .. SizeOf(TInstruction)] of Byte;
  end;

  PSaveOriginal = ^TSaveOriginal;

var
  SaveOriginal: TSaveOriginal;

//====================================================

procedure PatchCode(Address: Pointer; const NewCode; Size: Integer;
  SaveOriginal: PSaveOriginal);
var
  OldProtect: DWORD;
begin
  if VirtualProtect(Address, Size, PAGE_EXECUTE_READWRITE, OldProtect) then
  begin
    //======== Saving original function =========
    if Assigned(SaveOriginal) then
    begin
      SaveOriginal^.Addr := Address;
      Move(Address^, SaveOriginal^.Bytes, Size);
    end;
    //===========================================
    Move(NewCode, Address^, Size);
    FlushInstructionCache(GetCurrentProcess, Address, Size);
    VirtualProtect(Address, Size, OldProtect, @OldProtect);
  end;
end;

procedure RedirectProcedure(OldAddress, NewAddress: Pointer);
var
  NewCode: TInstruction;
begin
  NewCode.Opcode := $E9;
  NewCode.Offset := NativeInt(NewAddress) - NativeInt(OldAddress) -
    SizeOf(NewCode);
  PatchCode(OldAddress, NewCode, SizeOf(NewCode), @SaveOriginal);
end;

procedure UndoRedirectProcedure(const SaveOriginal: TSaveOriginal);
var
  OldProtect: Cardinal;
begin
  if not VirtualProtect(SaveOriginal.Addr, SizeOf(TInstruction),
    PAGE_EXECUTE_READWRITE, OldProtect) then
    RaiseLastOSError;
  Move(SaveOriginal.Bytes, SaveOriginal.Addr^, SizeOf(TInstruction));
  if not VirtualProtect(SaveOriginal.Addr, SizeOf(TInstruction), OldProtect,
    OldProtect) then
    RaiseLastOSError;
end;

function NewLdrLoadDll(szcwPath: PWideChar; dwFlags: DWORD;
  pUniModuleName: PUNICODE_STRING; pResultInstance: PPointer)
  : NTSTATUS; stdcall;
begin
  if (pos('111', pUniModuleName.Buffer) > 0) or
     (pos('222', pUniModuleName.Buffer) > 0) then

    Result := STATUS_ACCESS_DENIED
  else
  begin

    UndoRedirectProcedure(SaveOriginal); // Restore original function

    @Old_LdrLoadDll := SaveOriginal.Addr;

    Result := LdrLoadDll(szcwPath, dwFlags, pUniModuleName, pResultInstance); // Call original function

    RedirectProcedure(GetProcAddress(GetModuleHandle('ntdll.dll'),
      'LdrLoadDll'), @NewLdrLoadDll); // Hook again
  end;
end;

begin
  try
    RedirectProcedure(GetProcAddress(GetModuleHandle('ntdll.dll'),
      'LdrLoadDll'), @NewLdrLoadDll); 
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
  Readln;

end.
  • It is not reliable in case of multi-threaded calls to target function. – Alex Guteniev Jan 25 '19 at 04:09
  • For particular LdrLoadDll case, you may acquire loader lock before writing and calling original function. But for general case, the solution is not good, as generally functions should be callable concurrently without extra locks. – Alex Guteniev Jan 25 '19 at 04:20