About hooks

c++ / delphi package - dll injection and api hooking
Post Reply
pambol
Posts: 50
Joined: Sat Jun 23, 2018 1:15 am

About hooks

Post by pambol »

Hello,
I have a question, we did a dll who hook some apis like:

Code: Select all

HookApi('ntdll.dll', 'ZwOpenProcess', @InterceptZwOpenProcess, @TrampolineZwOpenProcess);

function InterceptZwOpenProcess(ProcessHandle: PHANDLE; DesiredAccess: ACCESS_MASK; ObjectAttributes: POBJECT_ATTRIBUTES; ClientId: PCLIENT_ID): NTSTATUS; stdcall;
begin
  if (ClientID <> nil) and AreThisProcessProtected(ClientID.UniqueProcess) then begin
    Result := NTStatus(STATUS_ACCESS_DENIED);
  end
  else
  begin
    Result := TrampolineZwOpenProcess(ProcessHandle, DesiredAccess,  ObjectAttributes, ClientId);
  end;
end;
But the process who we injected this dll call the original function (maybe he remove the hook, i don't know), how we don't know.
So we did a test, we created a thread who call RenewHook and so the hook back work's again.

But we don't need use a Thread since Madshi don't recommend that, so what options we have?

we already tested RenewHook after Result (Trampoline), tested flags, and the only way who works its on a thread who call it every time.
iconic
Site Admin
Posts: 1065
Joined: Wed Jun 08, 2005 5:08 am

Re: About hooks

Post by iconic »

What does AreThisProcessProtected() look like? If it's just checking the process ID that Windows shows you in Task Manager, Process Explorer etc. then your code can easily be bypassed, even by the higher level OpenProcess() API from kernel32. Many aren't aware of this however you can open *any* process with up to 4 different PIDs. Windows only lists the lowest value as the PID. For example, say Explorer.exe has a PID of 2800, if I called OpenProcess() on 2801, 2802, or 2803 I could still open the same process even if I had "protected" 2800, like in your code, and bypass your protection completely due to the logic flaw that most likely exists like other numerous examples of this online. If you're not already doing so you should be validating and sanitizing the PID like this within the callback, like any other parameter you work with inside a hook callback.

Code: Select all

function GetLowestPID(const PID: DWORD): DWORD;
begin
      result := PID - (PID mod 4);
end;

if (ClientID <> nil) and AreThisProcessProtected(GetLowestPID(DWORD(ClientID.UniqueProcess))) then
    Result := NTStatus(STATUS_ACCESS_DENIED);
You could also allow the real function to execute first and upon success then check the ProcessHandle with madRemote's ProcessHandleToProcessId(ProcessHandle^) which will also return the correct lowest PID that Windows displays to you. If it's a PID you want to protect you'd close the handle, zero out ProcessHandle^ and return STATUS_ACCESS_DENIED. Either of these methods would be less likely to be bypassed than just using the lowest PID to protect a process.

Also, in case you're running this code on x64, CLIENT_ID.UniqueProcess is a HANDLE (8 bytes) and not a DWORD (4 bytes) like it would be on x86. Just pointing this out. As far as being able to bypass this type of native API hook, it's actually rather easy. NTDLL can be remapped and executed (loaded from a different location), mapped as executable memory and executed directly, direct system call (INT 0x2E, SYSENTER, SYSCALL etc.), DuplicateHandle() of the protected process that may have already been opened by another process before your hook was installed etc. There's no telling what your target process is doing without uploading the binary for analysis. If a program wants to bypass your hooks badly enough it simply will =) This goes for all usermode hooking engines.


--Iconic
pambol
Posts: 50
Joined: Sat Jun 23, 2018 1:15 am

Re: About hooks

Post by pambol »

iconic wrote:What does AreThisProcessProtected() look like? If it's just checking the process ID that Windows shows you in Task Manager, Process Explorer etc. then your code can easily be bypassed, even by the higher level OpenProcess() API from kernel32. Many aren't aware of this however you can open *any* process with up to 4 different PIDs. Windows only lists the lowest value as the PID. For example, say Explorer.exe has a PID of 2800, if I called OpenProcess() on 2801, 2802, or 2803 I could still open the same process even if I had "protected" 2800, like in your code, and bypass your protection completely due to the logic flaw that most likely exists like other numerous examples of this online. If you're not already doing so you should be validating and sanitizing the PID like this within the callback, like any other parameter you work with inside a hook callback.

Code: Select all

function GetLowestPID(const PID: DWORD): DWORD;
begin
      result := PID - (PID mod 4);
end;

if (ClientID <> nil) and AreThisProcessProtected(GetLowestPID(DWORD(ClientID.UniqueProcess))) then
    Result := NTStatus(STATUS_ACCESS_DENIED);
You could also allow the real function to execute first and upon success then check the ProcessHandle with madRemote's ProcessHandleToProcessId(ProcessHandle^) which will also return the correct lowest PID that Windows displays to you. If it's a PID you want to protect you'd close the handle, zero out ProcessHandle^ and return STATUS_ACCESS_DENIED. Either of these methods would be less likely to be bypassed than just using the lowest PID to protect a process.

Also, in case you're running this code on x64, CLIENT_ID.UniqueProcess is a HANDLE (8 bytes) and not a DWORD (4 bytes) like it would be on x86. Just pointing this out. As far as being able to bypass this type of native API hook, it's actually rather easy. NTDLL can be remapped and executed (loaded from a different location), mapped as executable memory and executed directly, direct system call (INT 0x2E, SYSENTER, SYSCALL etc.), DuplicateHandle() of the protected process that may have already been opened by another process before your hook was installed etc. There's no telling what your target process is doing without uploading the binary for analysis. If a program wants to bypass your hooks badly enough it simply will =) This goes for all usermode hooking engines.


--Iconic
So the only solution who i have at user mode its use a thread who call RenewHook every second, or have another solution?
iconic
Site Admin
Posts: 1065
Joined: Wed Jun 08, 2005 5:08 am

Re: About hooks

Post by iconic »

That's not a solution, it's a hack that will never work 100% and even create greater probability for process instability. If you can set a hook in usermode using usermode code so can the next process, or in this case, remove yours. Only proven method for truly blocking the opening or duplicating of a process handle is to dive into kernel mode and register a callback with ObRegisterCallbacks and handle both OB_OPERATION_HANDLE_CREATE and OB_OPERATION_HANDLE_DUPLICATE operations. You can either block the operation entirely and return 0 or only strip certain bit flags out of the ACCESS_MASK (i.e> PROCESS_VM_WRITE, PROCESS_CREATE_THREAD, PROCESS_DUP_HANDLE etc.). You can even protect your process' threads in the same manner with ObRegisterCallbacks too. Bottom line is you don't truly protect usermode code with additional usermode code. If you do try this the cat and mouse game never ends

--Iconic
madshi
Site Admin
Posts: 10754
Joined: Sun Mar 21, 2004 5:25 pm

Re: About hooks

Post by madshi »

iconic is right, as usual.

If you insist on trying your best in user mode, I'd suggest that you hook NtProtectVirtualMemory and disallow modification of your API hook's memory pages. But it's easy enough to bypass such a hook, so it's really only one level more secure, but easy to bypass. Cat and mouse, as iconic said...
pambol
Posts: 50
Joined: Sat Jun 23, 2018 1:15 am

Re: About hooks

Post by pambol »

madshi wrote:iconic is right, as usual.

If you insist on trying your best in user mode, I'd suggest that you hook NtProtectVirtualMemory and disallow modification of your API hook's memory pages. But it's easy enough to bypass such a hook, so it's really only one level more secure, but easy to bypass. Cat and mouse, as iconic said...
We will test using threads, if have problems so we think on driver mode.
pambol
Posts: 50
Joined: Sat Jun 23, 2018 1:15 am

Re: About hooks

Post by pambol »

iconic wrote:That's not a solution, it's a hack that will never work 100% and even create greater probability for process instability. If you can set a hook in usermode using usermode code so can the next process, or in this case, remove yours. Only proven method for truly blocking the opening or duplicating of a process handle is to dive into kernel mode and register a callback with ObRegisterCallbacks and handle both OB_OPERATION_HANDLE_CREATE and OB_OPERATION_HANDLE_DUPLICATE operations. You can either block the operation entirely and return 0 or only strip certain bit flags out of the ACCESS_MASK (i.e> PROCESS_VM_WRITE, PROCESS_CREATE_THREAD, PROCESS_DUP_HANDLE etc.). You can even protect your process' threads in the same manner with ObRegisterCallbacks too. Bottom line is you don't truly protect usermode code with additional usermode code. If you do try this the cat and mouse game never ends

--Iconic
Iconic, you have e-mail? skype? i need talk with you.
iconic
Site Admin
Posts: 1065
Joined: Wed Jun 08, 2005 5:08 am

Re: About hooks

Post by iconic »

Email: bindshell (at) gmail <dot> com

--Iconic
Post Reply