屏幕监控
通过对目标计算机进行截屏,并回传截屏数据,有利于黑客通过恶意代码直接了解目标计算机的工作状态,以监控计算机的屏幕。截屏频率够快甚至还能连成一段视频。
大致过程
大致可分为3步:
- 绘制桌面位图
- 绘制鼠标光标
- 保存位图图像
绘制桌面位图
windows程序在屏幕上绘图时,它并不是将像素直接输出到设备上,而是将图绘制到由DC(设备上下文)表示的具有逻辑意义的“显示平面”上。
DC是windows中的一种数据结构,包括GDI需要的所有与显示界面相关的描述字段,包括
相连的物理设备的各种状态信息。
windows程序从GDI(图形设备接口)获取设备上下文句柄(HDC),并每次调用完GDI输出函数后将句柄返回给HDC。
该过程流程如下:
- 获取桌面窗口句柄(
GetDesktopWindow
),获取桌面窗口设备上下文句柄(GetDC
),创建与桌面窗口兼容的内存上下文(CreateCompatibleDC
),用来绘制位图。 - 获取计算机屏幕的宽和高的像素值(
GetSystemMetrics
),创建兼容位图句柄(CreateCompatibleBitmap
),并将其作为绘制桌面画面的位图句柄。 - 将上述创建的兼容位图的句柄设置到兼容内存设备的上下文中(
SelectObject
),把桌面窗口设备上下文的内容绘制到兼容内存设备的上下文中(BitBlt
)。这样,兼容位图的内容便是桌面画面的内容。
绘制鼠标光标
使用GDI获取的桌面截图并不包括鼠标。所以需要程序单独在桌面位图上绘制鼠标光标。流程如下:
- 获取鼠标光标信息
CURSORINFO
(GetCursorInfo
),包括鼠标此时的位置信息,鼠标的光标句柄等。 - 根据光标句柄,获取光标对应的图标信息
ICONINFO
(GetIconInfo
) - 2次分别经过前景图和屏蔽图的绘制(
BitBlt
)(前景图和屏蔽图具体可参考windows黑客编程技术详解)
保存位图图像
可以使用基于CImage
类的方式保存图像,需要引入头文件atlimage.h
(VC6.0不支持),流程如下:
- 将位图句柄附加到
CImage
对象上(CImage::Attach
)。 - 保存图像文件(
CImage::Save
,该方法支持PNG,JPG,GIF,BMP等格式图片的生成)。
相关API
GetDesktopWindow函数
该函数返回桌面窗口的句柄。桌面窗口覆盖整个屏幕。桌面窗口是一个要在其上绘制所有的图标和其他窗口的区域。
HWND WINAPI GetDesktopWindow(VOID);
- 返回值
- 桌面窗口句柄
GetDC函数
Windows不允许程序员直接访问硬件,它对屏幕的操作是通过环境设备,也就是DC来完成的。屏幕上的每一个窗口都对应一个DC,可以把DC想象成一个视频缓冲区,对这这个缓冲区的操作,会表现在这个缓冲区对应的屏幕窗口上。
GetDC函数为一个指定窗口的客户端区域或者整个屏幕从一个设备上下文(DC)中提取一个句柄。你可以使用这个返回的句柄。
HDC WINAPI GetDC(__in_opt HWND hWnd);
-
参数
- hWnd:检索设备上下文环境的句柄。如果为
NULL
,则GetDC
将检索整个屏幕的设备上下文环境。
- hWnd:检索设备上下文环境的句柄。如果为
-
返回值
- 执行成功,返回指定窗口客户端区域的DC的句柄。
- 执行失败,返回
NULL
。
CreateCompatibleDC函数
该函数创建一个与指定设备兼容的内存设备上下文环境(相当于创建一个自己可控制的DC)
HDC WINAPI CreateCompatibleDC( __in_opt HDC hdc);
-
参数
- hWnd:现有设备上下文环境的句柄,如果该句柄为
NULL
,该函数创建一个与应用程序的当前显示器兼容的内存设备上下文环境。
- hWnd:现有设备上下文环境的句柄,如果该句柄为
-
返回值
- 执行成功,返回内存设备上下文环境的句柄。
- 执行失败,返回
NULL
。
GetSystemMetrics函数
用于得到被定义的系统数据或者系统配置信息。
int WINAPI GetSystemMetrics(__in int nIndex);
- 参数
- nIndex:一个索引,这个索引有75个标识符,通过设置不同的标识符就可以获取系统分辨率、窗体显示区域的宽度和高度、滚动条的宽度和高度等信息。
下面列举一些系统信息,具体可参考C# API GetSystemMetrics(int nIndex)用法及值说明
值 | 含义 |
---|---|
SM_CMOUSEBUTTONS | 返回值为系统支持的鼠标键数,返回0,则系统中没有安装鼠标 |
SM_CXSCREEN | 屏幕宽度 |
SM_CYSCREEN | 屏幕高度 |
SM_CXFULLSCREEN | 获取最大化窗体的显示区域宽度 |
SM_CYFULLSCREEN | 获取最大化窗体的显示区域高度 |
- 返回值
- 相关系统信息。
CreateCompatibleBitmap函数
该函数创建与指定的设备环境相关的设备兼容的位图
HBITMAP WINAPI CreateCompatibleBitmap(
__in HDC hdc,
__in int nWidth,
__in int nHeight);
-
参数
- hdc:设备环境句柄。
- nWidth:指定位图的宽度,单位为像素。
- nHeight:指定位图的高度,单位为像素。
-
返回值
- 执行成功,返回位图的句柄。
- 执行失败,返回
NULL
。
SelectObject函数
把一个对象(位图、画笔、画刷等)选入指定的设备描述表。新的对象代替同一类型的老对象。
HGDIOBJ WINAPI SelectObject(
__in HDC hdc,
__in HGDIOBJ hgdiobj);
-
参数
- hdc:设备描述表句柄(要载入的设备描述表句柄)。
- hgdiobj:选择要载入的对象的句柄。
-
返回值
- 选择对象不是区域并且函数执行成功,返回被取代的对象的句柄。
- 选择对象是区域并且函数执行成功,返回如下一值。
BitBlt函数
将一幅位图从一个设备场景复制到另一个。
BOOL WINAPI BitBlt(
__in HDC hdcDest,
__in int nXDest,
__in int nYDest,
__in int nWidth,
__in int nHeight,
__in_opt HDC hdcSrc,
__in int nXSrc,
__in int nYSrc,
__in DWORD rop);
- 参数
- hdcDest:指向目标设备环境的句柄
- nXDest:指定目标矩形区域左上角X轴逻辑坐标
- nYDest:指定目标矩形区域左上角Y轴逻辑坐标
- nWidth:指定源和目标矩形区域的逻辑宽度
- nHeight:指定源和目标矩形区域的逻辑高度
- hdcSrc:指向源设备环境的句柄
- nXSrc:指定源矩形区域左上角X轴逻辑坐标
- nYSrc:指定源矩形区域左上角Y轴逻辑坐标
- rop:指定光栅操作代码。这些代码将定义源矩形区域的颜色数据,如何与目标矩形区域的颜色数据组合以完成最后的颜色。
下面列出了一些常见的光栅操作代码
值 | 含义 |
---|---|
SRCCOPY | 将源矩形区域直接拷贝到目标矩形区域 |
SRCERASE | 通过使用AND(与)操作符将目标矩形区域颜色取反后与源矩形区域的颜色值合并 |
SRCINVERT | 通过使用布尔型的XOR(异或)操作符将源和目标矩形区域的颜色合并 |
SRCPAINT | 通过使用布尔型的OR(或)操作符将源和目标矩形区域的颜色合并 |
WHITENESS | 使用与物理调色板中索引1有关的颜色填充目标矩形区域。(对于缺省物理调色板来说,这个颜色就是白色) |
- 返回值
- 操作成功,返回
true
- 操作失败,返回
false
- 操作成功,返回
GetCursorInfo函数
获取光标信息
BOOL WINAPI GetCursorInfo(
__inout PCURSORINFO pci);
参数:
- pci:指向光标的结构体
光标结构体定义如下:
typedef struct {
DWORD cbSize;
DWORD flags;
HCURSOR hCursor;
POINT ptScreenPos;
} CURSORINFO, *PCURSORINFO, *LPCURSORINFO;
GetIconInfo函数
获取光标图标信息
BOOL WINAPI GetIconInfo(
__in HICON hIcon,
__out PICONINFO piconinfo);
示例代码
#include <Windows.h>
#include <atlimage.h>
#include <stdio.h>
#include <tchar.h>
#include <SDKDDKVer.h>
// 包括 SDKDDKVer.h 将定义可用的最高版本的 Windows 平台。
// 如果要为以前的 Windows 平台生成应用程序,请包括 WinSDKVer.h,并将
// WIN32_WINNT 宏设置为要支持的平台,然后再包括 SDKDDKVer.h。
BOOL SaveBmp(HBITMAP hBmp)
{
CImage image;
// 附加位图句柄
image.Attach(hBmp);
// 保存成jpg格式图片
image.Save("mybmp1.jpg");
return TRUE;
}
BOOL PaintMouse(HDC hdc)
{
HDC bufdc = NULL;
CURSORINFO cursorInfo = { 0 };
ICONINFO iconInfo = { 0 };
HBITMAP bmpOldMask = NULL;
bufdc = ::CreateCompatibleDC(hdc);
::RtlZeroMemory(&iconInfo, sizeof(iconInfo));
cursorInfo.cbSize = sizeof(cursorInfo);
// 获取光标信息
::GetCursorInfo(&cursorInfo);
// 获取光标图标信息
::GetIconInfo(cursorInfo.hCursor, &iconInfo);
// 绘制 白底黑鼠标(AND)
bmpOldMask = (HBITMAP)::SelectObject(bufdc, iconInfo.hbmMask);
::BitBlt(hdc, cursorInfo.ptScreenPos.x, cursorInfo.ptScreenPos.y, 20, 20,
bufdc, 0, 0, SRCAND);
// 绘制 黑底彩色鼠标(OR)
::SelectObject(bufdc, iconInfo.hbmColor);
::BitBlt(hdc, cursorInfo.ptScreenPos.x, cursorInfo.ptScreenPos.y, 20, 20,
bufdc, 0, 0, SRCPAINT);
// 释放资源
::SelectObject(bufdc, bmpOldMask);
::DeleteObject(iconInfo.hbmColor);
::DeleteObject(iconInfo.hbmMask);
::DeleteDC(bufdc);
return TRUE;
}
BOOL ScreenCapture()
{
// 获取桌面窗口句柄
HWND hDesktopWnd = ::GetDesktopWindow();
// 获取桌面窗口DC
HDC hdc = ::GetDC(hDesktopWnd);
// 创建兼容DC
HDC mdc = ::CreateCompatibleDC(hdc);
// 获取计算机屏幕的宽和高
int dwScreenWidth = ::GetSystemMetrics(SM_CXSCREEN);
int dwScreenHeight = ::GetSystemMetrics(SM_CYSCREEN);
// 创建兼容位图
HBITMAP bmp = ::CreateCompatibleBitmap(hdc, dwScreenWidth, dwScreenHeight);
// 选中位图
HBITMAP holdbmp = (HBITMAP)::SelectObject(mdc, bmp);
// 将窗口内容绘制到位图上
::BitBlt(mdc, 0, 0, dwScreenWidth, dwScreenHeight, hdc, 0, 0, SRCCOPY);
//绘制鼠标
PaintMouse(mdc);
//保存为图片
SaveBmp(bmp);
return true;
}
int _tmain(int argc, _TCHAR* argv[])
{
if (FALSE == ScreenCapture())
{
printf("Screen Cpature Error.\n");
}
printf("Screen Cpature OK.\n");
system("pause");
return 0;
}
运行结果
可以看到编译后的exe文件的目录下生成了mybmp1.jpg
截屏一部分内容如下:
IDA查看
main函数
ScreenCapture函数
PaintMouse函数,可以看到形参类型还是HDC
SaveBmp函数,这个就比较有意思了,形参成了int
类型(本来HBITMAP
类型也是句柄),CImage
类的成员方法也没显示出来
点进sub_43B8F6
,之后一路点进
到处都有头文件路径,超出知识范围,希望有大神赐教下。
参考
- windows黑客编程技术详解