Target Eye 揭秘:第二部分 - Target Eye 的屏幕捕获






4.96/5 (49投票s)
Target Eye 的屏幕捕获机制如何工作
投票选为 2012 年 9 月最佳 C++ 文章
引言
屏幕捕获是任何监控产品中的主要组成部分之一。Target Eye 监控系统,自 2000 年至 2010 年开发,拥有其独特的 JPG 屏幕截图。本文是 Target Eye 揭秘 系列的第二篇文章。第三篇文章(紧随本文之后)是关于 购物清单机制。第四篇文章是关于 键盘捕获,第五篇文章是关于 封面故事机制。 第六篇文章 是关于 Target Eye 的文件隐藏方式。
背景
我十二年前开发的 Target Eye 监控系统,可能是第一个用于捕获远程计算机活动的监控工具。以下描述摘自该项目最初的商业计划书。
Target Eye 是一家初创公司,其使命是开发基于公司专利申请中技术(60/204,084 和 60/203,832)的实时远程 PC 监控集成软件解决方案。我们的主要产品 Target Eye 监控系统是一款软件产品,能够以用户无法察觉的方式,持续跟踪、记录、回放、分析和报告在一台或多台远程 PC 上执行的任何活动。该软件依赖于快速捕获、压缩的全屏图像流和连续的键盘输入捕获,以提供用户活动的全面准确记录,包括不产生任何网络流量的本地活动。通过这种方式,该软件可以跟踪和记录网络流量分析系统无法检测到的活动。在被监控 PC 上运行的智能代理模块使用规则库将警报发送到监控位置,或执行预定义的本地操作。监控可以从多个位置进行。主要市场是执法部门。
Target Eye 的组成部分
Target Eye 监控系统由以下几个术语和组成部分构成。
- Target Eye Secret Agent - 该产品的隐蔽部分,运行在被监控的计算机上。
- Target Eye 操作员 - 操作秘密代理的权威(例如:执法机构)。
- 任务 - 执行某项操作的请求。有许多可能的操作(例如,本文中描述的屏幕捕获,或本文中描述的文件搜索和查找)。
- Target Eye Compiler - Target Eye Operator 用于自定义 Secret Agent 以执行特定任务的工具。
屏幕捕获的目的
屏幕捕获的目的只有一个,那就是提供被监控计算机在特定时刻活动的准确图像。这是通过抓取屏幕上显示的图像,并为其添加水印来实现的,水印反映了任何有助于以后将截图用作证据的其他数据:此类附加数据可以是日期和时间戳、时区、唯一的顺序编号以及任何附加标签,例如活动应用程序的名称或作为“热词”机制一部分捕获的特定按键序列(例如:“本·拉登
”)。
Target Eye 屏幕截图文件
屏幕捕获文件应大小小巧但仍可读。JPG 格式通过 1 到 100 之间的数值来设置图像质量。此值还为屏幕截图创建了独特的外观,使其几乎不可能伪造或篡改,从而确保每个截图文件都是真实的。Target Eye 使用其自己独特的不完美屏幕。
取自 2000 年 5 月的早期版本之一。如需原始图像,请单击此处。
这是我笔记本电脑在 2003 年 12 月截取的缩小尺寸屏幕截图。您可以看到打开了 Visual C++ 6.0 和 Target Eye 源代码……要查看原始图像,请单击此处。
屏幕捕获步骤
要捕获屏幕,应执行以下步骤:
获取 DC
DC 是 Device Contents(设备上下文)的缩写。GetDC
函数检索指定窗口客户区或整个屏幕的设备上下文 (DC) 的句柄。要获取给定窗口的 DC,请调用
HDC hDC = GetDC(hWnd);
在本例中,我们将“0
”传递给 GetDC
,以捕获整个桌面。
HDC hDC = GetDC(0);
返回的句柄在后续的 GDI 函数中用于在 DC 中绘图。设备上下文是一个不透明的数据结构,其值由 GDI 内部使用。
创建兼容内存 DC
现在,我们通过调用来创建一个与我们捕获的 DC 兼容的已分配内存。
HDC CreateCompatibleDC(
HDC hdc
);
我们还存储要捕获区域的 `width`(宽度)和 `height`(高度)。
int ScreenWidth = GetDeviceCaps(CI.m_hScreenDC, HORZRES);
int ScreenHeight = GetDeviceCaps(CI.m_hScreenDC, VERTRES);
创建兼容位图
通过调用 CreateCompatibleBitmap
,我们创建一个与指定设备上下文关联的设备兼容的位图。
HBITMAP CreateCompatibleBitmap(
__in HDC hdc,
__in int nWidth,
__in int nHeight
);
SelectObject
用于将数据从屏幕复制到内存。
HGDIOBJ SelectObject(
HDC hdc,
HGDIOBJ hgdiobj);
调用 BitBlt
BitBlt
函数用于捕获区域的位块传输,通过将每个颜色的全部数据传输到目标设备上下文。
BOOL BitBlt(
__in HDC hdcDest,
__in int nXDest,
__in int nYDest,
__in int nWidth,
__in int nHeight,
__in HDC hdcSrc,
__in int nXSrc,
__in int nYSrc,
__in DWORD dwRop
);
此时,我们可以将时间戳和任何其他数据添加到捕获的图像上,如以下各节所述,但首先,让我们解释一下我们为什么以及如何使用 ImgSource
。
ImgSource - Target Eye 屏幕捕获使用的 SDK
Target Eye 的屏幕截图是通过一个名为 ImgSource 的图形库创建的,该库由 Smaller Animals 提供,这是一家成立于 1993 年、于 1999 年注册的私营公司,总部位于 北卡罗来纳州研究三角园区。我拥有该库的完整源代码许可证,因为像 Target Eye 这样的产品(以及购买它的客户,如执法机构)要求拥有所有组件的完整源代码,没有任何“黑盒子”。
总裁 Chris Losinger 自 2000 年以来提供了大量的帮助。需要注意的是,ImgSource
不是开源库,也不是免费产品,但我强烈推荐它。未注册用户可以测试该库,但图像上会绘制一个大的红色 **X**(包括本文附带的源代码)。该库拥有众多功能,以下是部分列表:
图像文件函数
| 图像处理函数
|
CaptureScreen 函数
Target Eye 的屏幕截图是通过调用 CaptureScreen()
函数生成的,该函数用于将整个屏幕捕获到本地隐藏位置的 JPG 文件中,然后将其发送到 FTP 服务器。
Target Eye 的第一个版本于 2000 年 5 月发布,并具有一个简单的屏幕捕获例程,该例程将文件保存为 *.bmp 格式,文件体积庞大,如果我们希望每分钟捕获一次屏幕,就会造成问题……后来,它被替换为一种有损的 JPG 文件,从而解决了这个问题。以下函数经历了漫长的 QA 过程,因为 Windows 的 GDI 会消耗一些内存,如果此类例程运行数天,由于 GDI 中的 bug,应用程序会消耗计算机资源,但通过仔细分配和释放必要资源并正确管理内存分配,这个问题得到了解决。
static HDC ScreenDC=NULL;
static HDC hmemDC=NULL;
static HBITMAP hmemBM=NULL;
static HGLOBAL hRGB=NULL;
void SimpleCapture()
{
ScreenDC = GetWindowDC(0);
if (ScreenDC != NULL)
{
hmemDC = CreateCompatibleDC(ScreenDC);
if (hmemDC != NULL)
{
int ScreenWidth = GetDeviceCaps(ScreenDC, HORZRES);
int ScreenHeight = GetDeviceCaps(ScreenDC, VERTRES);
hmemBM = CreateCompatibleBitmap(ScreenDC, ScreenWidth, ScreenHeight);
if (hmemBM != NULL)
{
HGDIOBJ result=SelectObject(hmemDC, hmemBM); // copy screen to memory DC
if((ULONG)result!=0L && (ULONG)result!=GDI_ERROR)
{
GdiFlush();
if((BitBlt(hmemDC, 0, 0, ScreenWidth,
ScreenHeight, ScreenDC, 0, 0, SRCCOPY)))
{
hRGB = ISHBITMAPToRGB(hmemBM, (unsigned int *)&ScreenWidth,
(unsigned int *)&ScreenHeight, hmemDC, NULL);
if (hRGB)
{
// for grayscale
//.. RGB -> JPG
HISDEST hDest = ISOpenFileDest("c:\\test.jpg");
if(hDest!=NULL)
{
static ULONG ImageCount=0;
int Wrote=ISWriteJPG(hDest, (BYTE *)hRGB,
ScreenWidth, ScreenHeight, 25, 1, 24, NULL);
ISCloseDest(hDest);
TRACE("Created Image %ld\n",ImageCount++);
}
//.. clean up
GlobalFree(hRGB);
hRGB=NULL;
}
else
{
// handle error
}
}
}
DeleteObject(hmemBM);
hmemBM=NULL;
}
DeleteDC(hmemDC);
hmemDC=NULL;
}
DeleteDC(ScreenDC);
ScreenDC=NULL;
}
}
如您所见,此版本中设置的“Quality
”(质量)属性为 25
(满值为 100
)。
创建头部
自 Target Eye 2003 年和 2004 年版本以来,创建了一个包含屏幕截图宝贵信息的独特头部。该头部在屏幕图像被捕获到内存之后,并且在创建 JPG 文件之前创建。
// Now we create the header which is stamped on top of the screen
sprintf(s,"%ld",cycle);
CString FormattedTime=(CString)CTime::GetCurrentTime().FormatGmt
(L" %A, %d.%m.%y Time Zone: %z %H:%M Label = ");
if(TextToWrite!="") FormattedTime+=TextToWrite;
FormattedTime += s;
HPEN hNewPen;
hNewPen=CreatePen(PS_SOLID, 1, RGB(233,238,45));
SelectObject(CI.m_hmemDC, hNewPen);
TextOut(CI.m_hmemDC,0,0,FormattedTime,FormattedTime.GetLength());
命名文件
监控应用程序应有一个命名其创建的各种文件的方案,包括捕获的屏幕。附加源代码中的函数提供了一个非常简单的文件命名方案,基于文本“Screen”,后跟一个顺序编号和“.jpg”扩展名。
CString ComposeFileName(int i)
{
CString result;
result.Format(L"%s%d%s",(CString)L"screen",i,(CString)L".jpg");
return result;
}
Target Eye 使用一个更复杂的系统,该系统基于计数“周期”(即应用程序到达主事件循环的时间)。周期每 X 秒发生一次,X 是计算出的任何信息被请求捕获的最短时间。例如,如果操作员请求每 5 分钟复制一次文件,每 10 分钟捕获一次屏幕,并且没有其他定期信息收集请求,那么周期将是每 5 分钟。这是通过使用 Timer
设置的。
使用源代码
本文附加的源代码基于 Target Eye 的一个新版本,该版本是 100% Win32API(不使用 MFC)并支持 UNICODE。 以下屏幕截图是使用附加代码创建的,但为了发布此源代码,我删除了我自己的个人许可证密钥,因此屏幕截图顶部会有一个红色的 **X**。
运行一款电脑游戏(《大蓝熊的家》冒险),这是我从女儿 Emma 那里借来的,连续运行了数天,这使得我们能够确保屏幕捕获功能足够稳定,没有内存泄漏。
历史
- 2014 年 6 月 12 日:初始版本
Michael Haephrati ,2013 年度 CodeProject MVP
©2000-2010 Target Eye LTD (UK)