On this page we'll work out together how to write a project which hooks an
API system wide in both win9x and winNT families. Which API shall we hook?
Hmmmm... Let's take a very system API, "TerminateProcess". We'll install a
hook which protects our process from being terminated.
Before beginning with the work, let me say it loud and clear: I don't want
madCodeHook to be used for illegal purposes! If you want to write a virus or
a backdoor or whatever, please use something else. madCodeHook is used by
several people for serious work and I don't want their work to be impacted
negatively in any way. If you spread viruses which uses madCodeHook, some
anti virus programs will condemn every single product, which uses
madCodeHook. I don't want that to happen, so please use it only for serious
and legal purpose. Thank you!
Okay, let's begin with the API hooking code. When hooking an API, we need to
write some kind of hook callback function, which will later get called
everytime someone tries to call the to-be-hooked API. Then we may need some
way to call the original API from within our hook callback function. The
most simple way to do so is to use a function variable. So let's have a look
at a first easy chunk of code:
 |
var OriginalTerminateProcess : function (processHandle, exitCode: dword) : bool; stdcall;
function TerminateProcessCallback(processHandle, exitCode: dword) : bool; stdcall;
begin
result := OriginalTerminateProcess(processHandle, exitCode);
end;
|
|
Does that look alright to you? The "TerminateProcessCallback" function shall
be our hook callback function. Inside the first version of our callback, we
do nothing but call the original API. In order to enable us to handle all
parameters as well as the result of the API correctly, we must use *exactly*
the same function definition everywhere, which is also used by the original
API - including the calling convention.
Now that we have a callback function and a "OriginalAPI" function variable,
all we need to do is to install the hook. We do so by calling madCodeHook's
HookAPI. But before doing that let's slightly rename our function
variable. madCodeHook automatically builds up a full hooking queue. So in
fact what we thought would be the original API function might just be
another hook callback function. So let's rename "OriginalTerminateProcess"
to "TerminateProcessNext".
 |
var TerminateProcessNext : function (processHandle, exitCode: dword) : bool; stdcall;
function TerminateProcessCallback(processHandle, exitCode: dword) : bool; stdcall;
begin
result := TerminateProcessNext(processHandle, exitCode);
end;
initialization
HookAPI('kernel32.dll', 'TerminateProcess', @TerminateProcessCallback, @TerminateProcessNext);
end.
|
|
Okay, that should already work. Now let's fill our callback function, so
that it actually does something. We want our own process to be protected
from being terminated. So the first thing we have to do in our callback
function is to check out which process is about to be terminated. Hmmmmm,
the parameters give us a process handle. But which process hides behind that
handle? Fortunately, madCodeHook exports two useful functions. With
ProcessHandleToId we can convert the process handle to an ID, and
with ProcessIdToFileName we can get the process' file name. If the
file name is identical to our application's file name, we have to prevent
the TerminateProcess call! Otherwise we let it pass:
 |
function ThisIsOurProcess(processHandle: dword) : boolean;
var pid : dword;
arrCh : array [0..MAX_PATH] of char;
begin
pid := ProcessHandleToId(processHandle);
result := (pid <> 0) and ProcessIdToFileNameA(pid, arrCh, MAX_PATH) and
(PosText('OurApplication.exe', arrCh) > 0);
end;
function TerminateProcessCallback(processHandle, exitCode: dword) : bool; stdcall;
begin
if (integer(processHandle) > 0) and ThisIsOurProcess(processHandle) then begin
result := false;
SetLastError(ERROR_ACCESS_DENIED);
end else
result := TerminateProcessNext(processHandle, exitCode);
end;
|
|
This should actually work quite nicely. However, madCodeHook API hooks
normally show effect only in the current process. That means, if another
process calls TerminateProcess, we're lost. But the solution is quite easy.
First, let's put all of our code into a little DLL like this:
 |
library TPHook;
uses Windows, madRemote, madCodeHook, madStrings;
var TerminateProcessNext : function (processHandle, exitCode: dword) : bool; stdcall;
function ThisIsOurProcess(processHandle: dword) : boolean;
var pid : dword;
arrCh : array [0..MAX_PATH] of char;
begin
pid := ProcessHandleToId(processHandle);
result := (pid <> 0) and ProcessIdToFileNameA(pid, arrCh, MAX_PATH) and
(PosText('OurApplication.exe', arrCh) > 0);
end;
function TerminateProcessCallback(processHandle, exitCode: dword) : bool; stdcall;
begin
if (integer(processHandle) > 0) and ThisIsOurProcess(processHandle) then begin
result := false;
SetLastError(ERROR_ACCESS_DENIED);
end else
result := TerminateProcessNext(processHandle, exitCode);
end;
begin
HookAPI('kernel32.dll', 'TerminateProcess', @TerminateProcessCallback, @TerminateProcessNext);
end.
|
|
And now in our application let's just inject this DLL into all processes
system wide. That's very easy by using madCodeHook again.
After you have built the hook dll, you need to configure and then
sign the drivers
to make them work. More information about the
injection drivers can be found
here.
 |
program OurApplication;
uses Windows, madCodeHook;
begin
LoadInjectionDriver('TPDemoDriver', 'tpdemo32.sys', 'tpdemo64.sys');
InjectLibrary('TPDemoDriver', 'TPHook.dll', ALL_SESSIONS, true);
MessageBox(0, 'You can''t call TerminateProcess on me!', 'Boasting...', MB_ICONINFORMATION);
UninjectLibrary('TPDemoDriver', 'TPHook.dll', ALL_SESSIONS, true);
StopInjectionDriver('TPDemoDriver');
end.
|
|