Hook Windows NT

来源:互联网 发布:python j =1 编辑:程序博客网 时间:2024/05/05 21:53



To be continue...

Hook API2013年11月16日功能追溯Windows编程的最简单的程序结构,只需要一个消息环。以下展示一个基本的Win32程序,它在开发执行时,会播放Windows 7启动时的使用的音响。#pragma 是VC平台的专用指令,使用它来替代手动设置工程属性,免去手动添加链接所需的LIB库。和普通控制台程序    #pragma comment(lib,"winmm.lib")    #include     #include          using namespace std;         static TCHAR szTitle[] = TEXT("GUI App");    static TCHAR szSound[] = TEXT( "c:\\windows\\media\\Windows Logon Sound.wav" );         HWND hWin;    HINSTANCE hAPP;         LRESULT CALLBACK circle( HWND, UINT, WPARAM, LPARAM );         int WINAPI WinMain(HINSTANCE hApp, HINSTANCE hPre, LPSTR lpLine, int iCmd )    {        MSG msg;        WNDCLASS winClass;             hAPP = hApp;             winClass.style                  = CS_HREDRAW | CS_VREDRAW;        winClass.lpfnWndProc  = circle;        winClass.hInstance        = hApp;        winClass.hIcon                = LoadIcon( NULL, IDI_APPLICATION );        winClass.hCursor            = LoadCursor( NULL,IDC_CROSS );        winClass.hbrBackground = (HBRUSH) GetStockObject( WHITE_BRUSH );        winClass.lpszClassName = szTitle;        winClass.lpszMenuName = NULL;        winClass.cbClsExtra          = 0;        winClass.cbWndExtra       = 0;             if( !RegisterClass( &winClass ) ){            MessageBox( NULL, TEXT("I need a Window!"), szTitle, MB_ICONERROR );            return 0;        }             hWin = CreateWindow(                        szTitle, szTitle, //window class and caption                       WS_OVERLAPPEDWINDOW,                       CW_USEDEFAULT,  CW_USEDEFAULT, // position x, y                       CW_USEDEFAULT,  CW_USEDEFAULT, // size width, height                       NULL, // parent windows handle                       NULL, // menu handle                       hApp,                       NULL); //creation parameters             ShowWindow( hWin, iCmd );        UpdateWindow( hWin );             while(GetMessage( &msg, NULL, 0, 0 ) ){            TranslateMessage( &msg );            DispatchMessage( &msg );        }             return msg.wParam;         }         LRESULT CALLBACK circle( HWND hWin, UINT iMsg, WPARAM wp, LPARAM lp ){        HDC         hdc ;        PAINTSTRUCT ps ;        RECT        rect ;             switch( iMsg ){        case WM_CREATE:            PlaySound( szSound, NULL, SND_FILENAME | SND_ASYNC );            return 0;        case WM_DESTROY:            PostQuitMessage(0);            return 0;        case WM_PAINT:            hdc = BeginPaint( hWin, &ps);            GetClientRect( hWin, &rect );            string text =  "Appication API HOOK";            DrawText( hdc, text.c_str(), -1, &rect, DT_SINGLELINE | DT_CENTER | DT_VCENTER );            EndPaint( hWin, &ps);            return 0;        }             return DefWindowProc( hWin, iMsg, wp, lp);    }上面的程序结构和普通的DOS程序大体相同,只是加入了Windows平台的各种对象。为了新建一个GUI界面,首先设置了一个窗口类winClass,然后通过RegisterClass来注册到系统内,最后通过CreateWindow完成窗口的创建并用ShowWindow将其显示出来程序的结束同样是以WinMain的结束而完成的。不同的地方就在while循环,这个就是Windows系统特有的消息环。Windows通过消息机制来管理着系统设备的各种事件,如鼠标移动了,键盘被按下了,网络连接上了,或者是停电了等等。每一个事件有伴随着消息的传递,每条消息按先后缓急顺序被存储到一个称为消息队列Queue的地方。用户程序在消息环调用GetMessage方法时,就会进入一个不消耗CUP的等待状态,Windows在消息队列中发现一个属于当前用户程序的消息时,就会发送给用户程序,这样GetMessage就取得消息并返回到用户程序的消息环。这时最重要的事情就是对消息进行向应,这就是Windows编程要做的事。在前面,创建窗口时,给GreateWindow传入了一个窗口类,这个类的lpfnWndProc成员设置了一个引用circle的函数指针,这就是程序用来响应消息的方法,称为窗口过程。当消息环中调用DispatchMessage方法时,就会将消息传递给窗口过程进行处理。而在窗口过程中,将对不同的消息进行选择性处理,如程序完成初始化时的WM_CREATE消息,程序将播放一段乐音来响应。当用户通过鼠标点击窗口右上角的关闭按钮时,将产生一个WM_DESTORY消息,这就是一个关闭程序的意图。窗口过程在接收到这个关闭程序的消息时,就以调用PostQuitMesage来响应,它就是用来告知系统,程序需要关闭,不需要再做处理其它消息了。然后系统给消息环发送一个空消息,以使用while终止循环,最后WinMain即,程序结束。注意,和PostQuitMessage相似的PostMessage可以用来向程序发送任意消息,它只负责将消息放到消息队列中,然后直接返回,相应的SendMessage要等到受到消息处理的返回码后才继续。WM_PAINT是Windows窗口系统中一条重要的消息,应用程序通过处理该消息实现在窗口上的绘制工作。系统会在多个不同的时机发送WM_PAINT消息:当第一次创建一个窗口时,当改变窗口的大小时,当把窗口从另一个窗口背后移出时,当最大化或最小化窗口时,等等,这些动作都是由系统管理的,应用只是被动地接收该消息,在消息处理函数中进行绘制操作。大多数的时候应用也需要能够主动引发窗口中的绘制操作,比如当窗口显示的数据改变的时候,这一般是通过InvalidateRect和InvalidateRgn函数来完成的,前者把指定的区域加到窗口的待更新区域Update Region中,当应用的消息队列没有其他消息且待更新区域不为空时,系统就会自动产生WM_PAINT消息。待更新区域是用一个RECT结构表示的,如下定义:    typedef struct _RECT {      LONG left; // 窗口左边开始计算的像素位置      LONG top; // 窗口的顶部计算的像素位置      LONG right;      LONG bottom;    } RECT, *PRECT;系统为什么不在调用Invalidate时发送WM_PAINT消息呢?又为什么非要等应用消息队列为空时才发送WM_PAINT消息呢?这是因为系统把在窗口中的绘制操作当作一种低优先级的操作,于是尽可能地推后做。待更新区域区域会被累加起来,然后在一个WM_PAINT消息中一次得到更新,不仅能避免多次重复地更新同一区域,也优化了应用的更新操作。这种通过InvalidateRect和InvalidateRgn来使窗口区域无效,依赖于系统在合适的时机发送WM_PAINT消息的机制实际上是一种异步工作方式,也就是说,在无效化窗口区域和发送WM_PAINT消息之间是有延迟的;有时候这种延迟并不是我们希望的,这时我们当然可以在无效化窗口区域后利用SendMessage发送一条WM_PAINT消息来强制立即重画,但不如使用Windows GDI API:UpdateWindow和RedrawWindow或者使用WM_PRINT 、WM_PRINTCLIENT消息。BeginPaint和WM_PAINT消息紧密相关。试一试在WM_PAINT处理函数中不写BeginPaint会怎样?程序会像进入了一个死循环一样达到惊人的CPU占用率,因为程序总有处理不完的WM_PAINT消息。其实BeginPaint的一个作用就是把待更新区域清空。BeginPaint和WM_ERASEBKGND消息也有关系。当窗口的待更新区域被标志为需要擦除背景时,BeginPaint会发送WM_ERASEBKGND消息来重画背景,同时在其返回信息里有一个标志表明窗口背景是否被重画过。当我们用InvalidateRect和InvalidateRgn来把指定区域加到待更新区域中时,可以设置该区域是否需要被擦除背景,这样下一个BeginPaint就知道是否需要发送WM_ERASEBKGND消息了。要注意的是,BeginPaint只能在WM_PAINT处理函数中使用,在其它消息下无法实现窗口重绘功能。例如可以使用以下方法来产生一个待更新区,以强制刷新窗口。    RECT rect;    GetClientRect( hWin, &rect );    InvalidateRect( hWin, &rect, TRUE);    UpdateWindow( hWin );Windows程序就是这样一个基本的工作过程,然而,从DOS开始,程序开发就有个传统:程序需求对系统功能的监视和响应以实现程序的功能。这也是程序开发的基本需求,Windows 3.x的时代HOOK已经普遍应用。HOOK根本上来讲就是一处提供给开发者嵌入自定义例程以实现程序功能的场所。目前普遍将HOOK翻译为钩子的做法其实不太恰当,如果按照我在做Wordpress二次开发的经验,我更愿意将HOOK称作过滤器 Filter,从本质上讲Windows的HOOK和Wordpress的Filter是一致的。它们所起的作用就像是水管中间接上的一个过滤器,把某些东西过滤出来。事实上,1993年Kyle Marsh在MSDN上发表过一篇文章Win32 Hooks,里就是将钩子回调过程为过滤器函数,这篇文章可以在MSDN的技术文章栏目中找到。实现程序功能代码嵌入的方法有各种形式,如下:    使用注册表注入,将程序注册到:HKLM/Software/Microsoft/Windows NT/CurrentVersion/Windows/AppInit_DLLs。这AppInit_DLLs这个键记录了一个或一组逗号分隔的DLL文件,当一个使用USER32.DLL的程序载入时,就会透过LoadLibrary()API依次加载AppInit_DLLs指定的链接库。这种方法只在NT架构系统且2K+版本上才有效。    使用API注入,通过SetWindowsHookEx()来注册HOOK处理程序,使用CallNextHookEx() 来保持钩子链正常工作,退出时,使用UnhookWindowsHookEx()卸载钩子。    通过修改PE文件注入,PE程序文件中有一个导入地址表IAT Import Address Table,它记录了程序要调用的外部函数的地址,改PE文件的IAT,使之指向自己的代码,这样EXE/DLL在调用系统API的时候便会调用你自己的函数。要注意,Windows API均有两个版本:Ansi和Unicode。例如获取程序标题的GetWindowTextAPI实际上只是一个宏,根据编译条件UNICODE来决定是调用GetWindowTextA还是GetWindowTextW。在NT系统下所有ANSI版本API会转换成UNICODE版本。    通过遥距线程注入,使用CreateRemoteThread()方法可以创建一个线程,将要注入的程序透过LPTHREAD_START_ROUTINE参数传递给创建的线程,但是程序要先使用ThreadProc()API包装。这种方法是Jeffrey Ritcher提出来的,他写的文档也很完善,不过也只在NT架构系统且2K+版本上才有效。    透过BHO插件注入,BHO是Browser Helper Objects,只在IE浏览器中使用。IE运行时会加载所有实现IObjectWithSite接口的COM组件。    通过Office插件注入,和BHO方式相似,使用范围限制在Office内。本文主要涉及API注入、PE文件注入及遥距线程注入。API钩子程序结构钩子,按习惯,大伙都叫它为钩了。在伴随Windows系统的发展中,钩子也发展出好多的分类,有系统层次的,有应用程序层次的,有核心层次的,有处理键盘消息的,有处理系统日志的,有监视API调用的,各式各样。对于一个钩子程序,按上面水管过滤的理解,首先就需要安装一个钩子,主钩子在系统中起作用;然后程序按功能逻辑进行处理,这需要一个钩子回调函数hook procedure;完了,程序要退出,就要清场,把钩子回收。而每条水管可以安装多个不同的过滤器,同理钩子不也可以有多个,因此组成了一条链,系统将按注册顺序来调用。先调用线程钩子,然后调用系统钩子,后注册的先调用。对于用户层次的钩子,只需要一个可以安装和回收钩子的程序就可以了,连带程序功能都在一个程序内实现。当钩子需要处理Windows内核消息时,就需要钩子运行于内核模式,这时就需要可以开发内核程序的DDK,它才是用来开发内核应用的,像驱动程序这类一样,而且必需将钩子程序编译到DLL程序中。只Win16程序才允许在程序内容注册一个系统钩子。先来看看SetWindowsHookEx原型:    HHOOK SetWindowsHookEx(      int idHook,        // hook type      HOOKPROC lpfn,     // hook procedure      HINSTANCE hMod,    // handle to application instance      DWORD dwThreadId   // thread identifier    );hMod 指定钩子回调函数所在DLL的实例句柄。如果安装的是局部钩子的话,由于局部钩子的回调函数并不需要放在动态链接库中,这时这个参数就使用NULL。dwThreadID是安装钩子后想监控的线程的ID号。该参数可以决定钩子是线程钩子局部范围的还是系统钩子全局范围。如果参数指定的是自己进程中的某个线程ID号,那么该钩子是一个局部钩子;如果指定的线程ID是另一个进程中某个线程的ID,那么安装的钩子是一个局部的远程钩子;如果想要安装系统范围的全局钩子的话,可以将这个参数指定为NULL,这样钩子就会被解释成系统范围的,可以用来监控所有的进程及它们的线程。由于32-bit钩子不能注入到64-bit的进程,反之亦然,在x64平台上,需要准备x64版本的钩子程序,才能正常得到系统支持。使用SetWindowsHookEx注册钩子时,如果回NULL,则表示注册失败,可以通过GetLastError()获取错误代码:返回代码含义ERROR_INVALID_HOOK_FILTER钩子代码无效。ERROR_INVALID_FILTER_PROC钩子函数无效。ERROR_HOOK_NEEDS_HMOD注册系统钩子使用了空hInstance参数,或者注册线程钩子的线程不存在。ERROR_GLOBAL_ONLY_HOOK以系统钩子方式注册了线程钩子。ERROR_INVALID_PARAMETER线程ID无效。ERROR_JOURNAL_HOOK_SETJOURNAL钩子已经注册。ERROR_MOD_NOT_FOUNDhInstance参数不是指向一个库。简而言之就是在模块列表中定位到不到指定模块。其它值出于安全,不允许或系统内存不足。下表展示了WINUSER.H定义的一些钩子的类型:钩子名称作用层次监视消息的类型和时机WH_CALLWNDPROC线程、系统应用于SendMessage函数调用时。WH_CALLWNDPROCRET线程、系统应用于SendMessage函数调用后。WH_CBT线程、系统当基于计算机的训练(CBT)事件发生时调用钩子函数WH_DEBUG线程、系统在系统调用其他钩子函数前执行的钩子,当然是除了WH_DEBUG了,不然会循环。WH_FOREGROUNDIDLE系统系统空闲钩子,当系统空闲的时候调用钩子函数,这样就可以在这里安排一些优先级很低的任务WH_GETMESSAGE线程、系统应用于GetMessage或PeekMessage函数执行后。WH_HARDWARE线程、系统每当调用GetMessage或PeekMessage函数时,如果从消息队列中得到的是非鼠标和键盘消息,则调用钩子函数WH_JOURNALRECORD系统日志记录钩子,用来记录发送给系统消息队列的所有消息WH_JOURNALPLAYBACK系统日志回放钩子,用来回放日志记录钩子记录的系统事件WH_KEYBOARD线程、系统每当调用GetMessage或PeekMessage函数时,如果从消息队列中得到的是WM_KEYUP或WM_KEYDOWN消息,则调用钩子函数WH_KEYBOARD_LL系统像Ctrl+alt+del 系统会先处理掉,WH_KEYBOARD没法截获,而WH_KEYBOARD_LL可以,但很容易引起挂起之类的问题,不过操作系统通过LowLevelHooksTimeout限时操作,超时就直接被忽略。WH_MOUSE线程、系统每当调用GetMessage或PeekMessage函数时,如果从消息队列中得到的是鼠标消息,则调用钩子函数WH_MOUSE_LL系统截获整个系统的鼠标事件消息。WH_MSGFILTER线程、系统应用于用户程序对对话框、菜单和滚动条的消息,先于程序行为。WH_SYSMSGFILTER系统同WH_MSGFILTER,应用于系统范围,影响更大。WH_SHELL线程、系统当Windows shell程序准备接收一些通知事件前调用钩子函数,如shell被激活和重画等日志记录钩子和日志回放钩子可以放在安装钩子的程序中,并不需要单独放在一个动态链接库中,因为它们是由Windows系统调用的钩子。程序内的线程级别钩子在前面熟悉了Win32程序的基本结构和消息环的作用,在此基础上进行应用应用程序级别的钩子开发相对会比较容易入手。为了直观地显示注册钩子不成功的原因,下面使用FormatMessage增加了一个显示错误信息的方法,此方法通过查询GetLastError返回的错误代码返回文字版本的信息,然后通过对话框显示出来。    void ShowErrorInfo(int Error){        LPVOID lpMsgBuf;        FormatMessage(            FORMAT_MESSAGE_ALLOCATE_BUFFER |            FORMAT_MESSAGE_FROM_SYSTEM |            FORMAT_MESSAGE_IGNORE_INSERTS,            NULL,            Error,            MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language            (LPTSTR) &lpMsgBuf,            0,            NULL        );        MessageBox( NULL, (LPCTSTR)lpMsgBuf, "Error", MB_OK | MB_ICONINFORMATION );    }按照前面对钩子应用的基本结构,这里要添加注册钩子HookStart和清除钩子HookDown的函数,以及钩子过程FilterFunc。现在就来实现最简单的当前程序级别的钩子,将文本最开始给出的Win32例子的窗口过程稍为修改一下,再添加上钩子功能得以下修改过的部分:    UINT COUNT;    HHOOK MyHook;         LRESULT CALLBACK FilterFunc( int nCode, WORD wp, DWORD lp) {        stringstream ss;        char text[MAXBYTE];        ss << szTitle << " -  " << ++COUNT << std::hex << " MSG:" << nCode             << std::hex << " wp:0x" << wp << " lp:0x" << lp << "\n";        ss.getline( text, MAXBYTE);        SetWindowText( hWin, text );        return CallNextHookEx( MyHook, nCode, wp, lp);    }         HHOOK HookStart(int idHook, HINSTANCE hd, DWORD td){        HOOKPROC hkp = reinterpret_cast( FilterFunc );        HHOOK hk = SetWindowsHookEx( idHook, hkp, hd, td );        if(hk==NULL){            UINT Error = GetLastError();            ShowErrorInfo(Error);        }        return hk;    }         void HookDown(HHOOK hook){        UnhookWindowsHookEx(hook);    }         LRESULT CALLBACK circle( HWND hWin, UINT iMsg, WPARAM wp, LPARAM lp ){        HDC         hdc ;        PAINTSTRUCT ps ;        RECT        rect ;             switch( iMsg ){        case WM_CREATE:            PlaySound( szSound, NULL, SND_FILENAME | SND_ASYNC );            MyHook = HookStart( WH_KEYBOARD, NULL,GetCurrentThreadId() );            return 0;        case WM_DESTROY:            PostQuitMessage(0);            HookDown( MyHook );            return 0;        case WM_PAINT:            hdc = BeginPaint( hWin, &ps);            GetClientRect( hWin, &rect );            string text =  "Appication API HOOK";            DrawText( hdc, text.c_str(), -1, &rect, DT_SINGLELINE | DT_CENTER | DT_VCENTER );            EndPaint( hWin, &ps);            return 0;        }             return DefWindowProc( hWin, iMsg, wp, lp);    }当按下键盘时,标题就会显示WPARAM和LPARAM参数,注意窗口过程中没有对按键消息进行处理,这是在钩子过程FilterFunc中处理的,如下图:下一步将要实现系统级别的钩子,这个程序将需要获取其它进程的线程ID以将DLL钩子注入线程,这里要先热身,介绍几个相关方法的原型:    HWND SetCapture(  HWND hWnd );  // 捕捉鼠标事件    BOOL ReleaseCapture(VOID);  // 释放鼠标    HWND WindowFromPoint(  POINT Point  );  // 通过鼠标位置来获取窗口句柄         HWND FindWindow(  // 通过窗口类查找窗体      LPCTSTR lpClassName,  // 窗口类名,NULL结束字符串      LPCTSTR lpWindowName  // 窗口标题过滤    ); // 例如记事本Notepad、写字板WordPadClass、控制台ConsoleWindowClass         HWND FindWindowEx(  // 升级版FindWindow      HWND hwndParent,      // handle to parent window      HWND hwndChildAfter,  // handle to child window      LPCTSTR lpszClass,    // class name      LPCTSTR lpszWindow    // window name    );         BOOL EnumWindows(  // 通过枚举查找窗口,回调函数原型随后      WNDENUMPROC lpEnumFunc,  // callback function      LPARAM lParam            // application-defined value    );    BOOL EnumChildWindows(      HWND hWndParent,         // handle to parent window      WNDENUMPROC lpEnumFunc,  // pointer to callback function      LPARAM lParam            // application-defined value    );         BOOL CALLBACK EnumWindowsProc( // 返回False主动停止窗口枚举      HWND hwnd,      // handle to parent window      LPARAM lParam   // application-defined value    );         DWORD GetWindowThreadProcessId(  // 通过窗口句柄来获取线程ID      HWND hWnd,             // handle to window      LPDWORD lpdwProcessId  // process identifier    );         HANDLE GetCurrentProcess(VOID) // 一组获取当前进程、线程及基ID的API    DWORD GetCurrentProcessId(VOID)    DWORD GetCurrentThreadId(VOID)    HANDLE GetCurrentThread(VOID)         int GetClassName(  // 获取窗口类名      HWND hWnd,           // handle to window      LPTSTR lpClassName,  // class name      int nMaxCount        // size of class name buffer    );         HANDLE OpenProcess( // 通过线程ID获取线程句柄      DWORD dwDesiredAccess,  // 访问许可标志,见随后常数定义      BOOL bInheritHandle,    // handle inheritance option      DWORD dwProcessId       // process identifier    );         #define PROCESS_TERMINATE         (0x0001)    // 允许终止进程。    #define PROCESS_CREATE_THREAD     (0x0002)  // 允许创建远程线程。    #define PROCESS_VM_OPERATION      (0x0008) // 许可WriteProcessMemory、VirtualProtectEx修改地址空间。    #define PROCESS_VM_READ           (0x0010)    // 允许对进程的地址空间进行读操作。    #define PROCESS_VM_WRITE          (0x0020)    // 允许对进程的地址空间进行写操作。    #define PROCESS_DUP_HANDLE        (0x0040)  // 允许进程句柄被复制。    #define PROCESS_CREATE_PROCESS    (0x0080)  //    #define PROCESS_SET_QUOTA         (0x0100)    //    #define PROCESS_SET_INFORMATION   (0x0200)  // 许可SetPriorityClass函数设置进程的优先级。    #define PROCESS_QUERY_INFORMATION (0x0400)  //许可GetExitCodeProcess查询进程的退出码            // 或使用GetPriorityClass函数查询进程的优先级。    #define PROCESS_ALL_ACCESS  (STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE | 0xFFF)         FARPROC GetProcAddress( // 获取指定模块的函数地址      HMODULE hModule,    // handle to DLL module      LPCSTR lpProcName   // name of function    );    HINSTANCE LoadLibrary( // 手动装载程序模块      LPCTSTR lpLibFileName   // address of filename of executable module    );    HINSTANCE LoadLibraryEx(      LPCTSTR lpLibFileName,  // points to name of executable module      HANDLE hFile,           // reserved, must be NULL      DWORD dwFlags           // 入口点处理标志,见随后的常数定义    );    #define DONT_RESOLVE_DLL_REFERENCES     0x00000001    #define LOAD_LIBRARY_AS_DATAFILE        0x00000002  // 不执行入口,相当LoadResource    #define LOAD_WITH_ALTERED_SEARCH_PATH   0x00000008         DWORD GetModuleFileName( // 获取模块文件位置      HMODULE hModule,    // handle to module to find filename for      LPTSTR lpFilename,  // pointer to buffer to receive module path      DWORD nSize         // size of buffer, in characters    );         UINT_PTR SetTimer( // 设置定时器      HWND hWnd,              // handle to window      UINT_PTR nIDEvent,      // your timer identifier      UINT uElapse,           // time-out value in milliseconds      TIMERPROC lpTimerFunc   // timer procedure    );    BOOL KillTimer(      HWND hWnd,          // handle to window      UINT_PTR uIDEvent   // timer identifier    );    VOID CALLBACK TimerProc(      HWND hwnd,         // handle to window      UINT uMsg,         // WM_TIMER message      UINT_PTR idEvent,  // timer identifier      DWORD dwTime       // current system time    );先来热身一下,这是一个通过枚举查找窗口类,标题、句柄等信息的控制台程序:    #include     #include     #include          using namespace std;         BOOL CALLBACK EnumProc( HWND hwnd, LPARAM lParam ){        char buf[MAXWORD];        char bufClass[MAXWORD];        DWORD thread;             GetWindowTextA( hwnd, buf, MAXWORD );        GetClassNameA( hwnd, bufClass, MAXWORD );        thread = GetWindowThreadProcessId( hwnd, NULL );             string title(buf);        if(            title==string("Default IME") ||            title==string("Default IME") ||            title==string("MSCTFIME UI")        ) return true;        cout << "Found:0x" << hwnd << " THREAD:0x" << thread               << " CLASS:" << bufClass << " TITLE:" << buf << endl;        return true;    }         void main(){        int (WINAPI *cb)() = reinterpret_cast(EnumProc);        BOOL isok = EnumWindows( cb,  NULL);        cout << "exit:" << isok << endl;    }这一个程序可以通过ShowWindow来让程序在任务栏显示或隐藏。    #include     #include     #include          using namespace std;         BOOL Toggle(const char *name,  int state=SW_SHOW){        HWND hwin = FindWindowA( name, NULL );        return ShowWindow( hwin, state );    }         void main(){        cout << "Type exit to close." << endl;        for(;;){            string s;            cin >> s;            if(s=="show"){                string name;                cin >> name;                if( name.length()>0 ) Toggle( name.c_str(), SW_SHOW );            }else if( s=="hide"){                string name;                cin >> name;                if( name.length()>0 ) Toggle( name.c_str(), SW_HIDE );            }else if( s=="exit") break;        }    }系统钩子接下来开始正题,这次要建立系统级别的钩子。接下来需要深入一层使用系统钩子,这样就要新建一个DLL工程了,命名为hooksrv,这样工程就会生成hooksrv.dll。现在就来定义hooksrv.h头文件,注意前面的代码文件中HookStart和HookDown两个方法是要导出供程序调用的,这也是DLL程序的基本要求,那么导出标记EXPORT就在下面这个头文件中按格式定义。导出标记可以有三种方式,源代码中的 __declspec(dllexport) 关键字,.def 文件中的 EXPORTS 语句,LINK 命令中的 /EXPORT 规范。注意,使用 .def 文件从 DLL 中导出变量时,不需要在变量上指定导出标记。但是,在使用 DLL 的代码上,仍必须使用函数的导出声明,这个声明通常伴随在DLL的头文件内。头文件同时定义了一个WM_HOOK消息,这个消息将在消息环中使用。因为这是个自定义消息,所以只能使用Windows要求的比WM_USER大的值,比WM_USER小的消息值只供系统专用。    /*********** hooksrv.h **********/    #include          #define EXPORT __declspec(dllexport)    // #define IMPORT extern _declspec(dllimport)    // IMPORT int hookCode; // 此两行用于程序导入DLL变量         EXPORT HHOOK HookStart( int hookID, HWND hWindow, DWORD dwThreadId );    EXPORT BOOL HookDown( HHOOK );         #define WM_HOOK WM_USER + 1     这里使用了Visual Studio 97,编译输出是x86架构的,如果要使用x64平台,请使用Visual Studio 2005的x64编译器。接下来要设置一下工程属性。在Post-build step新建一个命令动作,在DLL完成编译时用来将DLL拷贝到程序目录。假设我们的主程序在hookcross目录下,并且和hooksrv工作目录同级,那么对于DEBUG版本就可以使用以下命令,这样编译DLL时,就会自动拷贝到程序的工作目录下了:    copy debug\hooksrv.dll ..\hookcross\debug\再来实现钩子的基本功能代码部分hooksrv.cpp,其中HookShared是一个共享段,RWS标记它是Read、Write和Shared的共享段,它是可选部分,这时用来展示如果实现DLL共享数据段。这样DLL注入到不同的程序后,可以透过共享段来进行数据共享。否则,在Win32受保护的程序内存空间下,程序间是不可以相互直接获取数据的。当然可以变通地使用传输只读数据的WM_COPYDATA消息,网络传输,或用CreateFileMapping内存映射文件实现共享内存,进程内存读写ReadProcessMemory及WriteProcessMemory,又或者使用剪切板SetClipboardData等手段。DLL共享数据时,一定要使用静态始化,否则编译时会因被放到未初始化数据段而失效,初始化为0的值也会当作未初始化数据而存放于.bss段中。另外,DLL 本身有可能加载到每个进程的虚拟地址空间中的不同地址。因此具有指向 DLL 中的函数或指向其他共享变量的指针是不安全的。注意永远不要将特定于进程的信息存储在共享数据段中,这里只是展示如何使用DLL共享数据,在逻辑上来讲这样共享hookCode做是不正确的,因为它是属于被注入钩子的进程的。而前导的EXPORT是导出标记,定义在头文件中,导出DLL变量是为了方便在程序中访问,这些导出也可以通过模块定义文件DEF来标记。特别说明一下,导出变量和共享变量是两个概念,共享变量是所有进程加载DLL时,共享变量只有一个副本,任何修改都会在其它进程上反映。    #include "hooksrv.h"         #pragma data_seg("HookShared")    HHOOK hHook = NULL;    HWND hWnd = NULL;    EXPORT int hookCode = 0xff;    #pragma data_seg()    #pragma comment(linker, "/SECTION:HookShared,RWS")         HINSTANCE hMod;         EXPORT BOOL APIENTRY DllMain( HANDLE hModule,                           DWORD  fdwreason, LPVOID lpReserved )    {        hMod = (HINSTANCE)hModule;        return TRUE;    }         void ShowErrorInfo(int Error){ } // 函数体在前面已经给出,这里省略。         LRESULT WINAPI HookProc(int code, WPARAM wParam, LPARAM lParam){        hookCode = code;        SendMessage( hWnd, WM_HOOK, wParam, lParam );        return CallNextHookEx(hHook, code, wParam, lParam);    }         EXPORT HHOOK HookStart(int hookID, HWND hWindow, DWORD dwThreadId){        hWnd = hWindow;        HOOKPROC cb = reinterpret_cast( HookProc ); // for VS97        hHook = SetWindowsHookEx( hookID, cb, hMod, dwThreadId);        if( !hHook ) ShowErrorInfo( GetLastError() );        return hHook;    }         EXPORT BOOL HookDown(HHOOK idHook){        hWnd = NULL;        return UnhookWindowsHookEx(hHook);    }对于DLL的入口DLLMain,这是自定义的入口点。如果没有指定它,系统会使用内置的入口点_DllMainCRTStartup,它会调用一个例程来初始化 C/C++ 的运行库,这个例程就是_CRT_INIT。事实上,DLLMain也应该完成这些初始化的功能。入口参数hModule指载入DLL的进行句柄,参数fdwreason表示了载入DLL的进程是以什么方式进行的,如下:    #define DLL_PROCESS_ATTACH 1  // 正在映射到进程地址空间,执行初始化,出错则返回FALSE    #define DLL_THREAD_ATTACH  2  // 线程已经创建,执行初始化    #define DLL_THREAD_DETACH  3  // 线程终止,执行清理。测试时并没有执行!    #define DLL_PROCESS_DETACH 0  // 进程终止,从映射空间撤除,执行必要的清理对于主程序,还是拿最开始的样板来使用,只需要修改一部分就可以了。新建一具hookcross工程,添加一个代码文件,文章开始将样板代码拷贝过来,修改窗口过程,并添加一个时钟调用。这样程序执行时会定时检查系统,看看有没有写字板程序在运行,如果发现它就将钩子注入。hooksrv.dll在程序执行时将由系统自动调入,手动装入DLL可以参考MSDN的Platform SDK > Windows Base Services > Excutables部分。注意,注册钩子时,使用了DLL的进程ID,而且使用了其它线程,这里指写字板线程,所以注册的是一个线程的系统钩子,而且只对Wordpad的消息环进行监测。如果,线程ID设置为NULL,那么,这个钩子将对所有进程的消息进行监测,这就成了一个全局系统钩子。主程序不用修改,主要是修改窗口过程部分,这里将修改及添加部分的代码罗列出来:    #include "../HookSrv/hooksrv.h"    #pragma comment(lib,"../HookSrv/Debug/hooksrv.lib")         extern __declspec(dllimport) int hookCode;         UINT COUNT;    HHOOK MyHook;    UINT TIMER_HOOK = 1;    string tipText =  "Appication API HOOK Wating for a Wordpad.";         void CALLBACK TimerProc(HWND hwin, UINT uMsg, UINT id, DWORD t){        HWND hw = FindWindow( "WordPadClass",NULL );        if(!hw) return;        DWORD thread = GetWindowThreadProcessId( hw, NULL );        MyHook = HookStart( WH_SHELL, hWin,  thread );        if(MyHook){            KillTimer( hWin,TIMER_HOOK );            tipText = "Hooked for Wordpad.";            RECT rect;            GetClientRect( hWin, &rect );            InvalidateRect( hWin, &rect, TRUE);            UpdateWindow( hWin );        }    }         void SendToPad(char * buf){ // 通过消息给程序发送字符及换行符        string txt(buf);        HWND hw = FindWindow( "WordPadClass",NULL );        HWND  hc = FindWindowEx( hw, NULL,"RICHEDIT50W",NULL );        if(!hc) return;        for( int i=0; i<txt.length(); i++){            SendMessage( hc, WM_CHAR, (char) txt[i], 0 );        }        SendMessage( hc, WM_KEYDOWN, VK_RETURN, 0 ); // "\r\n" for Wordpad;    }         LRESULT CALLBACK circle( HWND hWin, UINT iMsg, WPARAM wp, LPARAM lp ){        HDC         hdc ;        PAINTSTRUCT ps ;        RECT        rect ;        string text =  "Appication API HOOK Wating for a Wordpad.";        char buf[MAXBYTE];        stringstream ss;        TIMERPROC cb = reinterpret_cast(TimerProc); // for VS97             switch( iMsg ){        case WM_CREATE:            PlaySound( szSound, NULL, SND_FILENAME | SND_ASYNC );            SetTimer( hWin, TIMER_HOOK,  1000, cb);            return 0;        case WM_DESTROY:            PostQuitMessage(0);            HookDown( MyHook );            return 0;        case WM_PAINT:            hdc = BeginPaint( hWin, &ps);            GetClientRect( hWin, &rect );            DrawText( hdc, tipText.c_str(), -1, &rect, DT_SINGLELINE | DT_CENTER | DT_VCENTER );            EndPaint( hWin, &ps);            return 0;        case WM_HOOK:            ss << szTitle << " -  " << ++COUNT << std::hex << " HOOK:0x" << hookCode                 << " wp:0x" << wp << " lp:0x" << lp << "\n";            ss.getline( buf, MAXBYTE);            SetWindowText( hWin, buf );            SendToPad(buf);        }             return DefWindowProc( hWin, iMsg, wp, lp);    }注意主程序使用__declspec(dllimport)标记来导入DLL变量,hookCode这个变量存储在DLL的共享数据段,所有使用此DLL的进程都可以修改它,并且所有进程都会得到修改后的数据,这种情况就是进程不安全的状态。如果钩子不是DLL模块,那么在设置系统钩子时,就会出错:没有模块句柄无法设置非本机连接。这里尝试将钩子也编译到同一个程序文件来测试,通过LocaLibrary来加载程序,通过返回的模块句柄来操作,但LoadLibrary不能使其形成有效的模块,即使程序正常运行,但即收不到钩子回调动作。同时,每一个DLL钩子同时只能有个程序使用,如果多个程序使用注册DLL钩子,那么最后注册的才有效,即设置了不同的钩子类型,但是清除钩子却可以由不同的程序完成!钩子回调过程细节钩子回调时,传回三个参数中,第一个为ncode,它指一个钩子的代码,如果这个为负值,那么应该直接通过CallNextHookEx传回系统内部处理,然后回调函数直接返回系统给出的结果。但是从Windows 3.1开始,不再向回调函数传递负值了。后两个wParam和lParam,与消息环的参数可以说十分一致。它们则是根据不同的钩子,用来传递不同的参数或数据。下面逐个钩子类型进行解释。这里特别要说明一点,因为Windows进程的内存空间是受保护的,所以注入DLL的进程和主程序是两个受保护的内存空间,要共享数据就要透过前面介绍的DLL共享数据的方法,或其它手段的实现,绝对不能通过指针来实现。WH_CALLWNDPROC得到一个钩子代码指示SendMessage已经发送消息,和一个指针指向真正的消息数据,如下:    typedef struct tagCWPSTRUCT {        LPARAM  lParam;        WPARAM  wParam;        DWORD   message;        HWND    hwnd;    } CWPSTRUCT, *PCWPSTRUCT, NEAR *NPCWPSTRUCT, FAR *LPCWPSTRUCT;WH_DEBUG当系统调用其它类型钩子过程之前会执行这个钩子,wParam中得到下一个将被调用的钩子的ID,例如可能是一个WH_MOUSE。可以通过返回一个非0值来阻止系统,但不能修改钩子ID。lParam指向一个结构体,定义如下:    typedef struct tagDEBUGHOOKINFO    {        DWORD   idThread;  // The thread ID for the current thread        LPARAM  reserved;        LPARAM  lParam;    // The lParam for the target filter function        WPARAM  wParam;    // The wParam for the target filter function        int     code;    } DEBUGHOOKINFO, *PDEBUGHOOKINFO, NEAR *NPDEBUGHOOKINFO, FAR* LPDEBUGHOOKINFO;WH_FOREGROUNDIDLE当用户进程空闲无用户输入时调用的钩子,注意,只有钩子注入的线程是当前线程时才有效。这只是一个通知钩子,可以用来执行空闲任务,参数 wParam 和 lParam 都是0。WH_GETMESSAGE在GetMessage和PeekMessage即将返回时消息时调用的钩子,lParam指向一个结构体,包含了消息体数据,对消息的所有修改也会原样返回给程序。    typedef struct tagMSG {     /* msg */        HWND   hwnd;      \\ The window whose Winproc will receive the message        UINT   message;   \\ The message number        WPARAM wParam;        LPARAM lParam;        DWORD  time;      \\ The time the message was posted        POINT  pt;        \\ The cursor position in screen coordinates                          \\  of the message    } MSG;WH_HARDWARE欠缺资料。WH_KEYBOARD当GetMessage或PeekMessage即将返回键盘消息时调用,即一系列的WM_KEYUP, WM_KEYDOWN, WM_SYSKEYUP, WM_SYSKEYDOWN, 和 。回调过程会收到键盘的虚拟按键代码和键盘状态,还可以让系统忽略掉这些消息。回调过程的钩子代码有两种,HC_ACTION和HC_NOREMOVE。前者表示事件将要从系统队列中清除,而后者则提示消息队列不会移除键盘消息。这是因为程序使用了带PM_NOREMOVE参数的PeekMessage。wParam存放虚拟键值如VK_ESCAPE之类,或在WM_CHAR消息中存放字符代码,lParam存放按键数据,如重复次数,按键扫描码之类。虚拟键值定义在WINRESRC.H,现罗列如下:    #define VK_LBUTTON        0x01    #define VK_RBUTTON        0x02    #define VK_CANCEL         0x03    #define VK_MBUTTON        0x04    /* NOT contiguous with L & RBUTTON */         #define VK_BACK           0x08          #define VK_SPACE          0x20    #define VK_TAB            0x09          #define VK_PRIOR          0x21                                            #define VK_NEXT           0x22    #define VK_CLEAR          0x0C          #define VK_END            0x23    #define VK_RETURN         0x0D          #define VK_HOME           0x24                                            #define VK_LEFT           0x25    #define VK_SHIFT          0x10          #define VK_UP             0x26    #define VK_CONTROL        0x11          #define VK_RIGHT          0x27    #define VK_MENU           0x12          #define VK_DOWN           0x28    #define VK_PAUSE          0x13          #define VK_SELECT         0x29    #define VK_CAPITAL        0x14          #define VK_PRINT          0x2A                                            #define VK_EXECUTE        0x2B                                            #define VK_SNAPSHOT       0x2C    #define VK_ESCAPE         0x1B          #define VK_INSERT         0x2D                                            #define VK_DELETE         0x2E                                            #define VK_HELP           0x2F         /* VK_0 thru VK_9 are the same as ASCII '0' thru '9' (0x30 - 0x39) */    /* VK_A thru VK_Z are the same as ASCII 'A' thru 'Z' (0x41 - 0x5A) */         #define VK_LWIN           0x5B    #define VK_RWIN           0x5C    #define VK_APPS           0x5D          #define VK_SEPARATOR      0x6C                                            #define VK_SUBTRACT       0x6D    #define VK_NUMLOCK        0x90          #define VK_DECIMAL        0x6E    #define VK_SCROLL         0x91          #define VK_DIVIDE         0x6F    #define VK_NUMPAD0        0x60          #define VK_F1             0x70    #define VK_NUMPAD1        0x61          #define VK_F2             0x71    #define VK_NUMPAD2        0x62          #define VK_F3             0x72    #define VK_NUMPAD3        0x63          #define VK_F4             0x73    #define VK_NUMPAD4        0x64          #define VK_F5             0x74    #define VK_NUMPAD5        0x65          #define VK_F6             0x75    #define VK_NUMPAD6        0x66          #define VK_F7             0x76    #define VK_NUMPAD7        0x67          #define VK_F8             0x77    #define VK_NUMPAD8        0x68          #define VK_F9             0x78    #define VK_NUMPAD9        0x69          #define VK_F10            0x79    #define VK_MULTIPLY       0x6A          #define VK_F11            0x7A    #define VK_ADD            0x6B          #define VK_F12            0x7B         /*     * VK_L* & VK_R* - left and right Alt, Ctrl and Shift virtual keys.     * Used only as parameters to GetAsyncKeyState() and GetKeyState().     * No other API or message will distinguish left and right keys in this way.     */    #define VK_LSHIFT         0xA0    #define VK_RSHIFT         0xA1    #define VK_LCONTROL       0xA2    #define VK_RCONTROL       0xA3    #define VK_LMENU          0xA4    #define VK_RMENU          0xA5        WH_MOUSE当GetMessage或PeekMessage即将返回鼠标消息时调用,和WH_KEYBOARD相似,也有钩子代码指示消息是否会从队列中移除。而鼠标事件消息有好多,如WM_MOUSEMOVE,也有直接根据鼠标按键状态引发的事件消息,如WM_LBUTTONDOWN、WM_LBUTTONUP、WM_LBUTTONDBLCLK,还有相应的中键和右键的消息。lParam的高低两个字节分别存储了Y、X坐标,即LOWORD(lParam)表示X坐标。wParam存储了按键状态数据,可以和按键掩码进行运算,如MK_CONTROL、MK_LBUTTON、MK_MBUTTON、MK_RBUTTON、MK_SHIFT。当鼠标在非显示区域时,会引发WM_NCMOUSEMOVE这类事件,有对应按下左键的WM_NCLBUTTONDOWN、WM_NCLBUTTONUP、WM_NCLBUTTONDBLCLK,其它按键类似。此时wParam表示命中的区域,lParam还是表示鼠标的屏幕坐标系。鼠标命中测试消息WM_NCHITTEST,Windows根据它来产生与鼠标位置相关的所有其它鼠标消息。wParam未使用,lParam 存储屏幕坐标。滑轮消息WM_MOUSEWHEEL,此时wParam还保存按键状态数据,而且在其高两字节保存了滚动计数值zDelta,即HIWORD(wParam)。但是在鼠标钩子回调过程中,wParam存放的是鼠标消息ID,lParam指向一个结构体,如下罗列:    #define WM_MOUSEACTIVATE                0x0021    #define WM_NCMOUSEMOVE                  0x00A0    #define WM_NCMOUSEMOVE                  0x00A0    #define WM_NCLBUTTONDOWN                0x00A1    #define WM_NCLBUTTONUP                  0x00A2    #define WM_NCLBUTTONDBLCLK              0x00A3    #define WM_NCRBUTTONDOWN                0x00A4    #define WM_NCRBUTTONUP                  0x00A5    #define WM_NCRBUTTONDBLCLK              0x00A6    #define WM_NCMBUTTONDOWN                0x00A7    #define WM_NCMBUTTONUP                  0x00A8    #define WM_NCMBUTTONDBLCLK              0x00A9    #define WM_MOUSEFIRST                   0x0200    #define WM_MOUSEMOVE                    0x0200    #define WM_LBUTTONDOWN                  0x0201    #define WM_LBUTTONUP                    0x0202    #define WM_LBUTTONDBLCLK                0x0203    #define WM_RBUTTONDOWN                  0x0204    #define WM_RBUTTONUP                    0x0205    #define WM_RBUTTONDBLCLK                0x0206    #define WM_MBUTTONDOWN                  0x0207    #define WM_MBUTTONUP                    0x0208    #define WM_MBUTTONDBLCLK                0x0209         #if(_WIN32_WINNT >= 0x0400)    #define WM_MOUSEWHEEL                   0x020A    #endif /* _WIN32_WINNT >= 0x0400 */    #if (_WIN32_WINNT < 0x0400)    #define WM_MOUSELAST                    0x0209    #else    #define WM_MOUSELAST                    0x020A    #endif /* if (_WIN32_WINNT < 0x0400) */         #if(_WIN32_WINNT >= 0x0400)    #define WM_MOUSEHOVER                   0x02A1    #define WM_MOUSELEAVE                   0x02A3    #endif /* _WIN32_WINNT >= 0x0400 */         typedef struct tagMOUSEHOOKSTRUCT {        POINT   pt;        HWND    hwnd;        UINT    wHitTestCode;        DWORD   dwExtraInfo;    } MOUSEHOOKSTRUCT, FAR *LPMOUSEHOOKSTRUCT, *PMOUSEHOOKSTRUCT;         typedef struct tagPOINT {        LONG x;        LONG y;    } POINT;WH_MSGFILTER这个钩子在对话框、消息框、滚动条、菜单条收到消息时,或ALT+TAB、ALT+ESC等组合键在钩子活动中被按下时调用,不过测试时发现组合键不会引用钩子调用。因为这个钩子是指定线程的,因此它可以在程序或DLL中运行良好。它的回调过程将会收到lParam指向的消息数据,还有以下几个钩子代码表示不同的状态,还有些未使用的就不罗列了:    #define MSGF_DIALOGBOX      0 // 消息框或对话框消息    #define MSGF_MENU           2 // 菜单条消息    #define MSGF_MOVE           3 // 移动窗口消息    #define MSGF_SIZE           4 // 调整窗口大小    #define MSGF_SCROLLBAR      5 // 滚动条消息    #define MSGF_NEXTWINDOW     6 // 即将替换为下一个窗口WH_SYSMSGFILTER相似WH_MSGFILTER钩子,它要更早执行,因为是系统级别的。因此通过返回TRUE,可以忽略掉WH_MSGFILTER钩子。WH_SHELL外壳钩子发生在顶级窗口消息中,因此也是要指定线程ID的,钩子只在属于线程的窗口消息中引发。是一个通知钩子,因此不能更改事件消息,在wParam参数中包含了窗口的句柄。部分钩子代码有如下值定义:    #define HSHELL_WINDOWCREATED        1 // 窗口已经创建消息    #define HSHELL_WINDOWDESTROYED      2 // 窗口即将解构    #define HSHELL_ACTIVATESHELLWINDOW  3 // 激活shell主窗口    #define HSHELL_WINDOWACTIVATED      4 // 窗口已切换    #define HSHELL_GETMINRECT           5 // 系统需要该窗口被最小化时的矩形坐标    #define HSHELL_REDRAW               6 // 任务条上的标题已被重画    #define HSHELL_TASKMAN              7 // 用户已选择其任务列表    #define HSHELL_LANGUAGE             8 // 键盘语言被改变或者一个新的键盘布局被加载    #define HSHELL_ACCESSIBILITYSTATE   11 // NT5.0或以上版本有效,指示可访问性已改变记录钩子JOURNAL,是日志记录的意思,相关的钩子也是和记录和回放事件有关的,因为它是系统全局的钩子,影响所有程序,因而负作用更多。为此系统提供了几种默认的的按键来清除这些钩子,有CTRL+ESC、 ALT+ESC 和 CTRL+ALT+DEL。然后,系统通过一条WM_CANCELJOURNAL消息通知程序被挂了日志钩子。这个消息并没指定窗口句柄,因此没有窗口过程会收到这样的消息分配。一个好方法就是使用WH_GETMESSAGE钩子来截取这条消息。当然提供一个取消钩子的方法更重要,如通过VK_CANCEL 即CTRL+BREAK。下面是两个日志相关钩子。WH_JOURNALRECORD记录钩子,系统从队列中移除鼠标及键盘消息时引发,除了回放钩子的消息外。钩子可以处理,但不可以修改或丢弃消息,这是因为记录已经保存在磁盘或内存中。目前只实现HC_ACTION这个有效钩子代码。lParam参数指向一个ENVENTMSG结构体,通常的做法是将这些数据存储起来,然后再通过回放钩子将这些数据形成回放动作。注意它只能是全局的系统钩子。    typedef struct tagEVENTMSG {        UINT    message;  // 消息ID,如WM_MOUSEMOVE        UINT    paramL;        UINT    paramH;        DWORD    time; // 消息发生的系统时间,GetTickCount的返回值        HWND     hwnd;    } EVENTMSG;    typedef struct tagEVENTMSG *PEVENTMSG, NEAR *NPEVENTMSG, FAR *LPEVENTMSG;如果是键盘消息,paramL高字节放有扫描码,低字节放有虚拟键值,paramH则包含重复次数、Bit 15指示扩展键是否等内容;如果是鼠标消息,paramL 和 paramH 则是xy坐标。WH_JOURNALPLAYBACK回放钩子在回放记录钩子的数据,或给其它程序发送事件消息时使用。当回放钩子一挂接,系统就会忽略鼠标的移动,其它的键盘和鼠标事件消息将会在回放钩子撤除后才会进入队列。钩子代码有以下两个:HC_GETNEXT,在访问线程消息时引发,系统会使用相同的消息调用多次。lParam指定一个EVENTMSG结构体,回调过程需要将已经记录的数据填回到这个结构体中,通常是直接拷贝已在记录钩子保存的数据。系统需要两个参数来处理钩子提供的回放数据,一个是等待处理这条消息的时间,另一个是在什么时候处理这条消息。通常等待时间可以使用两个相间的消息时间的差值,而指定处理时间则通过等待时间和当前GetTickCount返回值相加得到。如果想要加速回放,设置等待时间为0就可以了,等待时间通过钩子回调返回给系统。HC_SKIP,当系统处理完一个回放记录时引发,此时应当准备下一个回放记录,当所胡记录都回放完了,就应该清除回放钩子,以使程序回到正常工作状态。WH_CBT这可以说是最最长篇的一个钩子,它有如下几种钩子代码:#define HCBT_MOVESIZE 0 #define HCBT_MINMAX 1 #define HCBT_QS 2 #define HCBT_CREATEWND 3 #define HCBT_DESTROYWND 4 #define HCBT_ACTIVATE 5 #define HCBT_CLICKSKIPPED 6 #define HCBT_KEYSKIPPED 7 #define HCBT_SYSCOMMAND 8 #define HCBT_SETFOCUS 9HCBT_ACTIVATE,激活窗口,返回TRUE阻止焦点以禁止激活。对应的参数,wParam指向正在激活的窗口的句柄,lParam指向一个结构体:    typedef struct tagCBTACTIVATESTRUCT    {       BOOL    fMouse;         // TRUE if activation results from a mouse click       HWND    hWndActive;     // the currently active window's handle.    } CBTACTIVATESTRUCT, *LPCBTACTIVATESTRUCT;HCBT_CREATEWND,窗口创建中,但程序的WM_GETMINMAXINFO, WM_NCCREATE, WM_CREATE消息还没有发出,因此返回TRUE可以禁止窗体的创建。参数wParam是窗口的句柄,lParam指向一个结构体:    struct CBT_CREATEWND{        struct tagCREATESTRUCT *lpcs;        HWND           hwndInsertAfter;   // The window will be followed, in Z-order.    } CBT_CREATEWND, *LPCBT_CREATEWND;         typedef struct tagCREATESTRUCTA {        LPVOID      lpCreateParams;        HINSTANCE   hInstance;        HMENU       hMenu;        HWND        hwndParent;        int         cy;        int         cx;        int         y;        int         x;        LONG        style;        LPCSTR      lpszName;        LPCSTR      lpszClass;        DWORD       dwExStyle;    } CREATESTRUCTA, *LPCREATESTRUCTA;HCBT_DESTROYWND,窗口解构中,但WM_DESTROY消息还没有发出,可以通过返回TRUE来阻止窗口解构。参数wParam是窗口的句柄,lParam为0L。HCBT_MINMAX最大最小化时引发,返回TRUE可以禁止动作。参数wParam是窗口的句柄,lParam是一个ShowWindow常数SW_*值:    #define SW_HIDE             0    #define SW_SHOWNORMAL       1    #define SW_NORMAL           1    #define SW_SHOWMINIMIZED    2    #define SW_SHOWMAXIMIZED    3    #define SW_MAXIMIZE         3    #define SW_SHOWNOACTIVATE   4    #define SW_SHOW             5    #define SW_MINIMIZE         6    #define SW_SHOWMINNOACTIVE  7    #define SW_SHOWNA           8    #define SW_RESTORE          9    #define SW_SHOWDEFAULT      10    #define SW_MAX              10HCBT_MOVESIZE,移动或调整窗口大小时引发,返回TRUE可以禁止动作。参数wParam是窗口的句柄,lParam是一个矩形RECT结构体指针,文章开始处已经出现过。HCBT_SYSCOMMAND,系统菜单命令,即左上角的弹出菜单引发,返回TRUE可以禁止系统菜单弹出。WH_CBT钩子是由DefWindowsProc过程调用的,如果没有发送WH_SYSCOMMAND过来,就不会有这个钩子的调用。参数wParam包含即将执行行的系统命令,即鼠标划过的菜单项,lParam的低位字和高位字存放鼠标的xy坐标。如果wParam是SC_HOTKEY,那么lParam就包含热键适用的窗口句柄。其它系统命令列表如下:    #define SC_SIZE         0xF000 // 调整窗口大小    #define SC_MOVE         0xF010 // 移动窗口位置    #define SC_MINIMIZE     0xF020 // 最小化窗口    #define SC_MAXIMIZE     0xF030 // 最大化窗口    #define SC_NEXTWINDOW   0xF040 // 下一个窗口    #define SC_PREVWINDOW   0xF050 // 上一个窗口    #define SC_CLOSE        0xF060 // 关闭命令    #define SC_VSCROLL      0xF070 // 垂直滚动    #define SC_HSCROLL      0xF080 // 水平滚动    #define SC_MOUSEMENU    0xF090 // 通过鼠标单击获取菜单    #define SC_KEYMENU      0xF100 // 通过按键获取菜单    #define SC_ARRANGE      0xF110    #define SC_RESTORE      0xF120 // 还原窗口位置状态    #define SC_TASKLIST     0xF130 // 执行或激活任务管理程序    #define SC_SCREENSAVE   0xF140 // 执行屏保程序    #define SC_HOTKEY       0xF150    #define SC_DEFAULT      0xF160    #define SC_MONITORPOWER 0xF170    #define SC_CONTEXTHELP  0xF180    #define SC_SEPARATOR    0xF00FHCBT_SETFOCUS,当窗口激活就要取得焦点时引起,可以通过返回TRUE来阻止窗口取得焦点。参数wParam有窗口的句柄,lParam失去焦点的窗口句柄。HCBT_QS,当移动或调整窗口大小过程中,一个WM_QUEUESYNC消息从系统队列移除时引起,其它任何情况不会发生。参数wParam和lParam都为0。HCBT_CLICKSKIPPED,当鼠标事件要从队列移除时引发,也就是说这个鼠标事件是无效的,通常是日志钩子回放时引起的。wParam包含鼠标事的类型,如WM_LBUTTONDOWN,lParam包含一个结构体的指针:    typedef struct tagMOUSEHOOKSTRUCT {        POINT   pt;             // Location of mouse in screen coordinates        HWND    hwnd;           // Window that receives this message        UINT    wHitTestCode;   // The result of hit-testing (HT_*)        DWORD   dwExtraInfo;    // Extra info associated with the current message    } MOUSEHOOKSTRUCT, FAR *LPMOUSEHOOKSTRUCT, *PMOUSEHOOKSTRUCT;HCBT_KEYSKIPPED,和前者一样,路过或从队列中移除消息都会引发。wParam包含虚拟键值,lParam包含其它属性,和消息环的键盘事件消息一样。WM_QUEUESYNC,CBT编程通常都要响应主程序的这些键盘和鼠标事件,例如,在确定一个对话框后,CBT程序可能会要向主程序输入几个字符。通过鼠标钩子可以用来确定有没有按下OK按钮,根据结果来决定要输入哪些字符到主程序, 这样CBT程序就要等到按下OK按钮后的过程处理完成后。那么CBT程序就可以通过WM_QUEUESYNC消息来监视主程序,看看动作何时完成,下面是两个判断步骤:    CBT程序等待直到收到WH_CBT钩子,带 HCBT_CLICKSKIPPED 或 HCBT_KEYSKIPPED 代码的钩子。这会在主程序把消息从系统队列中移除时发生。    当CBT安装回放钩子时,直到收到HCBT_CLICKSKIPPED 或 HCBT_KEYSKIPPED 代码才能安装成功。回放钩子发送WM_QUEUESYNC消息给CBT程序,CBT程序可以响应这个事件,比如说输入一些字符到主程序。参考资料    Visual Studio MSDN Library 6.0 SPY Code Sample    Win32 Hooks -MSDN 99, Kyle Marsh July 29, 1993    Using Hooks, MSDN: http://msdn.microsoft.com/en-us/library/ms644960%28v=vs.85%29.aspx    "Load Your 32-bit DLL into Another Process's Address Space Using INJLIB" Jeffrey Ritcher, MSJ May 1994    "An In-Depth Look into the Win32 PE file format" , part 1, Matt Pietrek, MSJ February 2002    "An In-Depth Look into the Win32 PE file format" , part 2, Matt Pietrek, MSJ March 2002    DLLs in Win32 by Randy Kath,MSDN September 15, 1992    DLLs for Beginners by Debabrata Sarma,MSDN 1996    Advanced Windows NT, The Developer's Guide to the Win32 Application Programming Interface by J. Richter    Detecting Windows NT/2K process execution: http://www.codeproject.com/Articles/2018/Detecting-Windows-NT-2K-process-execution    API hooking revealed: http://www.codeproject.com/Articles/2082/API-hooking-revealed    API Hook完全手册 : http://blog.csdn.net/ATField/article/details/1507122    让EXE导出函数,看雪论坛: http://bbs.pediy.com/showthread.php?t=56840    如何与应用程序或其他 DLL 共享自己 DLL 中的数据?: http://msdn.microsoft.com/zh-cn/library/h90dkhs0    DLL导出变量: http://blog.csdn.net/henry000/article/details/6852521修改文件的IMAGE_NT_HEADERS.FileHeader.Characteristics为IMAGE_FILE_DLL。参考资料    :    :    版权声明:自由转载-非商用-非衍生-保持署名 | Creative Commons BY-NC-ND 3.0    ByNo CommercialNo Derived    建档时间:2013年11月15日    修改时间:2013年11月22日

原创粉丝点击