Shell-Hook
An example of how to build and use the ShellHook.dll yourself.
- Active · last change 7-31-2026
- ★ 1 on GitHub
README
:computer: Shell-Hook:


In computer programming, hooking is a range of techniques used to alter or augment the behaviour of an operating system, of applications, or of other software components by intercepting function calls or messages or 8events](https://en.wikipedia.org/wiki/Event_(computing)) passed between software components. Code that handles such intercepted function calls, events or messages is called a hook.
Hook methods are of particular importance in the template method pattern where common code in an abstract class can be augmented by custom code in a subclass. In this case each hook method is defined in the abstract class with an empty implementation which then allows a different implementation to be supplied in each concrete subclass.
Run the SHELLHook.dproj file and build the ShellHook.dll.
As with normal window messages, the second parameter of the window procedure identifies the message as WM_SHELLHOOKMESSAGE. However, for these shell hook messages, the message value is not a predefined constant like other message IDs such as WM_COMMAND. The value must be retrieved dynamically using a call to RegisterWindowMessage, as shown here:
RegisterWindowMessage(TEXT("SHELLHOOK"));:wrench: initialization driver
- First, open the
SHELLHook.dprojproject and build a new driver, which you then copy into the main project.- Ignore the error message after compiling.
- Ignore the error message after compiling.
- StartMouseHook function is typically a user-defined custom wrapper used to initialize a Windows mouse hook via SetWindowsHookEx, utilizing parameters like WH_MOUSE or WH_MOUSE_LL (low-level) to monitor mouse activity globally or locally.
-
32 bit
function StartMouseHook(State: Boolean; Wnd: HWND): Boolean; stdcall; external 'SHELLHook.dll'; function StopMouseHook(): Boolean; stdcall; external 'SHELLHook.dll'; -
64 bit
function StartMouseHook64(State: Boolean; Wnd: HWND): Boolean; stdcall; external 'SHELLHook64.dll'; function StopMouseHook64(): Boolean; stdcall; external 'SHELLHook64.dll'; Pointer size:
Use NativeInt or NativeUInt instead of Integer or LongInt when storing pointers or handle values in messages (TMessage).API declarations:
Windows API functions such as RegisterShellHookWindow andRegisterWindowMessage('SHELLHOOK')must be correctly imported for Win64 using the standard types from the Windows unit.DLL injection
(if used): If your shell hook is intended to operate globally within a DLL, that DLL must also be a 64-bit DLL, because 64-bit processes cannot load 32-bit DLLs (and vice versa).
-
Inject 64 bit dll
procedure InjectDLL64(ProcessID: DWORD; const DLLPath: string); var hProcess, hThread: THandle; pLibPath: Pointer; BytesWritten: NativeUInt; begin hProcess := OpenProcess(PROCESS_ALL_ACCESS, False, ProcessID); if hProcess <> 0 then begin // Allocate memory in the 64-bit target process pLibPath := VirtualAllocEx(hProcess, nil, Length(DLLPath) * SizeOf(Char), MEM_COMMIT, PAGE_READWRITE); // Write DLL path to memory WriteProcessMemory(hProcess, pLibPath, PChar(DLLPath), Length(DLLPath) * SizeOf(Char), BytesWritten); // Call LoadLibrary in the target process to execute the hook DLL. hThread := CreateRemoteThread(hProcess, nil, 0, GetProcAddress(GetModuleHandle('kernel32.dll'), 'LoadLibraryW'), pLibPath, 0, BytesWritten); WaitForSingleObject(hThread, INFINITE); CloseHandle(hThread); CloseHandle(hProcess); end; end; -
:x: Possible errors:
Initialization errors may occur with the 64-bit driver, and the solutions for these can vary. It can be helpful if the project includes both 32-bit and 64-bit configurations. To achieve this, the 64-bit platform configuration must be manually added in the compiler. Right-click on the target platform in the project browser on the right and select
"64-bit platform", See image..
:wrench: Parameters
The following table describes the wParam and lParam parameter values that can be passed to the window procedure for the shell hook messages.
:link: More Information : ShellProc callback function
| wParam | lParam |
|---|---|
HSHELL_GETMINRECT |
A pointer to a SHELLHOOKINFO structure. |
HSHELL_WINDOWACTIVATED |
A handle for the activated window. |
HSHELL_RUDEAPPACTIVATED |
A handle for the activated window. |
HSHELL_WINDOWREPLACING |
A handle for the window that replaces the top-level window.. |
HSHELL_WINDOWREPLACED |
A handle for the window to be replaced. |
HSHELL_WINDOWCREATED |
A handle for the window to be created. |
HSHELL_WINDOWDESTROYED |
A handle for the top-level window that will be destroyed. |
HSHELL_ACTIVATESHELLWINDOW |
Not used. |
HSHELL_TASKMAN |
Can be ignored. |
HSHELL_REDRAW |
A handle for the window that needs to be redrawn.. |
HSHELL_FLASH |
A handle for the window that needs to be flashed.. |
HSHELL_ENDTASK |
A handle for the window that is to be forced to close.. |
HSHELL_APPCOMMAND |
The APPCOMMAND parameter that has not been handled by the application or other hooks. For more information, see WM_APPCOMMAND and use the GET_APPCOMMAND_LPARAM macro to retrieve this parameter.. |
HSHELL_MONITORCHANGED |
A handle for the window that has been moved to another monitor.. |
:speech_balloon: C++ Virtual method Sample Code:
#include <iostream>
#include "windows.h"
using namespace std;
class VirtualClass
{
public:
int number;
virtual void VirtualFn1() //This is the virtual function that will be hooked.
{
cout << "VirtualFn1 called " << number++ << "\n\n";
}
};
using VirtualFn1_t = void(__thiscall*)(void* thisptr);
VirtualFn1_t orig_VirtualFn1;
void __fastcall hkVirtualFn1(void* thisptr, int edx) //This is our hook function which we will cause the program to call instead of the original VirtualFn1 function after hooking is done.
{
cout << "Hook function called" << "\n";
orig_VirtualFn1(thisptr); //Call the original function.
}
int main()
{
VirtualClass* myClass = new VirtualClass(); //Create a pointer to a dynamically allocated instance of VirtualClass.
void** vTablePtr = *reinterpret_cast<void***>(myClass); //Find the address that points to the base of VirtualClass' VMT (which then points to VirtualFn1) and store it in vTablePtr.
DWORD oldProtection;
VirtualProtect(vTablePtr, 4, PAGE_EXECUTE_READWRITE, &oldProtection); //Removes page protection at the start of the VMT so we can overwrite its first pointer.
orig_VirtualFn1 = reinterpret_cast<VirtualFn1_t>(*vTablePtr); //Stores the pointer to VirtualFn1 from the VMT in a global variable so that it can be accessed again later after its entry in the VMT has been
//overwritten with our hook function.
*vTablePtr = &hkVirtualFn1; //Overwrite the pointer to VirtualFn1 within the virtual table to a pointer to our hook function (hkVirtualFn1).
VirtualProtect(vTablePtr, 4, oldProtection, 0); //Restore old page protection.
myClass->VirtualFn1(); //Call the virtual function from our class instance. Because it is now hooked, this will actually call our hook function (hkVirtualFn1).
myClass->VirtualFn1();
myClass->VirtualFn1();
delete myClass;
return 0;
}:speech_balloon: C# keyboard event hook:
The following example will hook into keyboard events in Microsoft Windows using the Microsoft .NET Framework.
using System.Runtime.InteropServices;
namespace Hooks;
public class KeyHook
{
/* Member variables */
protected static int Hook;
protected static LowLevelKeyboardDelegate Delegate;
protected static readonly object Lock = new object();
protected static bool IsRegistered = false;
/* DLL imports */
[DllImport("user32")]
private static extern int SetWindowsHookEx(int idHook, LowLevelKeyboardDelegate lpfn,
int hmod, int dwThreadId);
[DllImport("user32")]
private static extern int CallNextHookEx(int hHook, int nCode, int wParam, KBDLLHOOKSTRUCT lParam);
[DllImport("user32")]
private static extern int UnhookWindowsHookEx(int hHook);
/* Types & constants */
protected delegate int LowLevelKeyboardDelegate(int nCode, int wParam, ref KBDLLHOOKSTRUCT lParam);
private const int HC_ACTION = 0;
private const int WM_KEYDOWN = 0x0100;
private const int WM_KEYUP = 0x0101;
private const int WH_KEYBOARD_LL = 13;
[StructLayout(LayoutKind.Sequential)]
public struct KBDLLHOOKSTRUCT
{
public int vkCode;
public int scanCode;
public int flags;
public int time;
public int dwExtraInfo;
}
/* Methods */
static private int LowLevelKeyboardHandler(int nCode, int wParam, ref KBDLLHOOKSTRUCT lParam)
{
if (nCode == HC_ACTION)
{
if (wParam == WM_KEYDOWN)
System.Console.Out.WriteLine("Key Down: " + lParam.vkCode);
else if (wParam == WM_KEYUP)
System.Console.Out.WriteLine("Key Up: " + lParam.vkCode);
}
return CallNextHookEx(Hook, nCode, wParam, lParam);
}
public static bool RegisterHook()
{
lock (Lock)
{
if (IsRegistered)
return true;
Delegate = LowLevelKeyboardHandler;
Hook = SetWindowsHookEx(
WH_KEYBOARD_LL, Delegate,
Marshal.GetHINSTANCE(
System.Reflection.Assembly.GetExecutingAssembly().GetModules()[0]
).ToInt32(), 0
);
if (Hook != 0)
return IsRegistered = true;
Delegate = null;
return false;
}
}
public static bool UnregisterHook()
{
lock (Lock)
{
return IsRegistered = (UnhookWindowsHookEx(Hook) != 0);
}
}
}:speech_balloon: API/function hooking method:
The following source code is an example of an API/function hooking method which hooks by overwriting the first six bytes of a destination function with a JMP instruction to a new function. The code is compiled into a DLL file then loaded into the target process using any method of DLL injection. Using a backup of the original function one might then restore the first six bytes again so the call will not be interrupted. In this example the win32 API function MessageBoxW is hooked.
#include <windows.h>
#define SIZE 6
typedef int (WINAPI *pMessageBoxW)(HWND, LPCWSTR, LPCWSTR, UINT); // Messagebox prototype
int WINAPI MyMessageBoxW(HWND, LPCWSTR, LPCWSTR, UINT); // Our detour
void BeginRedirect(LPVOID);
pMessageBoxW pOrigMBAddress = NULL; // address of original
BYTE oldBytes[SIZE] = {0}; // backup
BYTE JMP[SIZE] = {0}; // 6 byte JMP instruction
DWORD oldProtect, myProtect = PAGE_EXECUTE_READWRITE;
INT APIENTRY DllMain(HMODULE hDLL, DWORD Reason, LPVOID Reserved)
{
switch (Reason)
{
case DLL_PROCESS_ATTACH: // if attached
pOrigMBAddress = (pMessageBoxW)
GetProcAddress(GetModuleHandleA("user32.dll"), // get address of original
"MessageBoxW");
if (pOrigMBAddress != NULL)
BeginRedirect(MyMessageBoxW); // start detouring
break;
case DLL_PROCESS_DETACH:
VirtualProtect((LPVOID)pOrigMBAddress, SIZE, myProtect, &oldProtect); // assign read write protection
memcpy(pOrigMBAddress, oldBytes, SIZE); // restore backup
VirtualProtect((LPVOID)pOrigMBAddress, SIZE, oldProtect, &myProtect); // reset protection
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
break;
}
return TRUE;
}
void BeginRedirect(LPVOID newFunction)
{
BYTE tempJMP[SIZE] = {0xE9, 0x90, 0x90, 0x90, 0x90, 0xC3}; // 0xE9 = JMP 0x90 = NOP 0xC3 = RET
memcpy(JMP, tempJMP, SIZE); // store jmp instruction to JMP
DWORD JMPSize = ((DWORD)newFunction - (DWORD)pOrigMBAddress - 5); // calculate jump distance
VirtualProtect((LPVOID)pOrigMBAddress, SIZE, // assign read write protection
PAGE_EXECUTE_READWRITE, &oldProtect);
memcpy(oldBytes, pOrigMBAddress, SIZE); // make backup
memcpy(&JMP[1], &JMPSize, 4); // fill the nop's with the jump distance (JMP,distance(4bytes),RET)
memcpy(pOrigMBAddress, JMP, SIZE); // set jump instruction at the beginning of the original function
VirtualProtect((LPVOID)pOrigMBAddress, SIZE, oldProtect, &myProtect); // reset protection
}
int WINAPI MyMessageBoxW(HWND hWnd, LPCWSTR lpText, LPCWSTR lpCaption, UINT uiType)
{
VirtualProtect((LPVOID)pOrigMBAddress, SIZE, myProtect, &oldProtect); // assign read write protection
memcpy(pOrigMBAddress, oldBytes, SIZE); // restore backup
int retValue = MessageBoxW(hWnd, lpText, lpCaption, uiType); // get return value of original function
memcpy(pOrigMBAddress, JMP, SIZE); // set the jump instruction again
VirtualProtect((LPVOID)pOrigMBAddress, SIZE, oldProtect, &myProtect); // reset protection
return retValue; // return original return value
}:wrench: Netfilter hook:
This example shows how to use hooking to alter network traffic in the Linux kernel using Netfilter.
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/skbuff.h>
#include <linux/ip.h>
#include <linux/tcp.h>
#include <linux/in.h>
#include <linux/netfilter.h>
#include <linux/netfilter_ipv4.h>
/* Port we want to drop packets on */
static const uint16_t port = 25;
/* This is the hook function itself */
static unsigned int hook_func(unsigned int hooknum,
struct sk_buff **pskb,
const struct net_device *in,
const struct net_device *out,
int (*okfn)(struct sk_buff *))
{
struct iphdr *iph = ip_hdr(*pskb);
struct tcphdr *tcph, tcpbuf;
if (iph->protocol != IPPROTO_TCP)
return NF_ACCEPT;
tcph = skb_header_pointer(*pskb, ip_hdrlen(*pskb), sizeof(*tcph), &tcpbuf);
if (tcph == NULL)
return NF_ACCEPT;
return (tcph->dest == port) ? NF_DROP : NF_ACCEPT;
}
/* Used to register our hook function */
static struct nf_hook_ops nfho = {
.hook = hook_func,
.hooknum = NF_IP_PRE_ROUTING,
.pf = NFPROTO_IPV4,
.priority = NF_IP_PRI_FIRST,
};
static __init int my_init(void)
{
return nf_register_hook(&nfho);
}
static __exit void my_exit(void)
{
nf_unregister_hook(&nfho);
}
module_init(my_init);
module_exit(my_exit);:speech_balloon: Internal IAT hooking:
The following code demonstrates how to hook functions that are imported from another module. This can be used to hook functions in a different process from the calling process. For this the code must be compiled into a DLL file then loaded into the target process using any method of DLL injection. The advantage of this method is that it is less detectable by antivirus software and/or anti-cheat software, one might make this into an external hook that doesn't make use of any malicious calls. The Portable Executable header contains the Import Address Table (IAT), which can be manipulated as shown in the source below. The source below runs under Microsoft Windows.
#include <windows.h>
typedef int(__stdcall *pMessageBoxA) (HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType); //This is the 'type' of the MessageBoxA call.
pMessageBoxA RealMessageBoxA; //This will store a pointer to the original function.
void DetourIATptr(const char* function, void* newfunction, HMODULE module);
int __stdcall NewMessageBoxA(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType) { //Our fake function
printf("The String Sent to MessageBoxA Was : %s\n", lpText);
return RealMessageBoxA(hWnd, lpText, lpCaption, uType); //Call the real function
}
int main(int argc, CHAR *argv[]) {
DetourIATptr("MessageBoxA",(void*)NewMessageBoxA,0); //Hook the function
MessageBoxA(NULL, "Just A MessageBox", "Just A MessageBox", 0); //Call the function -- this will invoke our fake hook.
return 0;
}
void **IATfind(const char *function, HMODULE module) { //Find the IAT (Import Address Table) entry specific to the given function.
int ip = 0;
if (module == 0)
module = GetModuleHandle(0);
PIMAGE_DOS_HEADER pImgDosHeaders = (PIMAGE_DOS_HEADER)module;
PIMAGE_NT_HEADERS pImgNTHeaders = (PIMAGE_NT_HEADERS)((LPBYTE)pImgDosHeaders + pImgDosHeaders->e_lfanew);
PIMAGE_IMPORT_DESCRIPTOR pImgImportDesc = (PIMAGE_IMPORT_DESCRIPTOR)((LPBYTE)pImgDosHeaders + pImgNTHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);
if (pImgDosHeaders->e_magic != IMAGE_DOS_SIGNATURE)
printf("libPE Error : e_magic is no valid DOS signature\n");
for (IMAGE_IMPORT_DESCRIPTOR *iid = pImgImportDesc; iid->Name != NULL; iid++) {
for (int funcIdx = 0; *(funcIdx + (LPVOID*)(iid->FirstThunk + (SIZE_T)module)) != NULL; funcIdx++) {
char *modFuncName = (char*)(*(funcIdx + (SIZE_T*)(iid->OriginalFirstThunk + (SIZE_T)module)) + (SIZE_T)module + 2);
const uintptr_t nModFuncName = (uintptr_t)modFuncName;
bool isString = !(nModFuncName & (sizeof(nModFuncName) == 4 ? 0x80000000 : 0x8000000000000000));
if (isString) {
if (!_stricmp(function, modFuncName))
return funcIdx + (LPVOID*)(iid->FirstThunk + (SIZE_T)module);
}
}
}
return 0;
}
void DetourIATptr(const char *function, void *newfunction, HMODULE module) {
void **funcptr = IATfind(function, module);
if (*funcptr == newfunction)
return;
DWORD oldrights, newrights = PAGE_READWRITE;
//Update the protection to READWRITE
VirtualProtect(funcptr, sizeof(LPVOID), newrights, &oldrights);
RealMessageBoxA = (pMessageBoxA)*funcptr; //Some compilers require the cast (like "MinGW"), not sure about MSVC though
*funcptr = newfunction;
//Restore the old memory protection flags.
VirtualProtect(funcptr, sizeof(LPVOID), oldrights, &newrights);
}
