I. Concepts of message hooks
1. Basic Concepts
Windows applications are message-driven. Any thread registers window classes with a message queue to receive user input messages and system messages. To intercept messages, Windows proposes the hook concept. Hook is a monitoring point in Windows Message Processing Mechanism. Hook provides a callback function. After a hook is installed in a program, it monitors messages of the program. The hook program captures the message before the specified message reaches the window. This gives you the opportunity to filter this message or monitor Windows messages.
2. Classification
Message hooks include local hooks and global hooks. A local Hook intercepts specified messages of only one process. A global Hook intercepts specified messages of all processes in the system.
3. Implementation steps
The following steps are generally taken to intercept messages by using the hook technique:
- Set the hook callback function (the function called after the message is intercepted)
- Install the hook. (use the SetWindowsHookEx function)
- Uninstall the hook. (Use the UnhookWindowsHookEx function)
4. Functions
Message hooks can be used to implement special effects such as interface, synchronous Message, monitoring message, and Automatic startup.
Ii. Use of message Hook Technology by viruses
Computer viruses often use message hooks to implement two functions:
1. Monitor user buttons to steal user information.
This virus will start an EXE virus process in the resident memory, and then install a global Keyboard Message hook. The hook callback function is located in the virus process, so that any process with buttons in the system can be operated, the key details are intercepted by virus processes.
2. Self-start
Such a virus stores the hook callback function in a DLL file, and then installs a global message (easily triggered message, such as WH_CBT and WH_GETMESSAGE) Hook, in this case, the messaging process automatically loads the virus DLL and the virus runs automatically.
Iii. Protection against message hook virus (important)
1. Protection Technical Principles
The method for dealing with the message hook virus is simple. You only need to uninstall the hook installed with the virus. (Note: when many processes in the system have loaded virus DLL due to global hooks, they do not need to be uninstalled, as long as the installed message hook is uninstalled, the corresponding DLL will be automatically uninstalled in these processes .) There are two methods to uninstall a HOOK:
(1) Terminate the hook installation process.
The process that sets the hook to end. Before the process exits, it uninstalls all message hooks installed by the process. This method is suitable for the virus that monitors user buttons.
(2) obtain the message hook handle and call the UnhookWindowsHookEx function to unmount the message hook.
If the virus independently starts a virus process and installs a global message hook, the system will stay in the memory. Then we can end the virus process. However, if the virus injects code into the system process and the installed hook is located in the system process, we cannot end the system process. In this case, we can only get the message hook handle, then call the function to uninstall it.
2. Protection technical implementation details
You can easily uninstall the virus message hook by killing the installation hook process. The method for obtaining the virus message hook handle and then calling the function to uninstall the hook is complicated. This article focuses on the content, which will be detailed in the next title.
Iv. How to find and unmount the hook handle of a virus message (important and difficult)
1. Implementation Principle Analysis
The system stores all installed hook handles in the kernel. To find the message hook handle for virus installation, We need to enumerate all message hook handles. How can we solve the problem later? How do we know which handle is installed with a virus during the enumeration process?
By analyzing the virus sample, we usually get the virus installation hook to load the virus DLL to other legitimate processes, so it will write the hook callback function in the DLL. When enumerating the message hook handle, you can also obtain the DLL module of the callback function corresponding to the handle. The virus message hook handle can be found based on whether the DLL module is a virus DLL module, finally, uninstall it.
For how to enumerate the system message hook handle, the methods for different operating systems are very different. Here we introduce a user-layer READ memory method, which is only available in the 2000/XP system.
In the Windows 2000/XP system, there is an application interface User32.dll related to the Windows user interface. It is used to include features such as Windows window processing and basic user interface, such as creating a window and sending a message. When it is loaded to the memory, it stores all Windows and message-related handles, including the message hook handle. These handles are stored in a shared memory segment, usually called the R3 layer gui table. So long as we find the gui table, and then filter out the message hook handle in the handle. The memory segment of gui table can be accessed by all process spaces. Gui table is defined as the following structure:
Typedef struct tagSHAREDINFO {
Struct tagSERVERINFO * pServerInfo; // pointer to the tagSERVERINFO Structure
Struct _ HANDLEENTRY * pHandleEntry; // point to the handle table
Struct tagDISPLAYINFO * pDispInfo; // pointer to the tagDISPLAYINFO Structure
ULONG ulSharedDelta;
LPWSTR pszDllList;
} SHAREDINFO, * PSHAREDINFO;
The tagSERVERINFO struct pointed to by pServerInfo, the first member of the tagSHAREDINFO struct, is defined as follows.
Typedef struct tagSERVERINFO {
Short wRIPFlags;
Short wSRVIFlags;
Short wRIPPID;
Short wRIPError;
ULONG cHandleEntries; // Number of handles in the handle table
} SERVERINFO, * PSERVERINFO;
We can see that the number of handles pointed to by the pHandleEntry member in the tagSHAREDINFO structure can be obtained through the cHandleEntries member in the tagSERVERINFO structure.
The second member of the tagSHAREDINFO struct, pHandleEntry, is the pointer to the start address of the _ HANDLEENTRY struct array. A member of the array corresponds to a handle. The handle struct _ HANDLEENTRY is defined as follows.
Typedef struct _ HANDLEENTRY {
PVOID pObject; // point to the kernel object corresponding to the handle
ULONG pOwner;
BYTE bType; // handle type
BYTE bFlags;
Short wUniq;
} HANDLEENTRY, * PHANDLEENTRY;
The _ HANDLEENTRY struct member bType is the handle type. This variable can be used to filter the message hook handle. The types of handles stored in User32 are usually as follows.
Typedef enum _ HANDLE_TYPE
{
TYPE_FREE = 0,
TYPE_WINDOW = 1,
TYPE_MENU = 2, // menu handle
TYPE_CURSOR = 3, // cursor handle
TYPE_SETWINDOWPOS = 4,
TYPE_HOOK = 5, // message hook handle
TYPE_CLIPDATA = 6,
TYPE_CALLPROC = 7,
TYPE_ACCELTABLE = 8,
TYPE_DDEACCESS = 9,
TYPE_DDECONV = 10,
TYPE_DDEXACT = 11,
TYPE_MONITOR = 12,
TYPE_KBDLAYOUT = 13,
TYPE_KBDFILE = 14,
TYPE_WINEVENTHOOK = 15,
TYPE_TIMER = 16,
TYPE_INPUTCONTEXT = 17,
TYPE_CTYPES = 18,
TYPE_GENERIC = 255
} HANDLE_TYPE;
The member pObject of the _ HANDLEENTRY struct is the pointer to the corresponding Kernel Object of the handle.
In this way, you only need to use pObject to obtain detailed information about the handle (including information about the creation process, thread, callback function, and so on) and the type of the value handle that can be obtained through bType.
Other members of the _ HANDLEENTRY struct can be ignored.
(Knowledge points: how to read the kernel memory in the user-Layer Program)
Note that the pObject Pointer Points to the kernel memory and cannot directly access the kernel memory at the user layer. The kernel memory is also used in some aspects. Data in the kernel memory should be read to the user-layer memory for access. It cannot be accessed directly. After all, it is not in the driver.
The ZwSystemDebugControl function is used to read the kernel memory at the user layer. It is a Native API. The prototype is as follows.
NTSYSAPI
NTSTATUS
NTAPI
ZwSystemDebugControl (
IN DEBUG_CONTROL_CODE ControlCode, // control code
In pvoid InputBuffer OPTIONAL, // input memory
In ulong InputBufferLength, // input Memory Length
Out pvoid OutputBuffer OPTIONAL, // output memory
In ulong OutputBufferLength, // output Memory Length
Out pulong ReturnLength OPTIONAL // the actual output length );
The ZwSystemDebugControl function can be used to read/write kernel space, read/write MSR, read/write physical memory, read/write IO ports, read/write bus data, and KdVersionBlock. The first ControlCode parameter controls its function. You can use the following enumerated values.
Typedef enum _ SYSDBG_COMMAND {
// The following five versions are available in all versions of Windows NT
SysDbgGetTraceInformation = 1,
SysDbgSetInternalBreakpoint = 2,
SysDbgSetSpecialCall = 3,
Sysdbgclearspecialcils = 4,
Sysdbgqueryspecialcils = 5,
// The following are newly added NT 5.1
SysDbgDbgBreakPointWithStatus = 6,
// Obtain the KdVersionBlock
SysDbgSysGetVersion = 7,
// Copy from kernel space to user space or from user space to user space
// But cannot be copied from the user space to the kernel space
SysDbgCopyMemoryChunks_0 = 8,
// SysDbgReadVirtualMemory = 8,
// Copy from user space to kernel space or from user space to user space
// But cannot be copied from the kernel space to the user space
SysDbgCopyMemoryChunks_1 = 9,
// SysDbgWriteVirtualMemory = 9,
// Copy from physical address to user space and cannot write to kernel space
SysDbgCopyMemoryChunks_2 = 10,
// SysDbgReadVirtualMemory = 10,
// Copy from the user space to the physical address and cannot read the kernel space
SysDbgCopyMemoryChunks_3 = 11,
// SysDbgWriteVirtualMemory = 11,
// Read/write processor-related control blocks
SysDbgSysReadControlSpace = 12,
SysDbgSysWriteControlSpace = 13,
// Read/write port
SysDbgSysReadIoSpace = 14,
SysDbgSysWriteIoSpace = 15,
// Call RDMSR @ 4 and _ WRMSR @ 12 respectively
SysDbgSysReadMsr = 16,
SysDbgSysWriteMsr = 17,
// Read/write bus data
SysDbgSysReadBusData = 18,
SysDbgSysWriteBusData = 19,
SysDbgSysCheckLowMemory = 20,
// The following are newly added NT 5.2
// Call _ KdEnableDebugger @ 0 and _ KdDisableDebugger @ 0 respectively.
SysDbgEnableDebugger = 21,
SysDbgDisableDebugger = 22,
// Obtain and set some debugging-related variables
SysDbgGetAutoEnableOnEvent = 23,
SysDbgSetAutoEnableOnEvent = 24,
SysDbgGetPitchDebugger = 25,
Sysdbgsetdbgprs intbuffersize = 26,
SysDbgGetIgnoreUmExceptions = 27,
SysDbgSetIgnoreUmExceptions = 28
} SYSDBG_COMMAND, * PSYSDBG_COMMAND;
We need to read the kernel memory here, so the ControlCode parameter should be set to SysDbgReadVirtualMemory.
When the ControlCode value is SysDbgReadVirtualMemory, the 4th and 5th parameters of the ZwSystemDebugControl function are ignored. If the value is used, enter 0. The second parameter, InputBuffer, is a pointer to the struct _ MEMORY_CHUNKS. The struct is defined as follows.
Typedef struct _ MEMORY_CHUNKS {
ULONG Address; // kernel memory Address pointer (data to be read)
PVOID Data; // user-layer memory address pointer (storing read Data)
ULONG Length; // read Length
} MEMORY_CHUNKS, * PMEMORY_CHUNKS;
The third parameter InputBufferLength is the size of the _ MEMORY_CHUNKS struct. Use the sizeof operator.
If the SysDbgReadVirtualMemory function is successfully executed, 0 is returned. Otherwise, an error code is returned.
For ease of use, we can encapsulate a function GetKernelMemory to read the kernel memory, which is implemented as follows:
# Define SysDbgReadVirtualMemory 8
// Define the ZwSystemDebugControl function pointer type
Typedef DWORD (WINAPI * ZWSYSTEMDEBUGCONTROL) (DWORD, PVOID,
DWORD, PVOID, DWORD, PVOID );
BOOL GetKernelMemory (PVOID pKernelAddr, PBYTE pBuffer, ULONG uLength)
{
MEMORY_CHUNKS mc;
ULONG uReaded = 0;
Mc. Address = (ULONG) pKernelAddr; // kernel memory Address
Mc. pData = pBuffer; // user-layer memory address
Mc. Length = uLength; // read the Length of the memory.
ULONG st =-1;
// Obtain the ZwSystemDebugControl function address
ZWSYSTEMDEBUGCONTROL ZwSystemDebugControl = (ZWSYSTEMDEBUGCONTROL) GetProcAddress (
GetModuleHandle ("ntdll. dll"), "ZwSystemDebugControl ");
// Read kernel memory data to the user layer
St = ZwSystemDebugControl (SysDbgReadVirtualMemory, & mc, sizeof (mc), 0, 0, & uReaded );
Return st = 0;
}
For different types of handles, the struct corresponding to the memory of the kernel object is different. For the message hook handle, the struct corresponding to the memory of the kernel object is actually the _ HOOK_INFO type. Its definition is as follows.
Typedef struct _ HOOK_INFO
{
HANDLE hHandle; // hook HANDLE
DWORD Unknown1;
PVOID Win32Thread; // one pointing to win32k! _ W32THREAD struct pointer
PVOID Unknown2;
PVOID SelfHook; // point to the first address of the struct
PVOID NextHook; // point to the next hook Structure
Int iHookType; // hook type.
DWORD OffPfn; // address offset of the hook function, relative to the module
Int iHookFlags; // hook flag
Int iMod; // The Index Number of the hook function in the module. You can use it to obtain the base address of the module.
PVOID Win32ThreadHooked; // hook thread structure pointer
} HOOK_INFO, * PHOOK_INFO;
As shown in the preceding figure, after obtaining the hook kernel object data, the data corresponds to the HOOK_INFO struct information. Where:
HHandle is the hook handle. You can use it to uninstall the hook.
IHookType is the hook type. The message hook type is defined as follows.
Typedef enum _ HOOK_TYPE {
MY_WH_MSGFILTER =-1,
MY_WH_JOURNALRECORD = 0,
MY_WH_JOURNALPLAYBACK = 1,
MY_WH_KEYBOARD = 2,
MY_WH_GETMESSAGE = 3,
MY_WH_CALLWNDPROC = 4,
MY_WH_CBT = 5,
MY_WH_SYSMSGFILTER = 6,
MY_WH_MOUSE = 7,
MY_WH_HARDWARE = 8,
MY_WH_DEBUG = 9,
MY_WH_SHELL = 10,
MY_WH_FOREGROUNDIDLE = 11,
MY_WH_CALLWNDPROCRET = 12,
MY_WH_KEYBOARD_LL = 13,
MY_WH_MOUSE_LL = 14
} HOOK_TYPE;
OffPfn is the offset address of the hook callback function. This offset is relative to the base address of the module where the hook function is located.
Win32Thread is a pointer to the _ W32THREAD struct. Through this struct, you can obtain the process ID and thread ID of the hook. The struct is defined as follows.
Typedef struct _ W32THREAD
{
PVOID pEThread; // This pointer is used to obtain the process ID and thread ID
ULONG RefCount;
ULONG ptlW32;
ULONG pgdiDcattr;
ULONG pgdiBrushAttr;
ULONG pUMPDObjs;
ULONG pUMPDHeap;
ULONG dwEngAcquireCount;
ULONG extends mtable;
ULONG pUMPDObj;
PVOID ptl;
PVOID ppi; // This pointer is used to obtain the base address of a module.
} W32THREAD, * PW32THREAD;
The memory offset 0x01EC pointed to by the first parameter pEThread of the _ W32THREAD struct stores the process ID and thread ID respectively. Note that the pEThread Pointer Points to the kernel memory.
_ The Memory offset pointed to by the last parameter of the W32THREAD struct, 0xA8, is the address table of base addresses of all modules, the iMod Member of the _ HOOK_INFO struct identifies the base address of the module to which the hook belongs in this address table. (Each address occupies 4 bytes). Therefore, the ppi + 0xa8 + iMod * 4 is usually used to locate the module base address. Note that the ppi points to the kernel memory.
2. Implementation Details
First, write a program to enumerate the message hook handle and obtain the gui table. Its address is actually stored in a global variable of User32.dll. The UserRegisterWowHandlers function exported by this module will return the value of this global variable. Therefore, you only need to call this function to obtain the gui table. However, UserRegisterWowHandlers is an undisclosed function. If you are not sure about its function prototype, You Need To disassemble it to guess its prototype. The prototype obtained by the author after disassembly is as follows.
Typedef PSHAREDINFO (_ stdcall * USERREGISTERWOWHANDLERS) (PBYTE, PBYTE );
We only know that the two parameters are two pointers, but do not know the meaning of the two parameters, so we cannot construct reasonable parameters. If any construction parameter is passed in, the user32.dll module may be faulty. Therefore, the method to receive the returned value by calling this function is useless. The last three lines of code for this function in different operating systems are as follows.
2 K system: (5.0.2195.7032)
: 77E3565D B880D2E477 mov eax, 77E4D280
: 77E35662 C20800 ret 0008
XP system: (5.1.2600.2180)
: 77D535F5 B88000D777 mov eax, 77D70080
: 77D535FA 5D pop ebp
: 77D535FB C20800 ret 0008.
2003 system: (5.2.20.0.1830)
: 77E514D9 B8C024E777 mov eax, 77E724C0
: 77E514DE C9 leave
: 77E514DF C2080000 ret 0008.
We can see the commonalities. The code in the last and third lines of the function is to assign the value of the global variable that saves the gui table pointer to the Register EAX, as long as we can find a way to find this value. It can be seen that no matter which version of the function is implemented, there is C20800 code, meaning ret 0008. We can search for C20800 from the entry address of the UserRegisterWowHandlers function, find it, and then search for the B8 command. The four bytes after the B8 command are the data we need. The Code is as follows.
// Obtain the entry address of the UserRegisterWowHandlers function.
DWORD UserRegisterWowHandlers = (DWORD) GetProcAddress (LoadLibrary ("user32.dll"), "UserRegisterWowHandlers ");
PSHAREDINFO pGUITable; // Save the pointer of the GUITable address
For (DWORD I = UserRegisterWowHandlers; I <UserRegisterWowHandlers 1000; I ++)
{
If (* (USHORT *) I = 0x08c2) & * (BYTE *) (I + 2) = 0x00)
{// You have found the ret 0008 command and then searched for B8.
For (int j = I; j> UserRegisterWowHandlers; j --)
{// Find B8. The value stored in the last four bytes is the GUITable address.
If (* (BYTE *) j = 0xB8)
{
PGUITable = (PSHAREDINFO) * (DWORD *) (j + 1 );
Break;
}
} Break;
}
}
After obtaining the SHAREDINFO structure pointer, its member pServerInfo's member cHandleEntries is the total number of handles. Then, it cyclically traverses each handle and finds the message hook handle belonging to the specified module. The Code is as follows.
Int iHandleCount = pGUITable-> pServerInfo-> cHandleEntries;
HOOK_INFO HookInfo;
DWORD dwModuleBase;
Struct TINFO
{
DWORD dwProcessID;
DWORD dwThreadID;
};
Char cModuleName [256] = {0 };
For (I = 0; I <iHandleCount; I ++)
{// Determine whether the handle type is a message hook handle
If (pGUITable-> pHandleEntry [I]. bType = TYPE_HOOK)
{
DWORD dwValue = (DWORD) pGUITable-> pHandleEntry [I]. pObject;
// Obtain the message hook Kernel Object Data
GetKernelMemory (pGUITable-> pHandleEntry [I]. pObject, (BYTE *) & HookInfo, sizeof (HookInfo ));
W32THREAD w32thd;
If (GetKernelMemory (HookInfo. pWin32Thread, (BYTE *) & w32thd, sizeof (w32thd )))
{// Obtain the base address of the module where the hook function is located
If (! GetKernelMemory (PVOID) (ULONG) w32thd. ppi + 0xA8 + 4 * HookInfo. iMod ),
(BYTE *) & dwModuleBase, sizeof (dwModuleBase )))
{
Continue;
}
TINFO tInfo;
// Obtain the process ID and thread ID of the hook.
If (! GetKernelMemory (PVOID) (ULONG) w32thd. pEThread + 0x1ec ),
(BYTE *) & tInfo, sizeof (tInfo )))
{
Continue;
}
HANDLE hProcess = OpenProcess (PROCESS_ALL_ACCESS, FALSE, tInfo. dwProcessID );
If (hProcess = INVALID_HANDLE_VALUE)
{
Continue;
}
// Obtain the name of the module to which the hook function belongs Based on the module base address.
If (GetModuleFileNameEx (hProcess, (HMODULE) dwModuleBase, cModuleName, 256 ))
{
OutputDebugString (cModuleName );
OutputDebugString ("\ r \ n ");
}
}
}
}
With the above code, you can find the message hook handle of the virus DLL, and then call the UnhookWindowsHookEx function to uninstall the message hook.