Tool Functions

Usually a hook DLL frees its resources in DllMain(DLL_PROCESS_DETACH). However, this can be dangerous because of the loader lock. For example, you can't wait for secondary threads to finish because they're usually blocked from running while you're in DllMain.

madCodeHook itself unhooks all of your API hooks automatically, in the moment when you uninject your hook DLL - and it's doing that outside of DllMain. In order to allow you to also cleanup your stuff outside of DllMain, you can call "RegisterUninjectCallback()". Your callback function will then be called after madCodeHook has uninstalled your API hooks, and right before FreeLibrary is called on your hook DLL.

// When your DLL gets uninjected, your "uninject callback" will be called
// right before FreeLibrary(yourHook.dll) is executed, so your callback runs
// outside of DllMain.
// If your DLL gets unloaded by other means than uninjection, your callback
// will *not* be called at all.

type TUninjectCallback = procedure (context: pointer); stdcall;

procedure RegisterUninjectCallback (callback: TUninjectCallback; context: pointer); stdcall;

A DLL which gets injected into multiple processes often needs to find out in what kind of process it is running and how the environment is like. "AmSystemProcess" tells you, whether you're running in a system/service process or in a normal application. The function "AmUsingInputDesktop" is especially important, because only if the current process is using the input desktop, you should show messages boxes or other GUI stuff. If you show a message box in a non interactive system/service process, you can even crash the whole OS. Please note, that with XP fast user switching "AmUsingInputDesktop" may return true, although the current session is currently not visible. XP fast user switching is implemented by using terminal server logic, so each fast user session has its own window station and input desktop.

// is the current process a service/system process?  (win9x -> always false)
function AmSystemProcess : bool; stdcall;

// is the current thread's desktop the input desktop?  (win9x -> always true)
function AmUsingInputDesktop : bool; stdcall;

// Example:
if AmUsingInputDesktop then
  ShowMessage(0, 'the current process may use GUI stuff', 'info', 0);

When running in a 64bit OS, you may need to do some things differently. So here are two 64bit related helper functions:

// is the current OS a native 64bit OS?
function Is64bitOS : bool; stdcall;

// is the specified process a native 64bit process?
function Is64bitProcess (processHandle: dword) : bool; stdcall;

In terminal server and in XP fast user switching situations there can be multiple sessions, where each session has its own ID. The function "GetCurrentSessionId" returns the ID of the session to which the current process belongs. The function "GetInputSessionId" returns the ID of the session, which is currently shown on the physical screen.

// to which session does the current process belong?
function GetCurrentSessionId : dword; stdcall;

// which session is currently shown on the physical screen?
function GetInputSessionId : dword; stdcall;

// Example:
if GetCurrentSessionId = GetInputSessionId then
  MessageBox(0, 'our session is the currently active one', 'info', 0);

Sometimes one needs to know which module has called the current function. E.g. a hook callback function might want to behave differently, depending on which module has called the hooked API. "GetCallingModule" returns this information, but it works only if your function has a stack frame. If you are using Delphi XE2 (or newer) it is recommended to call "GetCallingModule(ReturnAddress)" to improve performance and reliability. MSVC++ users please call "GetCallingModule(_ReturnAddress())".

function GetCallingModule (returnAddress: pointer = nil) : HMODULE; stdcall;

// Example:
procedure TerminateProcessCallback(process, exitCode: dword) : bool; stdcall;
  if GetCallingModule <> GetModuleHandle('kernel32.dll') then begin
    // for some strange reason this hook callback function allows
    // TerminateProcess to be called only from inside the kernel32.dll  
    result := false;
  end else
    result := TerminateProcessNext(process, exitCode);

The following function gives you the path/name of the specified process.

// You get the full path in all OSs, provided you have enough privileges
function ProcessIdToFileNameW (processId: dword; fileName: PWideChar; bufLenInChars: word) : bool; stdcall;
function ProcessIdToFileNameA (processId: dword; fileName: PAnsiChar; bufLenInChars: word) : bool; stdcall;

When doing system wide stuff, there are two problems with named objects (like events, mutexes and file mappings). The first problem is that by default the objects are created session specific. That means, even if you enter exact the same name, each session will have its own set of objects. Internally Windows adds the session number to the object names. The second problem is that if you create an object inside of a system/service process without specifying appropriate security attributes, this object will not be accessible by normal applications. The following functions get rid of both problems. Objects created/opened by the following functions are terminal server and XP fast user switching session independent and can be opened from any process.

function CreateGlobalMutex (name: pchar) : dword; stdcall;
function   OpenGlobalMutex (name: pchar) : dword; stdcall;

function CreateGlobalEvent (name: pchar; manual, initialState: bool) : dword; stdcall;
function   OpenGlobalEvent (name: pchar                            ) : dword; stdcall;

function CreateGlobalFileMapping (name: pchar; size: dword) : dword; stdcall;
function   OpenGlobalFileMapping (name: pchar; write: bool) : dword; stdcall;

In API hook callbacks of wide functions (e.g. GetModuleFileNameW or CreateFileW) you should avoid to use ansi functions and you should also avoid any wide <-> ansi string conversions being done by the OS. Both can result in invalid string parameters and/or crashes. If you need to convert wide <-> ansi strings, you can use the following functions, which do not risk stability. The result buffer of both functions must be big enough to hold the result string including the terminating null character.

procedure WideToAnsi (wide: pwidechar; ansi: pchar    ); stdcall;
procedure AnsiToWide (ansi: pchar;     wide: pwidechar); stdcall;

Sometimes when writing a service you want to give every user (restricted or not) the access rights to e.g. start your service. The following function adds the specified access rights to the specified process or service for "Everyone". The first parameter can either be a process handle or a service handle.

function AddAccessForEveryone (processOrService, access: dword) : bool; stdcall;