Hook 简介
因为博主的原来已经讲过 Hook 相关的基础知识,所以这里就不重复了。如果没有接触过的请参见 《Windows API 教程(七) hook 监听》。
不管是写木马或者是做一些神奇的操作, Hook 都是一项必不可少的技术。
windows 系统中的【hook 机制】,就类似于一个【消息过滤网】,如果我们向操作系统申请并成功对某个窗口安装了一个【hook】也就相当于我们人为对这个窗口添加了一个【消息过滤网】。此时当 windows 操作系统要对这个窗口发送任何消息的时候(例如按键、鼠标点击等消息)操作系统会先调用我们在【消息过滤网】中设置的【回调函数】去接受、处理、过滤等等,当然如果你在【回调函数】中拿到了数据却没有继续传递给窗口的话,就相当于拦截了这些消息。
也就是说如果你想希望某个人的电脑不论是鼠标还是键盘都没有用,那么只需要安装一个鼠标的【消息过滤网】以及一个键盘的【消息过滤网】接着在这两个消息过滤网中将消息截断即可。
在编写木马的时候,我们就可以通过这个【消息过滤网】截获到我们所想要的数据,并对这些数据进行一定的处理或者加工。一般的 Hook 都需要以 DLL 的形式加载,但是出于方便实验考虑,所以本讲所要提到的 Hook (WH_MOUSE_LL 以及 WH_KEYBOARD_LL)是不需要以 DLL 的形式安装的。
Hook 基本类型
#t .head {text-align: center; width: 210px; } #t .content {padding: 10px; text-indent: 20px; } #t .content li { text-indent: 0px; }
| WH_CALLWNDPROC 和 WH_CALLWNDPROCRET Hooks |
WH_CALLWNDPROC 和 WH_CALLWNDPROCRET Hooks使你可以监视发送到窗口过程的消息。系统在消息发送到接收窗口过程之前调用 WH_CALLWNDPROCHook子过程,并且在窗口过程处理完消息之后调用 WH_CALLWNDPROCRET Hook子过程。 |
| WH_CBT Hook |
在以下事件之前,系统都会调用 WH_CBT Hook子过程,这些事件包括:
- 激活,建立,销毁,最小化,最大化,移动,改变尺寸等窗口事件
- 完成系统指令
- 来自系统消息队列中的移动鼠标,键盘事件
- 设置输入焦点事件
- 同步系统消息队列事件
Hook子过程的返回值确定系统是否允许或者防止这些操作中的一个。 |
| WH_DEBUG Hook |
在系统调用系统中与其它Hook关联的Hook子过程之前,系统会调用 WH_DEBUG Hook子过程。你可以使用这个Hook来决定是否允许系统调用与其它 Hook 关联的Hook子过程 |
| WH_FOREGROUNDIDLE Hook |
当应用程序的前台线程大概要变成空闲状态时,系统就会调用 WH_FOREGROUNDIDLE Hook子过程。 |
| WH_GETMESSAGE Hook |
应用程序使用 WH_GETMESSAGE Hook来监视从 GetMessage() 或者 PeekMessage() 函数返回的消息。你可以使用 WH_GETMESSAGE Hook 去监视鼠标和键盘输入,以及其它发送到消息队列中的消息。 |
| WH_JOURNALPLAYBACK Hook |
WH_JOURNALPLAYBACK Hook 使应用程序可以插入消息到系统消息队列。可以使用这个 Hook 回放通过使用 WH_JOURNALRECORD Hook 记录下来的连续的鼠标和键盘事件。只要 WH_JOURNALPLAYBACK Hook 已经安装,正常的鼠标和键盘事件就是无效的。 WH_JOURNALPLAYBACK Hook是全局Hook,它不能象线程特定Hook一样使用。WH_JOURNALPLAYBACK Hook返回超时值,这个值告诉系统在处理来自回放Hook当前消息之前需要等待多长时间(毫秒)。这就使Hook可以控制实时事件的回放。WH_JOURNALPLAYBACK 是system-wide local hooks,它们不会被注射到任何行程地址空间。 |
| WH_JOURNALRECORD Hook |
WH_JOURNALRECORD Hook 用来监视和记录输入事件。典型的,可以使用这个 Hook 记录连续的鼠标和键盘事件,然后通过使用WH_JOURNALPLAYBACK Hook 来回放。 WH_JOURNALRECORD Hook 是全局 Hook ,它不能象线程特定 Hook 一样使用。 WH_JOURNALRECORD 是 system-wide local hooks,它们不会被注射到任何行程地址空间。 |
| WH_KEYBOARD Hook |
在应用程序中,WH_KEYBOARD Hook用来监视 WM_KEYDOWN 以及 WM_KEYUP 消息,这些消息通过 GetMessage() 或者 PeekMessage() 返回。可以使用这个 Hook 来监视输入到消息队列中的键盘消息。 |
| WH_KEYBOARD_LL Hook |
WH_KEYBOARD_LL Hook监视输入到线程消息队列中的键盘消息。 |
| WH_MOUSE Hook |
WH_MOUSE Hook 监视从 GetMessage() 或者 PeekMessage() 返回的鼠标消息。使用这个Hook监视输入到消息队列中的鼠标消息。 |
| WH_MOUSE_LL Hook |
WH_MOUSE_LL Hook监视输入到线程消息队列中的鼠标消息。 |
| WH_MSGFILTER 和 WH_SYSMSGFILTER Hooks |
WH_MSGFILTER 和 WH_SYSMSGFILTER Hooks使我们可以监视菜单,滚动条,消息框,对话框消息并且发现用户使用ALT+TAB or ALT+ESC 组合键切换窗口。 WH_MSGFILTER Hook只能监视传递到菜单,滚动条,消息框的消息,以及传递到通过安装了Hook子过程的应用程序建立的对话框的消息。 WH_SYSMSGFILTER Hook监视所有应用程序消息。 WH_MSGFILTER 和 WH_SYSMSGFILTER Hooks使我们可以在模式循环期间过滤消息,这等价于在主消息循环中过滤消息。通过调用CallMsgFilter function可以直接的调用 WH_MSGFILTER Hook。通过使用这个函数,应用程序能够在模式循环期间使用相同的代码去过滤消息,如同在主消息循环里一样。 |
| WH_SHELL Hook |
外壳应用程序可以使用 WH_SHELL Hook 去接收重要的通知。当外壳应用程序是激活的并且当顶层窗口建立或者销毁时,系统调用 WH_SHELL Hook 子过程。 WH_SHELL 共有5钟情况:
- 只要有个top-level、unowned 窗口被产生、起作用、或是被摧毁
- 当Taskbar需要重画某个按钮
- 当系统需要显示关于Taskbar的一个程序的最小化形式
- 当目前的键盘布局状态改变
- 当使用者按Ctrl+Esc去执行Task Manager(或相同级别的程序)
按照惯例,外壳应用程序都不接收 WH_SHELL 消息。所以,在应用程序能够接收 WH_SHELL 消息之前,应用程序必须调用 SystemParametersInfo() 注册它自己。 |
通过 Hook 监视目标的屏幕
博主这里讲的监控跟平常的远程查看桌面不一样,也算是比较容易实现的版本。即,当用户鼠标发生点击的时候就把当前的页面截图留下。通过鼠标在多种情况下的屏幕截图来达到监视目标计算机的目的。
其主要的思路很简单,以下是示意代码:
/**
* Hook 钩子处理函数
*/
LRESULT CALLBACK MouseProc(int nCode, WPARAM wParam, LPARAM lParam)
{
// 变量定义 ...
if (nCode >= 0)
{
/* ... */
// 左键按下时将屏幕截图
if(wParam == WM_LBUTTONDOWN)
{
// 通过当前时间拼凑截图文件名
getCurrentDateTime( /*...*/ );
// 屏幕截图并保存到指定地址
CaptureImage( /*... */);
}
/* ... */
}
// 将消息继续传递给窗口
return CallNextHookEx(myhook, nCode, wParam, lParam);
// 如果不调用 CallNextHookEx 的话消息就被你的 Hook 截断了
}
在博主的这一讲这里仅当用户的鼠标左键点击的时候截取用户的截图。各位也可以适当的调整。关于获取时间可以参见博主的 《c 时间戳》,关于截图程序可以参见博主的 《C语言 屏幕截图》(GDI) 以及 《C++ 屏幕截图》(GDI+)
完整代码
#include <windows.h>
void echo(CHAR *str);
int CaptureImage(HWND hWnd, CHAR *dirPath, CHAR *filename);
int main()
{
echo(TEXT("Ready"));
CaptureImage(GetDesktopWindow(), "E:\", "screen"); // 保存为 E:screen.bmp
echo(TEXT("end"));
return 0;
}
/**
* 调试输出
*/
void echo(CHAR *str)
{
MessageBox(NULL, str, NULL, MB_OK);
}
/**
* GDI 截屏函数
*
* 参数 hwnd 要截屏的窗口句柄
* 参数 dirPath 截图存放目录
* 参数 filename 截图名称
*/
int CaptureImage(HWND hwnd, CHAR *dirPath, CHAR *filename)
{
HANDLE hDIB;
HANDLE hFile;
DWORD dwBmpSize;
DWORD dwSizeofDIB;
DWORD dwBytesWritten;
CHAR FilePath[MAX_PATH];
HBITMAP hbmScreen = NULL;
BITMAP bmpScreen;
BITMAPFILEHEADER bmfHeader;
BITMAPINFOHEADER bi;
CHAR *lpbitmap;
INT width = GetSystemMetrics(SM_CXSCREEN); // 屏幕宽
INT height = GetSystemMetrics(SM_CYSCREEN); // 屏幕高
HDC hdcScreen = GetDC(NULL); // 全屏幕DC
HDC hdcMemDC = CreateCompatibleDC(hdcScreen); // 创建兼容内存DC
if (!hdcMemDC)
{
echo(TEXT("CreateCompatibleDC has failed"));
goto done;
}
// 通过窗口DC 创建一个兼容位图
hbmScreen = CreateCompatibleBitmap(hdcScreen, width, height);
if (!hbmScreen)
{
echo(TEXT("CreateCompatibleBitmap Failed"));
goto done;
}
// 将位图块传送到我们兼容的内存DC中
SelectObject(hdcMemDC, hbmScreen);
if (!BitBlt(
hdcMemDC, // 目的DC
0, 0, // 目的DC的 x,y 坐标
width, height, // 目的 DC 的宽高
hdcScreen, // 来源DC
0, 0, // 来源DC的 x,y 坐标
SRCCOPY)) // 粘贴方式
{
echo(TEXT("BitBlt has failed"));
goto done;
}
// 获取位图信息并存放在 bmpScreen 中
GetObject(hbmScreen, sizeof(BITMAP), &bmpScreen);
bi.biSize = sizeof(BITMAPINFOHEADER);
bi.biWidth = bmpScreen.bmWidth;
bi.biHeight = bmpScreen.bmHeight;
bi.biPlanes = 1;
bi.biBitCount = 32;
bi.biCompression = BI_RGB;
bi.biSizeImage = 0;
bi.biXPelsPerMeter = 0;
bi.biYPelsPerMeter = 0;
bi.biClrUsed = 0;
bi.biClrImportant = 0;
dwBmpSize = ((bmpScreen.bmWidth * bi.biBitCount + 31) / 32) * 4 * bmpScreen.bmHeight;
// 在 32-bit Windows 系统上, GlobalAlloc 和 LocalAlloc 是由 HeapAlloc 封装来的
// handle 指向进程默认的堆. 所以开销比 HeapAlloc 要大
hDIB = GlobalAlloc(GHND, dwBmpSize);
lpbitmap = (char *)GlobalLock(hDIB);
// 获取兼容位图的位并且拷贝结果到一个 lpbitmap 中.
GetDIBits(
hdcScreen, // 设备环境句柄
hbmScreen, // 位图句柄
0, // 指定检索的第一个扫描线
(UINT)bmpScreen.bmHeight, // 指定检索的扫描线数
lpbitmap, // 指向用来检索位图数据的缓冲区的指针
(BITMAPINFO *)&bi, // 该结构体保存位图的数据格式
DIB_RGB_COLORS // 颜色表由红、绿、蓝(RGB)三个直接值构成
);
wsprintf(FilePath, "%s\%s.bmp", dirPath, filename);
// 创建一个文件来保存文件截图
hFile = CreateFile(
FilePath,
GENERIC_WRITE,
0,
NULL,
CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
NULL
);
// 将 图片头(headers)的大小, 加上位图的大小来获得整个文件的大小
dwSizeofDIB = dwBmpSize + sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);
// 设置 Offset 偏移至位图的位(bitmap bits)实际开始的地方
bmfHeader.bfOffBits = (DWORD)sizeof(BITMAPFILEHEADER) + (DWORD)sizeof(BITMAPINFOHEADER);
// 文件大小
bmfHeader.bfSize = dwSizeofDIB;
// 位图的 bfType 必须是字符串 "BM"
bmfHeader.bfType = 0x4D42; //BM
dwBytesWritten = 0;
WriteFile(hFile, (LPSTR)&bmfHeader, sizeof(BITMAPFILEHEADER), &dwBytesWritten, NULL);
WriteFile(hFile, (LPSTR)&bi, sizeof(BITMAPINFOHEADER), &dwBytesWritten, NULL);
WriteFile(hFile, (LPSTR)lpbitmap, dwBmpSize, &dwBytesWritten, NULL);
// 解锁堆内存并释放
GlobalUnlock(hDIB);
GlobalFree(hDIB);
// 关闭文件句柄
CloseHandle(hFile);
// 清理资源
done:
DeleteObject(hbmScreen);
DeleteObject(hdcMemDC);
ReleaseDC(NULL, hdcScreen);
return 0;
}
小结
如各位所见,实际上使用 Hook 的方式并不复杂,复杂是你要在 Hook 触发的时候做的事情,比如该程序中的截图功能是完整的无损截图,这样即耗时生成的图片又大,优化这个过程才是这个程序的难点。键盘监听也是类似的,我们只需要在键盘的 Hook 触发的时候去记录用户的输入即可,而这个功能的难点依旧不是设置 Hook 而是如何存储用户的按键,以及回头你怎么得到这个按键。而 Hook 只是给我们提供一个在某个特定的时机触发我们代码的机会,所以会用 Hook 并不代表什么,能把基础做好才是本事。
很多人在学习的时候很迷茫,不知道接下来要学什么,却总不知道回头看看自己到底会什么。总之,希望各位在学习的过程能够不断使用自己已经写过的东西去实践、应用,不要普通人像那花了 12 年的人生去记忆一些注定要忘记的东西。当你的功能代码写到了某个有感觉的程度之后,你所需要的,只是一个让你能调用这个代码机会。也许这个机会是一个 Hook,一个 APP 的灵感亦或者是一个发现你老板。
上一讲:木马,你好!(五)端口重用
下一讲:木马,你好!(七)远程弹出记事本写字