使用UnhookWindowsHookEx函数

来源:互联网 发布:品质淘生活 淘宝达人 编辑:程序博客网 时间:2024/05/21 23:06

  一、消息钩子的概念1、基本概念Windows应用程序是基于消息驱动的,任何线程只要注册窗口类都会有一个消息队列用于接收用户输入的消息和系统消息。为了拦截消息,Windows提出了钩子的概念。钩子(Hook)是Windows消息处理机制中的一个监视点,钩子提供一个回调函数。当在某个程序中安装钩子后,它将监视该程序的消息,在指定消息还没到达窗口之前钩子程序先捕获这个消息。这样就有机会对此消息进行过滤,或者对Windows消息实现监控。

  2、分类消息钩子分为局部钩子和全局钩子。局部钩子是指仅拦截指定一个进程的指定消息,全局钩子将拦截系统中所有进程的指定消息。

  3、实现步骤使用钩子技术拦截消息通常分为如下几个步骤:设置钩子回调函数;(拦截到消息后所调用的函数)

  安装钩子;(使用SetWindowsHookEx函数)

  卸载钩子。(使用UnhookWindowsHookEx函数)

  4、功能利用消息钩子可以实现特效界面、同步消息、监控消息、自启动等功效。

  二、病毒对消息钩子技术的利用计算机病毒经常利用消息钩子实现两种功能:1、监控用户按键,盗取用户信息。

  这样的病毒会启动一个常驻内存的EXE病毒进程,然后安装一个全局键盘消息钩子,钩子回调函数位于病毒进程中,这样系统中任何有按键操作的进程,其按键详细信息都会被病毒进程拦截记录。

  2、自启动这样的病毒会将钩子回调函数放在一个DLL文件中,然后安装一个全局消息(容易触发的消息,如WH_CBT、WH_GETMESSAGE等)钩子,这样凡响应该消息的进程都会自动加载病毒的DLL,病毒也就跟着自动运行了。

  三、消息钩子病毒的对抗技术(重点)

  1、对抗技术原理对付消息钩子病毒方法很简单,只要将病毒安装的钩子卸载掉即可。(注意:对于系统中许多进程已经因为全局钩子而加载了病毒DLL的情况,并不需要去卸载这些DLL,只要安装的消息钩子被卸载那么对应的DLL也都会被在这些进程中自动卸载。)卸载钩子有两种方法:(1)、结束掉安装钩子的进程将设置钩子的进程结束,进程在退出之前会自行卸载掉该进程安装的所有消息钩子。这种方法很适合对付监控用户按键的病毒。

  (2)、获得消息钩子句柄,然后调用UnhookWindowsHookEx函数即可将消息钩子卸载。

  如果病毒单独启动了一个病毒进程安装了一个全局消息钩子,然后就常驻内存。这时我们将这个病毒进程结束掉即可。但是如果病毒在系统进程中注入代码而安装的钩子,这样钩子句柄就位于系统进程中,我们不可以结束系统进程,这时就只能获取这个消息钩子句柄,然后调用函数卸载。

  2、对抗技术实现细节对于结束掉安装钩子进程从而卸载病毒消息钩子的方法很容易实现,只要找到病毒进程结束即可。而对于获取病毒消息钩子句柄,然后调用函数卸载钩子的方法比较复杂,也是本文重点讨论的内容,将在下一个标题中详细介绍。

  四、查找病毒消息钩子句柄然后卸载的方法实现(重点、难点)

  1、实现原理分析系统会将所有安装的钩子句柄保存在内核中,要查找病毒安装的消息钩子句柄,我们要枚举所有的消息钩子句柄。如何枚举稍后讲解,还要解决一个问题,就是在枚举过程中,我们怎么知道哪个句柄是病毒安装的呢?

  通过分析病毒样本我们通常可以得到病毒安装钩子就是为了令其他合法进程加载病毒DLL,所以它会将钩子回调函数写在该DLL中。在枚举消息钩子句柄时,同时也可以得到该句柄所对应的回调函数所属的DLL模块,根据这个DLL模块是不是病毒的DLL模块即可找到病毒的消息钩子句柄,最后将其卸载即可。

  关于如何枚举系统消息钩子句柄,对于不同的操作系统方法大不相同,这里介绍一种用户层读内存的方法,此方法仅在2000/XP系统下可用。

  在2000/XP系统下有一个Windows用户界面相关的应用程序接口User32.dll.它用于包括Windows窗口处理,基本用户界面等特性,如创建窗口和发送消息。当它被加载到内存后,它保存了所有Windows窗口、消息相关的句柄,其中就包括消息钩子句柄。这些句柄被保存在一块共享内存段中,通常称为R3层的GUI TABLE.所以只要我们找到GUI TABLE,然后在其中的句柄中筛选出消息钩子句柄。GUI TABLE这块内存段可以被所有进程空间访问。GUI TABLE被定义成如下结构:typedef struct tagSHAREDINFO { struct tagSERVERINFO *pServerInfo; //指向tagSERVERINFO结构的指针struct _HANDLEENTRY *pHandleEntry; // 指向句柄表struct tagDISPLAYINFO *pDispInfo; //指向tagDISPLAYINFO结构的指针ULONG ulSharedDelta;LPWSTR pszDllList;} SHAREDINFO, *PSHAREDINFO;tagSHAREDINFO结构体的第一个成员pServerInfo所指向的tagSERVERINFO结构体定义如下。

  typedef struct tagSERVERINFO { short wRIPFlags ;short wSRVIFlags ;short wRIPPID ;short wRIPError ;ULONG cHandleEntries; //句柄表中句柄的个数}SERVERINFO,*PSERVERINFO;可以看出通过tagSERVERINFO结构的cHandleEntries成员即可得到tagSHAREDINFO结构的pHandleEntry成员所指向的句柄表中的句柄数。

  tagSHAREDINFO结构体的第二个成员pHandleEntry是指向_HANDLEENTRY结构体数组起始地址的指针,该数组的一个成员对应一个句柄。句柄结构体_HANDLEENTRY定义如下。

  typedef struct _HANDLEENTRY{ PVOID pObject; //指向句柄所对应的内核对象ULONG pOwner;BYTE bType; //句柄的类型BYTE bFlags;short wUniq;}HANDLEENTRY,*PHANDLEENTRY;_HANDLEENTRY结构体成员bType是句柄的类型,通过该变量的判断可以筛选消息钩子句柄。User32中保存的句柄类型通常有如下种类。

  typedef enum _HANDLE_TYPE { TYPE_FREE = 0,TYPE_WINDOW = 1 ,TYPE_MENU = 2, //菜单句柄TYPE_CURSOR = 3, //光标句柄TYPE_SETWINDOWPOS = 4,TYPE_HOOK = 5, //消息钩子句柄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;_HANDLEENTRY结构体的成员pObject是指向句柄对应的内核对象的指针。

  这样只要通过pObject就可以得到句柄的详细信息(其中包括创建进程,线程、回调函数等信息),通过bType就可以的值句柄的类型。

  _HANDLEENTRY结构体的其他成员可以忽略不看。

  (知识要点补充:如何在用户层程序中读取内核内存)

  需要注意的是,pObject指针指向的是内核内存,不可以在用户层直接访问内核内存。后面还有些地方也同样是内核内存,需要加以注意。应该把内核内存的数据读取到用户层内存才可以访问。且不可以直接访问,毕竟不是在驱动中。

  在用户层读取内核内存使用ZwSystemDebugControl函数,它是一个Native API.其原型如下。

  NTSYSAPI NTSTATUS NTAPI ZwSystemDebugControl(IN DEBUG_CONTROL_CODE ControlCode,//控制代码IN PVOID InputBuffer OPTIONAL, //输入内存IN ULONG InputBufferLength, //输入内存长度OUT PVOID OutputBuffer OPTIONAL, //输出内存IN ULONG OutputBufferLength, //输出内存长度OUT PULONG ReturnLength OPTIONAL //实际输出的长度);ZwSystemDebugControl函数可以用于读/写内核空间、读/写MSR、读/写物理内存、读/写IO端口、读/写总线数据、KdVersionBlock等。由第一个参数ControlCode控制其功能,可以取如下枚举值。

  typedef enum _SYSDBG_COMMAND { //以下5个在Windows NT各个版本上都有SysDbgGetTraceInformation = 1,SysDbgSetInternalBreakpoint = 2,SysDbgSetSpecialCall = 3,SysDbgClearSpecialCalls = 4,SysDbgQuerySpecialCalls = 5,// 以下是NT 5.1 新增的SysDbgDbgBreakPointWithStatus = 6,//获取KdVersionBlock SysDbgSysGetVersion = 7,//从内核空间复制到用户空间,或者从用户空间复制到用户空间//但是不能从用户空间复制到内核空间SysDbgCopyMemoryChunks_0 = 8,//SysDbgReadVirtualMemory = 8,//从用户空间复制到内核空间,或者从用户空间复制到用户空间//但是不能从内核空间复制到用户空间SysDbgCopyMemoryChunks_1 = 9,//SysDbgWriteVirtualMemory = 9,//从物理地址复制到用户空间,不能写到内核空间SysDbgCopyMemoryChunks_2 = 10,//SysDbgReadVirtualMemory = 10,//从用户空间复制到物理地址,不能读取内核空间SysDbgCopyMemoryChunks_3 = 11,//SysDbgWriteVirtualMemory = 11,//读/写处理器相关控制块SysDbgSysReadControlSpace = 12,SysDbgSysWriteControlSpace = 13,//读/写端口SysDbgSysReadIoSpace = 14,SysDbgSysWriteIoSpace = 15,//分别调用RDMSR@4和_WRMSR@12 SysDbgSysReadMsr = 16,SysDbgSysWriteMsr = 17,//读/写总线数据SysDbgSysReadBusData = 18,SysDbgSysWriteBusData = 19,SysDbgSysCheckLowMemory = 20,// 以下是NT 5.2 新增的//分别调用_KdEnableDebugger@0和_KdDisableDebugger@0 SysDbgEnableDebugger = 21,SysDbgDisableDebugger = 22,//获取和设置一些调试相关的变量SysDbgGetAutoEnableOnEvent = 23,SysDbgSetAutoEnableOnEvent = 24,SysDbgGetPitchDebugger = 25,SysDbgSetDbgPrintBufferSize = 26,SysDbgGetIgnoreUmExceptions = 27,SysDbgSetIgnoreUmExceptions = 28 } SYSDBG_COMMAND, *PSYSDBG_COMMAND;我们这里要读取内核内存,所以参数ControlCode应取值为SysDbgReadVirtualMemory.当ControlCode取值为SysDbgReadVirtualMemory时,ZwSystemDebugControl函数的第4个参数和第5个参数被忽略,使用时传入0即可。第二个参数InputBuffer是一个指向结构体_MEMORY_CHUNKS的指针,该结构体定义如下。

  typedef struct _MEMORY_CHUNKS { ULONG Address; //内核内存地址指针(要读的数据)

  PVOID Data; //用户层内存地址指针(存放读出的数据)

  ULONG Length; //读取的长度}MEMORY_CHUNKS, *PMEMORY_CHUNKS;第三个参数InputBufferLength是_MEMORY_CHUNKS结构体的大小。使用sizeof运算符得到即可。

  SysDbgReadVirtualMemory函数执行成功将返回0.否则返回错误代码。

  为了方便使用,我们可以封装一个读取内核内存的函数GetKernelMemory,实现如下:#define SysDbgReadVirtualMemory 8 //定义ZwSystemDebugControl函数指针类型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; //内核内存地址mc.pData = pBuffer;//用户层内存地址mc.Length = uLength; //读取内存的长度ULONG st = -1 ;//获得ZwSystemDebugControl函数地址ZWSYSTEMDEBUGCONTROL ZwSystemDebugControl = (ZWSYSTEMDEBUGCONTROL) GetProcAddress(GetModuleHandle("ntdll.dll"), "ZwSystemDebugControl");//读取内核内存数据到用户层st = ZwSystemDebugControl(SysDbgReadVirtualMemory, &mc, sizeof(mc), 0, 0, &uReaded);return st == 0;}

  对于不同类型的句柄,其内核对象所属内存对应的结构体不同,对于消息钩子句柄,它的内核对象所属内存对应的结构体实际上是_HOOK_INFO类型,其定义如下。

  typedef struct _HOOK_INFO { HANDLE hHandle; //钩子的句柄DWORD Unknown1;PVOID Win32Thread; //一个指向 win32k!_W32THREAD 结构体的指针PVOID Unknown2;PVOID SelfHook; //指向结构体的首地址PVOID NextHook; //指向下一个钩子结构体int iHookType; //钩子的类型。

  DWORD OffPfn; //钩子函数的地址偏移,相对于所在模块的偏移int iHookFlags; //钩子标志int iMod; //钩子函数做在模块的索引号码,利用它可以得到模块基址PVOID Win32ThreadHooked; //被钩的线程结构指针} HOOK_INFO,*PHOOK_INFO;由上可以看出,得到钩子内核对象数据后,该数据对应HOOK_INFO结构体信息。其中:hHandle是钩子句柄,使用它就可以卸载钩子。

  iHookType是钩子的类型,消息钩子类型定义如下。

  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是钩子回调函数的偏移地址,该偏移地址是相对于钩子函数所在模块基址的偏移。


0 0
原创粉丝点击