UnhookCode thread stopper + example

c++ / delphi package - dll injection and api hooking
uall
Posts: 254
Joined: Sun Feb 20, 2005 1:24 pm

Post by uall »

ok i have tested something maybe u can say what do u think about it:
its an example of what we are doing in our hook method, only using Get/SetThreadContext in the Critical Code Block, which must be executed as one block because if we have to change SetThreadContext the EIP must be correct.
its only an example, if u call begintest there should be a messagebox "1" if the code is executed in one block
what do u think? it seems to me that i get enough "time" to execute the hook code. maybe better than suspending threads (we have to test it on serveral PCs first)
nildo has a third solution maybe this is working how we want :P

for testing u can add remove
SetThreadPriority(GetCurrentThread, THREAD_PRIORITY_TIME_CRITICAL);
and see thats not working without it (because messagebox with "0" = not executed as one code)

Code: Select all

var tid, tid2: cardinal;
    i,j: integer;
procedure start;
begin
  while (true) do i := 0;
end;

procedure hook;     // pseudo hook code
var lpContext: _CONTEXT;
   k: integer;
begin
  SetThreadPriority(GetCurrentThread, THREAD_PRIORITY_TIME_CRITICAL);
  i := 1;       // begin of block code which must be called without stop
  lpContext.ContextFlags := CONTEXT_CONTROL;
  for k := 1 to 10 do GetThreadContext(tid,lpContext);          // for all threads
  asm
    xor eax, eax
  @weiter:
    inc eax
    cmp eax, $1000000                     // do the hook (overwrite code with jmp)
                                           // i dont think it takes more instructions than this ;>
    jne @weiter
  end;
  for k := 1 to 10 do SetThreadContext(tid,lpContext);          // *maybe* for all threads
  j := i;      // end of block code which must be called without stop
  SetThreadPriority(GetCurrentThread, THREAD_PRIORITY_NORMAL);
end;

procedure begintest;
begin
  tid := CreateThread(nil,0,@start,nil,0,tid);   // create the first thread
  sleep(100);                                    // let i := 0
  tid2 := CreateThread(nil,0,@hook,nil,0,tid2);   // create the or thread thread
  Sleep(100);                                     // let i :=
  Messagebox(0,pchar(inttostr(j)),nil,0);      // is second thread called in 1 block? (j = 1)?
end;
madshi
Site Admin
Posts: 10764
Joined: Sun Mar 21, 2004 5:25 pm

Post by madshi »

I think this is not a good solution, because on hyperthreading or multi-CPU systems the current process may have threads which run on either CPUs. Setting the hook thread to REALTIME might make sure that threads running on this CPU won't make problems. But threads running on the other CPU will still continue to run. Hmmmm... Don't know whether I explained it well. Do you understand what I mean?
nildo
Posts: 249
Joined: Mon Mar 22, 2004 11:32 am
Contact:

Post by nildo »

madshi wrote:I think this is not a good solution, because on hyperthreading or multi-CPU systems the current process may have threads which run on either CPUs. Setting the hook thread to REALTIME might make sure that threads running on this CPU won't make problems. But threads running on the other CPU will still continue to run. Hmmmm... Don't know whether I explained it well. Do you understand what I mean?
The probabilities to have this situations is mutch lower
uall
Posts: 254
Joined: Sun Feb 20, 2005 1:24 pm

Post by uall »

yes i understand. but maybe setting all other threads of the process to IDLE will help. because if there is ANY other thread on the second CPU (from another process etc.) the threads from our process are also as good as stopped on second CPU
madshi
Site Admin
Posts: 10764
Joined: Sun Mar 21, 2004 5:25 pm

Post by madshi »

nildo wrote:The probabilities to have this situations is mutch lower
Not sure how often that situation would occur. Probably it's rare, but I can't really say.
uall wrote:maybe setting all other threads of the process to IDLE will help. because if there is ANY other thread on the second CPU (from another process etc.) the threads from our process are also as good as stopped on second CPU
Yeah, that might be better.

One thing I'm not sure about is this: Will SetThreadContext internally always keep the CPU running at 100%? Maybe internally SetThreadContext has a critical section or mutex (maybe even in kernel land) so that when SetThreadContext is called other threads might get a few time slices from the thread scheduler, even though the hook thread is running at "time critical"? Not sure, just wondering. What do you think?
uall
Posts: 254
Joined: Sun Feb 20, 2005 1:24 pm

Post by uall »

what i have debugged SetThreadContext doesnt have any CriticalSection or something that let us have < 100%

But i found a thread priority table in the internet and i have tested it.
If is set all other threads to IDLE and my thread to TIME_CRITICAL

the IDLE thread has prio 1
and the CRITICAL thread hast prio 15

if i know set the process priority to REALTIME the IDLE thread has prio 1 and the CRITICAL thread hast prio 31 - the highest:

Code: Select all


var tid, tid2: cardinal;
    i,j: integer;
procedure start;
begin
  while (true) do i := 0;
end;


procedure hook;     // pseudo hook code
var lpContext: _CONTEXT;
   k: integer;
begin
  SetPriorityClass(GetCurrentProcess, REALTIME_PRIORITY_CLASS);
  SetThreadPriority(GetCurrentThread, THREAD_PRIORITY_TIME_CRITICAL);
  SetThreadPriority(tid, THREAD_PRIORITY_IDLE);
  i := 1;       // begin of block code which must be called without stop
  lpContext.ContextFlags := CONTEXT_CONTROL;
  for k := 1 to 10 do GetThreadContext(tid,lpContext);          // for all threads
  asm
    xor eax, eax
  @weiter:
    inc eax
    cmp eax, $FFFFFFFF                     // do the hook (overwrite code with jmp)
                                           // i dont think it takes more instructions than this ;>
    jne @weiter

    xor eax, eax
  @weiter2:
    inc eax
    cmp eax, $FFFFFFFF                     // do the hook (overwrite code with jmp)
                                           // i dont think it takes more instructions than this ;>
    jne @weiter2

    xor eax, eax
  @weiter3:
    inc eax
    cmp eax, $FFFFFFFF                     // do the hook (overwrite code with jmp)
                                           // i dont think it takes more instructions than this ;>
    jne @weiter3
  end;
  for k := 1 to 10 do SetThreadContext(tid,lpContext);          // *maybe* for all threads
  j := i;      // end of block code which must be called without stop
  SetThreadPriority(tid, THREAD_PRIORITY_IDLE);
  SetThreadPriority(GetCurrentThread, THREAD_PRIORITY_NORMAL);
  SetPriorityClass(GetCurrentProcess, NORMAL_PRIORITY_CLASS);
end;

procedure begintest;
begin
  {tid := }CreateThread(nil,0,@start,nil,0,tid);   // create the first thread
  sleep(100);                                    // let i := 0
  tid2 := CreateThread(nil,0,@hook,nil,0,tid2);   // create the or thread thread
  Sleep(100);                                     // let i :=
  SuspendThread(tid);
  SuspendThread(tid2);
  Messagebox(0,pchar(inttostr(j)),nil,0);      // is second thread called in 1 block? (j = 1)?

end;

the processor is NOTHING doing - only our hook procedure!
madshi
Site Admin
Posts: 10764
Joined: Sun Mar 21, 2004 5:25 pm

Post by madshi »

Hi guys,

I'm trying to find a solution which gets along without SuspendThread and SetThreadPriority (at least in the normal case, when no threads collide with our hooking attempt). Maybe I've found a good solution, but I'm getting unexpected exceptions, which I can't explain...

:cry:

Do you have fun to have a look at my attempt? I've created a little demo project with which the unexpected exceptions can be reproduced:

Code: Select all

program WhyDoesThisCrash;

uses Windows;

procedure Test2;
begin
end;

var Test2_ : procedure = Test2;
procedure Test;
begin
  Test2_;
end;

function WorkerThread(dummy: pointer) : integer; stdcall;
begin
  while true do
    Test;
end;

function ModifyThread(dummy: pointer) : integer; stdcall;
var c1 : dword;
    b1 : byte;
begin
  VirtualProtect(@Test, 1, PAGE_EXECUTE_READWRITE, c1);
  while true do begin
    b1 := byte(pointer(@Test)^);
    byte(pointer(@Test)^) := $f4;  // HLT instruction -> privileged
    Sleep(50);
    byte(pointer(@Test)^) := b1;
    Sleep(50);
  end;
end;

var RtlDispatchExceptionNext : function (excRec: PExceptionRecord; excCtxt: PContext) : integer; stdcall = nil;
procedure InstallExcDispatchHook;

  function RtlDispatchExceptionCallback(excRec: PExceptionRecord; excCtxt: PContext) : integer; stdcall;
  begin
    if excRec.ExceptionCode = STATUS_PRIVILEGED_INSTRUCTION then begin
      excCtxt.eip := dword(@Test2);
      result := 1;
    end else
      result := RtlDispatchExceptionNext(excRec, excCtxt);
  end;

var c1, c2 : dword;
begin
  c1 := dword(GetProcAddress(GetModuleHandle('ntdll.dll'), 'KiUserExceptionDispatcher'));
  if (dword(pointer(c1    )^)               = $04244c8b) and  // mov  ecx, [esp+4] ; PContext
     (dword(pointer(c1 + 4)^) and $00ffffff =   $241c8b) and  // mov  ebx, [esp+0] ; PExceptionRecord
     (byte (pointer(c1 + 7)^)               =       $51) and  // push ecx
     (byte (pointer(c1 + 8)^)               =       $53) and  // push ebx
     (byte (pointer(c1 + 9)^)               =       $e8) and  // call RtlDispatchException
     VirtualProtect(pointer(c1 + 10), 4, PAGE_EXECUTE_READWRITE, c2) then begin
    RtlDispatchExceptionNext := pointer(c1 + 14 + dword(pointer(c1 + 10)^));
    dword(pointer(c1 + 10)^) := dword(@RtlDispatchExceptionCallback) - c1 - 14;
    VirtualProtect(pointer(c1 + 10), 4, c2, c2);
  end;
end;

var tid : dword;
begin
  InstallExcDispatchHook;
  CreateThread(nil, 0, @WorkerThread, nil, 0, tid);
  CreateThread(nil, 0, @ModifyThread, nil, 0, tid);
  MessageBox(0, 'please wait...', 'info', 0);
end.
The code works fine for a while, but it randomly crashes - which I don't understand. If you stop the "ModifyThread" after it has written the HLT instruction, everything works fine. But if the "ModifyThread" continues to swap between HLT and the original code, then sometimes there are unexpected exceptions (always the same, so at least nothing wild).

P.S: Please note that inside of the debugger the stress testing will result in exceptions like "too many consecutive exceptions occurred". This is a pure debugger message and can be ignored. It's annoying, though, so probably you need to test outside of the IDE/debugger.

P.P.S: I'm aware that the unexpected exceptions also run through the RtlDispatchException hook. I don't want a workaround which manipulates these exceptions. I'd like to understand why these exceptions occur!
uall
Posts: 254
Joined: Sun Feb 20, 2005 1:24 pm

Post by uall »

you get an access violation on reading data @ 0xFFFFFFFF
i dont know the reason yet but ill look into it
fixed with this:

Code: Select all

function RtlDispatchExceptionCallback(excRec: PExceptionRecord; excCtxt: PContext) : integer; stdcall;
begin
  if excRec.ExceptionCode = STATUS_PRIVILEGED_INSTRUCTION then
  begin
    excCtxt.eip := dword(@Test)+1;
    result := 1;
  end else
  if excRec.ExceptionCode = STATUS_ACCESS_VIOLATION then
  begin
    result := 1;
  end else
    result := RtlDispatchExceptionNext(excRec, excCtxt);
end;
madshi
Site Admin
Posts: 10764
Joined: Sun Mar 21, 2004 5:25 pm

Post by madshi »

uall wrote:you get an access violation on reading data @ 0xFFFFFFFF
Yeah, that's exactly the problem.
uall wrote:i dont know the reason yet but ill look into it
Thanks! I hope you'll find something. I spent some hours on it and I still don't understand that crash. But maybe I'm blinded because its my own code.
uall wrote:fixed with this:
Yeah, I know. But I think that the whole idea with the HLT instruction makes sense only if it can be realized without impacting the normal exception management.
uall
Posts: 254
Joined: Sun Feb 20, 2005 1:24 pm

Post by uall »

i only can specualte why its crashing. i am sure thats crashing on the executing of the "Test" function when the original byte was written back.

what i think is:

the privileged function is executed -> go to our exception handler, when we are in the excpetion handler the other thread writes the original byte back.

if you are inside the exception u can simply change the byte back to $90, but if another thread changes the byte on exception address, windows recognizes it, but the other thread can change byte on exception address.

but now the situation has changed - we could solve the problem when there was a privilegued function, but now an other thread has changed the byte, so windows doesnt know if our corrections (which we did in our excpetion handler) are working with the changed byte. now windows locks the memory and we get an execute exception...

Code: Select all

program WhyDoesThisCrash;

uses Windows, sysutils;

procedure Test;
begin
  asm
    nop
    nop
    nop
    nop
    nop
  end;
end;

function WorkerThread(dummy: pointer) : integer; stdcall;
begin
  while true do
    Test;  //exception here on execute, but $90 is written correctly
end;

function ModifyThread(dummy: pointer) : integer; stdcall;
var c1 : dword;
begin
  VirtualProtect(@Test, 1, PAGE_EXECUTE_READWRITE, c1);
  while true do
  begin
    pbyte(@Test)^ := $6E;  // HLT instruction -> privileged
    Sleep(50);
    pbyte(@Test)^ := $90;
    Sleep(50);
  end;
end;


var RtlDispatchExceptionNext : function (excRec: PExceptionRecord; excCtxt: PContext) : integer; stdcall = nil;
    tid, tid2 : dword;

function RtlDispatchExceptionCallback(excRec: PExceptionRecord; excCtxt: PContext) : integer; stdcall;
begin
  if excRec.ExceptionCode = STATUS_PRIVILEGED_INSTRUCTION then
  begin
    excCtxt.eip := dword(@Test)+1;
    result := 1;
  end else
  if (excRec.ExceptionCode = STATUS_ACCESS_VIOLATION) and (excCtxt.Eip = dword(@test)) then
    result := 1 else
    result := RtlDispatchExceptionNext(excRec, excCtxt);
end;

procedure InstallExcDispatchHook;
var c1, c2 : dword;
begin
  c1 := dword(GetProcAddress(GetModuleHandle('ntdll.dll'), 'KiUserExceptionDispatcher'));
  if (dword(pointer(c1    )^)               = $04244c8b) and  // mov  ecx, [esp+4] ; PContext
     (dword(pointer(c1 + 4)^) and $00ffffff =   $241c8b) and  // mov  ebx, [esp+0] ; PExceptionRecord 
     (byte (pointer(c1 + 7)^)               =       $51) and  // push ecx 
     (byte (pointer(c1 + 8)^)               =       $53) and  // push ebx 
     (byte (pointer(c1 + 9)^)               =       $e8) and  // call RtlDispatchException
     VirtualProtect(pointer(c1 + 10), 4, PAGE_EXECUTE_READWRITE, c2) then begin
    RtlDispatchExceptionNext := pointer(c1 + 14 + dword(pointer(c1 + 10)^));
    dword(pointer(c1 + 10)^) := dword(@RtlDispatchExceptionCallback) - c1 - 14;
    VirtualProtect(pointer(c1 + 10), 4, c2, c2);
  end; 
end;

begin
  InstallExcDispatchHook;
  CreateThread(nil, 0, @WorkerThread, nil, 0, tid);
  CreateThread(nil, 0, @ModifyThread, nil, 0, tid2);
  MessageBox(0, 'please wait...', 'info', 0);
end.
madshi
Site Admin
Posts: 10764
Joined: Sun Mar 21, 2004 5:25 pm

Post by madshi »

uall wrote:i am sure thats crashing on the executing of the "Test" function when the original byte was written back.
After those hours of analyzing this I got to the following conclusion: Before "Test" is called from the worker thread the HLT instruction is active. But our exception handler is *not* called with "privileged instruction". Instead it's called with the AV. And I'm quite sure that before our handler gets called, the other thread has already removed the HLT instruction. It seems to me that the raising of the exception and the modification HLT->originalCode happens at virtually the same time.

At least that's what I'm thinking right now. But I still don't understand why this situation can end up in an AV with "read of 0xffffffff"! I mean no code variation we ever write to the "Test" function could ever result in such an AV!
uall wrote:the privileged function is executed -> go to our exception handler, when we are in the excpetion handler the other thread writes the original byte back.

if you are inside the exception u can simply change the byte back to $90, but if another thread changes the byte on exception address, windows recognizes it, but the other thread can change byte on exception address.

but now the situation has changed - we could solve the problem when there was a privilegued function, but now an other thread has changed the byte, so windows doesnt know if our corrections (which we did in our excpetion handler) are working with the changed byte. now windows locks the memory and we get an execute exception...
I'm not sure. How much has Windows to do with this? The exception raising stuff for privileged exceptions is done by the CPU, as far as I know. Windows just handles the "privileged instruction" exception state raised by the CPU.

After reading your comment and thinking about it all again, I can only guess that the CPU reported "privileged instruction" and Windows checks the code to see which privileged instruction was executed exactly. But in this moment the HLT is already removed. So Windows is confused and doesn't know what problem to report. Does that make any sense to you?
uall
Posts: 254
Joined: Sun Feb 20, 2005 1:24 pm

Post by uall »

our program: try to execute privileged function
windows: oh no u are not in ring 0 sorry, ill must create an exception
our exceptionhandler: oh ok, i have fixed it
other thread: thanks that i can work, ill change the function
our excpetionhandler: finished, now call ZWContinue
windows: (sysenter from zwContinue), omg function has changed, i think the excpetionhandler fix wasn correct... on next execute create access violation

thats what i think

"After reading your comment and thinking about it all again, I can only guess that the CPU reported "privileged instruction" and Windows checks the code to see which privileged instruction was executed exactly. But in this moment the HLT is already removed. So Windows is confused and doesn't know what problem to report. Does that make any sense to you?"

could be also, but between processor said: privileged function and windows is looking for which function no other thread is executed, so i think its not possible that the other thread can change the memory

our program: try to execute privileged function
CPU: its privileged
windows: ok thanks ill create exception with exception record & context
other thread: change value now
madshi
Site Admin
Posts: 10764
Joined: Sun Mar 21, 2004 5:25 pm

Post by madshi »

uall wrote:our program: try to execute privileged function
windows: oh no u are not in ring 0 sorry, ill must create an exception
our exceptionhandler: oh ok, i have fixed it
other thread: thanks that i can work, ill change the function
our excpetionhandler: finished, now call ZWContinue
windows: (sysenter from zwContinue), omg function has changed, i think the excpetionhandler fix wasn correct... on next execute create access violation
So basically you say:
(1) WorkerThread calls Test.
(2) Our exception handler hook is called with "Privileged instruction".
(3) Our exception handler hook is called with "Access Violation".

But that is not what my logs say. They seem to say (as far as I understand it):
(1) WorkerThread calls Test.
(2) Our exception handler hook is called with "Access Violation".
uall wrote:could be also, but between processor said: privileged function and windows is looking for which function no other thread is executed
Are you sure about that? I mean the kernel is multi threaded, too. Does exception handling in the kernel block all other threads from running?
uall
Posts: 254
Joined: Sun Feb 20, 2005 1:24 pm

Post by uall »

Are you sure about that? I mean the kernel is multi threaded, too. Does exception handling in the kernel block all other threads from running?

HLT is one function its finished in KIUserExceptionDisptacher
thats the next step where an other thread can do something

Test ist executed with AccessViolation -> the value there is NOT the privileged function, so i think there can be only one threadswitch between KiUserExceptionDistpatcher and our call (the 6-8 bytes u dont hook)
i dont think there can a threadswitch between HLT and KiUserException disptacher
madshi
Site Admin
Posts: 10764
Joined: Sun Mar 21, 2004 5:25 pm

Post by madshi »

uall wrote:i dont think there can a threadswitch between HLT and KiUserException disptacher
Are you 100% sure about that?

Currently I'm thinking that our problem occurs in exactly that situation when a thread switch occurs between HLT and KiUserException - if during that thread switch the HLT instruction is removed.
Post Reply