{
	"id": "b9fd281c-8de1-4a6a-be78-9578f7613402",
	"created_at": "2026-04-06T00:19:35.481628Z",
	"updated_at": "2026-04-10T03:20:29.739986Z",
	"deleted_at": null,
	"sha1_hash": "09bd1f5564ce12cf1d5da6184d1fdef5b448aa76",
	"title": "MoVP 3.1 Detecting Malware Hooks in the Windows GUI Subsystem",
	"llm_title": "",
	"authors": "",
	"file_creation_date": "0001-01-01T00:00:00Z",
	"file_modification_date": "0001-01-01T00:00:00Z",
	"file_size": 178514,
	"plain_text": "MoVP 3.1 Detecting Malware Hooks in the Windows GUI\r\nSubsystem\r\nArchived: 2026-04-05 18:37:03 UTC\r\nMonth of Volatility Plugins\r\nApplications can place hooks into the Windows GUI subsystem to customize the user\r\nexperience, receive notification when certain actions take place, or to record everything the user does - for\r\nexample to create a CBT training video. As you probably expected, this type of access and control is often\r\nexploited by malware for capturing keystrokes, injecting malicious DLLs into trusted processes, and other\r\nnefarious actions.\r\nIn this post, we'll discuss the undocumented Windows kernel data structures used to manage the two most popular\r\nclasses of hooks - message hooks and event hooks. We'll also show how you can detect the installed hooks using\r\nnew plugins for the Volatility memory forensics framework. This is significant because hooks are installed very\r\nfrequently by malware, yet there are no forensic tools to detect and analyze these hooks. In fact, there aren't even\r\nmany tools that run on live systems, except the old anti-rootkit IceSword (doesn't support all Windows\r\nversions) and the open source MsgHookLister (does not detect event hooks). Many sandboxes produce alerts\r\nwhen hooks are set, but they typically rely on pre-installed API hooks to determine when the involved functions\r\nare called.\r\nMessage Hooks\r\nWhen a user presses a key, the system generates a WM_KEYDOWN message and delivers it to the target\r\nwindow's queue. Typically the target window is the foreground window (i.e. the one the user is currently\r\ninteracting with). When the message hits the queue, the owning thread wakes up and processes the message -\r\nwhich could mean appending it to a line being typed, taking some special action if the key is a \"hot key\" or even\r\njust ignoring it. The diagram below shows a very simplified diagram of a non-hooked messaging system:\r\nMessage hooks can intercept these window messages before they reach the target window procedure. For\r\nexample, by using SetWindowsHookEx with the WH_KEYBOARD filter, malware can essentially \"spy\" on the\r\nWM_KEYDOWN messages (and other keyboard operations), log them, and either pass them on to the intended\r\napplication or prevent them from ever reaching the right place. This is one of the oldest and most effective ways to\r\nlog keystrokes on Windows based systems. \r\nhttps://volatility-labs.blogspot.com/2012/09/movp-31-detecting-malware-hooks-in.html\r\nPage 1 of 9\n\nMessage hooks can also serve as a generic way of injecting a malicious DLL into a GUI process. Aside from the\r\nWH_* filter, the other parameters to SetWindowsHookEx include:\r\nan address to a hook procedure that will handle the message before the target window. This procedure is a\r\nfunction of type HOOKPROC that receives the message, processes it, and optionally passes it to the next\r\nhook in the chain (or the target window if there are no other hooks) with CallNextHookEx. \r\na handle to the DLL that contains the hook procedure. This DLL is loaded into the address space of the\r\nthread that owns the target window. \r\na thread ID (i.e. scope) for the hook. This can be a specific thread or 0 to affect all threads in the current\r\ndesktop.\r\nThe diagram below shows how the messaging system works when hooks are installed. When a message is\r\ngenerated, the DLL containing the hook procedure is mapped into the address space of the specified thread(s), if it\r\nisn't already loaded. The message is passed to the hook procedure, which handles it as desired, and then finally, if\r\nallowed, the message reaches the target window procedure for normal processing. \r\nData Structures\r\nThe main data structure for message hooks is tagHOOK. The one below is from a Windows 7 x64 system.\r\n \u003e\u003e\u003e dt(\"tagHOOK\")\r\n'tagHOOK' (96 bytes)\r\n0x0   : head                           ['_THRDESKHEAD']\r\n0x28  : phkNext                        ['pointer64', ['tagHOOK']]\r\n0x30  : iHook                          ['long']\r\n0x38  : offPfn                         ['unsigned long long']\r\n0x40  : flags                          ['Flags', {'bitmap': {'HF_INCHECKWHF': 8, 'HF_HOOKFAULTED': 4,\r\n'HF_WX86KNOWNDLL': 6, 'HF_HUNG': 3, 'HF_FREED': 9, 'HF_ANSI': 1, 'HF_GLOBAL': 0,\r\n'HF_DESTROYED': 7}}]\r\n0x44  : ihmod                          ['long']\r\n0x48  : ptiHooked                      ['pointer64', ['tagTHREADINFO']]\r\n0x50  : rpdesk                         ['pointer64', ['tagDESKTOP']]\r\n0x58  : fLastHookHung                  ['BitField', {'end_bit': 8, 'start_bit': 7, 'native_type': 'long'}]\r\nhttps://volatility-labs.blogspot.com/2012/09/movp-31-detecting-malware-hooks-in.html\r\nPage 2 of 9\n\n0x58  : nTimeout                       ['BitField', {'end_bit': 7, 'start_bit': 0, 'native_type': 'unsigned long'}]\r\nKey Points \r\nhead is the common header for USER objects and can help identify the owning process or thread. More\r\ninformation on these headers will be published in a future MoVP post. \r\nphkNext is a pointer to the next hook in the chain. When a hook procedure calls CallNextHookEx, the\r\nsystem locates the next hook using this member.\r\noffPfn is an RVA (relative virtual address) to the hook procedure. The procedure can be in the same module\r\nas the code calling SetWindowsHookEx (for local, thread-specific hooks only) in which case ihmod will be\r\n-1. Otherwise, for global hooks, the procedure is in a DLL and ihmod is an index into\r\nwin32k!_aatomSysLoaded (an array of atoms).  To determine the name of the DLL, you must translate the\r\nihmod into an atom and then obtain the atom name. \r\nptiHooked can be used to identify the hooked thread. \r\nrpdesk can be used to identify the desktop in which the hook is set. Hooks cannot cross desktop\r\nboundaries. \r\nThe MessageHooks Plugin\r\nThe messagehooks plugin enumerates global hooks by finding all desktops and analyzing\r\ntagDESKTOP.pDeskInfo.aphkStart - an array of tagHOOK structures whose positions in the array indicate which\r\ntype of message is to be filtered (such as WH_KEYBOARD or WH_MOUSE). The\r\ntagDESKTOP.pDeskInfo.fsHooks value can be used as a bitmap to tell you which positions in the array are\r\nactively being used. Likewise, for each thread attached to a desktop, the plugin scans for thread-specific hooks by\r\nlooking at tagTHREADINFO.aphkStart and tagTHREADINFO.fsHooks.\r\nThe disassembly below shows malware installing a WM_GETMESSAGE hook. This is an example of malware\r\nusing SetWindowsHookEx as simply a means to load DLLs in other processes (not to actually monitor/intercept\r\nmessages sent to/from other windows). You can tell because the lpfnWndProc is empty - it just passes control to\r\nthe next hook in the chain using CallNextHookEx. Also note the dwThreadId parameter (ebx) is going to be 0,\r\nwhich means the hook is global and will affect all GUI threads in the desktop. \r\nhttps://volatility-labs.blogspot.com/2012/09/movp-31-detecting-malware-hooks-in.html\r\nPage 3 of 9\n\nRunning the messagehooks plugin on a memory dump infected with this malware will show results such as the\r\nfollowing:\r\n$ python vol.py -f laqma.vmem messagehooks --output=block\r\nVolatile Systems Volatility Framework 2.1_alpha\r\nOffset(V)  : 0xbc693988\r\nSession    : 0\r\nDesktop    : WinSta0\\Default\r\nThread     : \u003cany\u003e\r\nFilter     : WH_GETMESSAGE\r\nFlags      : HF_ANSI, HF_GLOBAL\r\nProcedure  : 0x1fd9\r\nihmod      : 1\r\nModule     : C:\\WINDOWS\\system32\\Dll.dll\r\nOffset(V)  : 0xbc693988\r\nSession    : 0\r\nDesktop    : WinSta0\\Default\r\nThread     : 1584 (explorer.exe 1624)\r\nhttps://volatility-labs.blogspot.com/2012/09/movp-31-detecting-malware-hooks-in.html\r\nPage 4 of 9\n\nFilter     : WH_GETMESSAGE\r\nFlags      : HF_ANSI, HF_GLOBAL\r\nProcedure  : 0x1fd9\r\nihmod      : 1\r\nModule     : C:\\WINDOWS\\system32\\Dll.dll\r\nOffset(V)  : 0xbc693988\r\nSession    : 0\r\nDesktop    : WinSta0\\Default\r\nThread     : 252 (VMwareUser.exe 1768)\r\nFilter     : WH_GETMESSAGE\r\nFlags      : HF_ANSI, HF_GLOBAL\r\nProcedure  : 0x1fd9\r\nihmod      : 1\r\nModule     : C:\\WINDOWS\\system32\\Dll.dll\r\n[snip]\r\nAs you can see, all of the hooks are global, because the flags include HF_GLOBAL. That means they're the direct\r\nresult of calling SetWindowsHookEx with the dwThreadId parameter set to 0. Although all global hooks are not\r\nmalicious, out of the malware samples I've seen that hook window messages, they typically do use global hooks.\r\nThe difference between the three hooks shown is that the first one is global and was gathered from the\r\ntagDESKTOP structure.  You can tell because the target thread is “\u003cany\u003e”. This is essentially just telling you that\r\nany GUI threads that run in the WinSta0\\Default desktop will be subject to monitoring by the malware. The next\r\ntwo are associated with specific threads (as a result of the global hook) and have caused Dll.dll to be injected into\r\nexplorer.exe and VMwareUser.exe.\r\nWe already know from the disassembly that the lpfnWndProc is empty - this hook just exists to inject a DLL into\r\nother processes. However, don't overlook the fact that the messagehooks plugin shows you the address (as an\r\nRVA) of the hook procedure in the DLL. In the examples shown the hook procedure can be found at 0x1fd9 from\r\nthe base of Dll.dll in the affected processes. Thus if you didn't preemptively know the purpose of a hook, you can\r\neasily use volshell and switch into the target process' contexts and disassemble the function.\r\nBelow, we first locate the base address (0xac0000) of the injected DLL inside explorer.exe. Next we disassemble\r\nthe code at offset 0x1fd9:\r\nhttps://volatility-labs.blogspot.com/2012/09/movp-31-detecting-malware-hooks-in.html\r\nPage 5 of 9\n\n$ python vol.py -f laqma.vmem dlllist -p 1624 | grep Dll.dll\r\nVolatile Systems Volatility Framework 2.2_alpha\r\n0x00ac0000     0x8000 C:\\Documents and Settings\\Mal Ware\\Desktop\\Dll.dll\r\n$ python vol.py -f laqma.vmem volshell\r\nVolatile Systems Volatility Framework 2.2_alpha\r\nCurrent context: process System, pid=4, ppid=0 DTB=0x31a000\r\nWelcome to volshell! Current memory image is:\r\nfile:///Users/Michael/Desktop/laqma.vmem\r\nTo get help, type 'hh()'\r\n\u003e\u003e\u003e cc(pid = 1624)\r\nCurrent context: process explorer.exe, pid=1624, ppid=1592 DTB=0x80001c0\r\n\u003e\u003e\u003e dis(0x00ac0000 + 0x00001fd9)\r\n0xac1fd9 ff74240c                     PUSH DWORD [ESP+0xc]\r\n0xac1fdd ff74240c                     PUSH DWORD [ESP+0xc]\r\n0xac1fe1 ff74240c                     PUSH DWORD [ESP+0xc]\r\n0xac1fe5 ff350060ac00                 PUSH DWORD [0xac6000]\r\n0xac1feb ff157c40ac00                 CALL DWORD [0xac407c] ; CallNextHookEx\r\n0xac1ff1 c20c00                       RET 0xc\r\nEvent Hooks\r\nEvent hooks can be used by applications to receive notification when certain events occur. Similar to message\r\nhooks, the SetWinEventHook API function can also be used to generically load a DLL into any processes that fire\r\nevents, such as explorer.exe. For example, Explorer uses events when sounds are played\r\n(EVENT_SYSTEM_SOUND), when a scroll operation begins (EVENT_SYSTEM_SCROLLSTART), or when\r\nan item in a menu bar such as the Start menu is selected (EVENT_SYSTEM_MENUSTART). If malware doesn’t\r\ncare about the type of event (just that it can load a DLL into the remote process), it will use EVENT_MIN and\r\nEVENT_MAX, which apply to all events. This is a quick and effective way to execute code in the context of a\r\nremote process.\r\nData Structures \r\nThe data structure for event hooks is tagEVENTHOOK. Microsoft does not document this internal structure (even\r\nin the public Windows 7 PDBs), so its fields and offsets are reverse engineered from win32k.sys binaries.\r\n\u003e\u003e\u003e dt(\"tagEVENTHOOK\")\r\n'tagEVENTHOOK' (None bytes)\r\n0x18  : phkNext                        ['pointer', ['tagEVENTHOOK']]\r\n0x20  : eventMin                       ['Enumeration', {'target': 'unsigned long', 'choices': {1: 'EVENT_MIN', 2:\r\n'EVENT_SYSTEM_ALERT', [snip]\r\n0x24  : eventMax                       ['Enumeration', {'target': 'unsigned long', 'choices': {1: 'EVENT_MIN', 2:\r\n'EVENT_SYSTEM_ALERT', [snip]\r\nhttps://volatility-labs.blogspot.com/2012/09/movp-31-detecting-malware-hooks-in.html\r\nPage 6 of 9\n\n0x28  : dwFlags                        ['unsigned long']\r\n0x2c  : idProcess                      ['unsigned long']\r\n0x30  : idThread                       ['unsigned long']\r\n0x40  : offPfn                         ['unsigned long long']\r\n0x48  : ihmod                          ['long']\r\nKey Points\r\nphkNext is the next hook in the chain \r\neventMin is the lowest system event that the hook applies to\r\neventMax is the highest system event that the hook applies to\r\ndwFlags tells you if the process generating the event will load the DLL containing the event hook\r\nprocedure into its address space (WINEVENT_INCONTEXT).  It also tells you if the thread installing the\r\nhook wishes to be except from the hook (WINEVENT_SKIPOWNPROCESS and\r\nWINEVENT_SKIPOWNTHREAD). \r\nidProcess is the pid of the target process, or 0 for all processes in the desktop\r\nidThread is the tid of the target thread, or 0 for all threads in the desktop\r\noffPfn is the RVA to the hook procedure in the DLL \r\nihmod is the index into the win32k!_aatomSysLoaded array, which can be used to identify the full path to\r\nthe DLL containing the hook procedure (see ihmod in the message hook section). \r\nThe EventHooks Plugin\r\nThe eventhooks plugin leverages Volatility’s API for USER handles (to be presented in a future MoVP post) and it\r\nfilters TYPE_WINEVENTHOOK types. Before showing the plugin output, here’s a quick volshell demo script so\r\nyou can see exactly how the API works. The goal is to print the thread IDs that installed the hooks and associate\r\nthem with the hooked threads. The code first enumerates all unique sessions and finds each session’s shared\r\ninformation structure. Then it walks the handle table looking for event hooks. Finally, it calls\r\n_HANDLEENTRY.reference_object() which basically casts the handle entry’s phead pointer as a\r\ntagEVENTHOOK. \r\n$ python vol.py -f win7x64.dd --profile=Win7SP1x64 volshell\r\nVolatile Systems Volatility Framework 2.1_alpha\r\nCurrent context: process System, pid=4, ppid=0 DTB=0x187000\r\nWelcome to volshell! Current memory image is:\r\nfile:///Users/Michael/Desktop/win7x64.dd\r\n\u003e\u003e\u003e for session in gui_utils.session_spaces(self.addrspace):\r\n...   shared_info = session.find_shared_info()\r\n...   for handle in shared_info.handles():\r\nhttps://volatility-labs.blogspot.com/2012/09/movp-31-detecting-malware-hooks-in.html\r\nPage 7 of 9\n\n...     if str(handle.bType) == \"TYPE_WINEVENTHOOK\":\r\n...       event_hook = handle.reference_object()\r\n...       print \"Hook for tid\", event_hook.idThread, \"installed by\", handle.Thread.Cid.UniqueThread\r\n... \r\nHook for tid 0 installed by 1516\r\nNow let’s view the full eventhooks output for this Windows 7 x64 system:\r\n$ python vol.py -f  win7x64.dd --profile=Win7SP1x64 eventhooks\r\nVolatile Systems Volatility Framework 2.1_alpha\r\nHandle: 0x300cb, Object: 0xfffff900c01eda10, Session: 1\r\nType: TYPE_WINEVENTHOOK, Flags: 0, Thread: 1516, Process: 880\r\neventMin: 0x4 EVENT_SYSTEM_MENUSTART\r\neventMax: 0x7 EVENT_SYSTEM_MENUPOPUPEND\r\nFlags: none, offPfn: 0xff567cc4, idProcess: 0, idThread: 0\r\nihmod: -1\r\nThere is one event hook installed, by a thread of the process 880 (explorer.exe). The types of events being filtered\r\nare menu operations and desktop switches. Since ihmod is -1 for the hook, you know the offPfn hook procedure is\r\nlocated in explorer.exe itself and not a loaded DLL. Thus, most likely this hook is not malicious. Why would we\r\nshow a benign example? Because you need to be able to distinguish for yourself during your own investigations.\r\nIf the hook was malicious, it would look very similar to the message hooks in the previous section - it would be\r\nglobal and the hook procedure would be inside a DLL. \r\nConclusion\r\nMessage and Event hooks are similar to API hooks (such as trampoline, IAT, EAT hooks) in that they allow\r\nmalware to redirect the normal flow of execution to attacker-designed functions. Yet they are also unique because\r\nthey're legitimate components of the GUI subsystem just being used in a malicious manner. They don't rely on\r\noverwriting pointers or patching instructions in process or kernel memory, so typical API-hook detecting utilities\r\nwon't catch them. The low-level internals and data structures are completely undocumented, which explains why\r\nthere aren't many tools, much less forensic tools, that can analyze a system in the way that Volatility now can.\r\nMore information on the messagehooks and eventhooks plugin and its usages in forensic investigations will be\r\npresented at Open Memory Forensics Workshop (OMFW) 2012.\r\nhttps://volatility-labs.blogspot.com/2012/09/movp-31-detecting-malware-hooks-in.html\r\nPage 8 of 9\n\nSource: https://volatility-labs.blogspot.com/2012/09/movp-31-detecting-malware-hooks-in.html\r\nhttps://volatility-labs.blogspot.com/2012/09/movp-31-detecting-malware-hooks-in.html\r\nPage 9 of 9",
	"extraction_quality": 1,
	"language": "EN",
	"sources": [
		"MITRE"
	],
	"references": [
		"https://volatility-labs.blogspot.com/2012/09/movp-31-detecting-malware-hooks-in.html"
	],
	"report_names": [
		"movp-31-detecting-malware-hooks-in.html"
	],
	"threat_actors": [],
	"ts_created_at": 1775434775,
	"ts_updated_at": 1775791229,
	"ts_creation_date": 0,
	"ts_modification_date": 0,
	"files": {
		"pdf": "https://archive.orkl.eu/09bd1f5564ce12cf1d5da6184d1fdef5b448aa76.pdf",
		"text": "https://archive.orkl.eu/09bd1f5564ce12cf1d5da6184d1fdef5b448aa76.txt",
		"img": "https://archive.orkl.eu/09bd1f5564ce12cf1d5da6184d1fdef5b448aa76.jpg"
	}
}