using RestoreCode with NtHookEngine

c++ / delphi package - dll injection and api hooking
Post Reply
Bevan Collins
Posts: 42
Joined: Fri Jul 07, 2006 2:50 am

using RestoreCode with NtHookEngine

Post by Bevan Collins »

Hi,

is there any way to unhook NtHookEngine hooks with RestoreCode? RestoreCode always returns FALSE

Code: Select all

#include <windows.h>
#include <nthookengine.h>
#include <madchook.h>
#include <stdio.h>

int WINAPI MyMessageBoxA(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType) {
  printf("hooked MessageBoxA(%s)\n", lpText);
  return 0;
}

void main() {
  HookFunction((ULONG_PTR)MessageBoxA, (ULONG_PTR)&MyMessageBoxA);

  MessageBoxA(0, "test1", "", MB_OK);

  InitializeMadCHook();
  BOOL ok = RestoreCode(MessageBoxA);

  MessageBoxA(0, "test2", "", MB_OK);
}
Here is the source files and x86 binary if that helps https://www.dropbox.com/s/4c029ppmk29fm ... k.zip?dl=0

Thanks
madshi
Site Admin
Posts: 10434
Joined: Sun Mar 21, 2004 5:25 pm

Re: using RestoreCode with NtHookEngine

Post by madshi »

I'm not sure why it fails. RestoreCode is pretty simple. Here's how the code looks like (in Delphi):

Code: Select all

function RestoreCode(code: pointer) : bool; stdcall;
var module  : HMODULE;
    orgCode : int64;
    s1      : AnsiString;
    op      : dword;
begin
  result := false;
  if FindModule(code, module, s1) and WasCodeChanged(module, code, orgCode) and (orgCode <> 0) then begin
    if VirtualProtect(code, 8, PAGE_EXECUTE_READWRITE, @op) then begin
      result := true;
      if not AtomicMove(@orgCode, code, sizeOf(orgCode)) then
        int64(code^) := orgCode;
      FlushInstructionCache(code, sizeOf(orgCode));
      VirtualProtect(code, 8, op, @op);
    end;
  end;
end;
Basically it loads the original DLL (which contains the API you want to restore) from harddisk, gets the first 8 bytes of the API code from there, then tries to unprotect the API and restore the code. It's very simple and straighforward. So if RestoreCode fails, that either means that:

1) Loading the original DLL from harddisk and getting the first 8 bytes of the API code failed for some unknown reason (unlikely).
2) The code wasn't really changed. Maybe NtHookEngine uses IAT patching instead of API code overwriting? RestoreCode only uninstalls API code hooks, not IAT patching.
3) Maybe unprotecting the API (VirtualProtect) failed for some reason?
4) Maybe NtHookEngine has hooked a different address than you or madCodeHook thought? Using "(LONG_PTR)MessageBoxA" may not always directly point to the user32.dll API. It may point to a JMP which points to user32.dll. It's better to use GetProcAddress().

I don't really have the time to analyze why RenewCode() doesn't work for you, but it seems somewhat unlikely to be a bug in madCodeHook (although it's not impossible, of course).
Bevan Collins
Posts: 42
Joined: Fri Jul 07, 2006 2:50 am

Re: using RestoreCode with NtHookEngine

Post by Bevan Collins »

I've tried using GetProcAddress with the same result.
NtHookEngine is very simple, the code can be found here: https://ntcore.com/files/nthookengine.htm, it doesn't use IAT patching.
I have dissassembled madCodeHook to try to figure out what the problem is. WasCodeChanged is returning 1 but orgCode is 0.
iconic
Site Admin
Posts: 1019
Joined: Wed Jun 08, 2005 5:08 am

Re: using RestoreCode with NtHookEngine

Post by iconic »

Bevan,

What does GetLastError() return for you immediately after RestoreCode() is called? Seems something is amiss. Call SetLastError(0) before the call to RestoreCode() just to be extra sure that the hook code didn't set any error internally via the OS.

--Iconic
Bevan Collins
Posts: 42
Joined: Fri Jul 07, 2006 2:50 am

Re: using RestoreCode with NtHookEngine

Post by Bevan Collins »

GetLastError returns 0
madshi
Site Admin
Posts: 10434
Joined: Sun Mar 21, 2004 5:25 pm

Re: using RestoreCode with NtHookEngine

Post by madshi »

I've checked the code of WasCodeChanged(). It internally loads the first 16 bytes of code from harddisk, then applies relocation (if necessary). And then it checks if only the first (up to) 8 bytes of code have changed. If the hooking code that NtHookEngine writes is longer than 8 bytes, then WasCodeChanged() reports true, but doesn't provide the data to undo the code change. Which seems to be the case here (?). Could that explain the situation?
Bevan Collins
Posts: 42
Joined: Fri Jul 07, 2006 2:50 am

Re: using RestoreCode with NtHookEngine

Post by Bevan Collins »

NtHookEngine overwrites writes 10 bytes on x86, 14 bytes on x64. So that would explain the behavior, but why can't the code be restored?
madshi
Site Admin
Posts: 10434
Joined: Sun Mar 21, 2004 5:25 pm

Re: using RestoreCode with NtHookEngine

Post by madshi »

RestoreCode was created to undo simple "JMP trampoline" hooks which are either 5 or 6 bytes long. Restoring more than that is sort of dangerous. Let's assume there's 10 bytes of changed code. How do we know if that's 1 API which is 10 bytes long or 2 APIs which are 5 bytes long each? Furthermore: There's gotta be a limit of how many bytes to restore. Should RestoreCode support up to 16 bytes? Up to 16 KB? Up to 16 MB? Up to 16 TB?

In any case, we have the explanation here: RestoreCode has a hard limit to support only up to 8 bytes. That's not a bug, it's the way RestoreCode was designed. But I guess I should probably add this (intentional) limitation to the documentation.
madshi
Site Admin
Posts: 10434
Joined: Sun Mar 21, 2004 5:25 pm

Re: using RestoreCode with NtHookEngine

Post by madshi »

Oh wait, the documentation already says that:

// restores the original code of the API/function (only first 6 bytes)
Bevan Collins
Posts: 42
Joined: Fri Jul 07, 2006 2:50 am

Re: using RestoreCode with NtHookEngine

Post by Bevan Collins »

thanks for the answer madshi
iconic
Site Admin
Posts: 1019
Joined: Wed Jun 08, 2005 5:08 am

Re: using RestoreCode with NtHookEngine

Post by iconic »

Yes, that would make complete sense. If the hook was just a relative jmp (0xE9 and 5 bytes) it would be restored along with some other 6 byte methods such as absolute jump (0x25FF) and push address ret (0x68 <address> 0xC3) but since the function prologue is modified > 6 bytes RestoreCode() just refuses to rewrite the original bytes. Tbh I had actually completely forgotten about the 6 byte limitation.

--Iconic
Bevan Collins
Posts: 42
Joined: Fri Jul 07, 2006 2:50 am

Re: using RestoreCode with NtHookEngine

Post by Bevan Collins »

NtHookEngine uses an absolute jump:

Code: Select all

VOID WriteJump(VOID *pAddress, ULONG_PTR JumpTo) {
	DWORD dwOldProtect = 0;

	VirtualProtect(pAddress, JUMP_WORST, PAGE_EXECUTE_READWRITE, &dwOldProtect);

	BYTE *pCur = (BYTE *) pAddress;

#ifdef _M_IX86
	*(pCur++) = 0xff;
	*(pCur++) = 0x25;
	*((DWORD *) pCur) = (DWORD)(((ULONG_PTR) pCur) + sizeof (DWORD));
	pCur += sizeof (DWORD);
	*((ULONG_PTR *)pCur) = JumpTo;
#else ifdef _M_AMD64
	*(pCur++) = 0xff;
	*(pCur++) = 0x25;
	*((DWORD *) ++pCur) = 0;
	pCur += sizeof (DWORD);
	*((ULONG_PTR *)pCur) = JumpTo;
#endif

	DWORD dwBuf = 0;
	VirtualProtect(pAddress, JUMP_WORST, dwOldProtect, &dwBuf);
}
iconic
Site Admin
Posts: 1019
Joined: Wed Jun 08, 2005 5:08 am

Re: using RestoreCode with NtHookEngine

Post by iconic »

Bevan,

They both (x86 and x64) exceed the byte limits (10 and 14 bytes, respectively) , since the original author already states this and you had mentioned this in a previous post.
This means we'd have a worst case scenario of 10 bytes on x86 and of 14 bytes on x64. In this hook engine I'm using only worst case scenarios (no 5 byte relative addresses), simply because if the space between the original function and the hooked one is > 2GB or the space between the original function and the bridge is > 2GB, then I would have to recreate the bridge from scratch every time I hook/unhook the function
Now, if the x86 version was just a simple prologue change of say push <address> ret, then that's only 6 bytes and should always be restored, I don't use RestoreCode() personally so I've not tested it, but it's what Madshi just stated. If he just blindly restored only the first 6 bytes and the code was modified beyond this then the function would crash.


*Edit*
Here's a real world test I quickly wrote for x86 and I got the expected results, it should help you visualize better perhaps. See the comments in the code. An atomic 8 byte move is made when restoring IIRC via RestoreCode() - I don't have the exact source in front of me but I know MCH is atomic when modifying memory. It's not about the type of hook but the modified byte count, which is all RestoreCode() cares about *count*.

Code: Select all

function Callback(h: HWND; p1, p2: PAnsiChar; u1: UINT32): Integer; stdcall;
begin
     result := MessageBoxW(0, 'Hooked', 'Hooked', 0);
end;

procedure TForm2.FormCreate(Sender: TObject);
var
     op: DWORD;
     pFunc: PByte;
begin
{$IFDEF WIN32}
     pFunc := GetProcAddress(GetModuleHandle('user32'), 'MessageBoxA');
     if VirtualProtect(pFunc, 16, PAGE_READWRITE, op) then
     begin
     pFunc^ := $68; // PUSH
     PDWORD(pFunc + 1)^ := DWORD(@Callback); // return address
     (pFunc + 5)^ := $C3; // RET
     // Restoration Succeeds
     (pFunc + 6)^ := $90; // NOP
     // Restoration Succeeds
     (pFunc + 7)^ := $90; // NOP
     // Restoration Succeeds
     // uncomment below to make RestoreCode() fail because modification byte count exceeds 8 consecutive bytes
     // (pFunc + 8)^ := $90; // NOP
     VirtualProtect(pFunc, 16, op, op);
     FlushInstructionCache(GetCurrentProcess(), nil, 0);
     MessageBoxA(0, 'Not Hooked', 'Not Hooked', 0);
     RestoreCode(pFunc);
     MessageBoxA(0, 'Not Hooked', 'Not Hooked', 0);
      end;
{$ENDIF}
end;

--Iconic
Post Reply