屏幕監控簡單說就是對進程的當前桌面進行截屏存成位圖,然後將此位圖數據傳輸到遠程。
對桌面進行截圖需要通過壹系列Windows GDI API來完成的。
首先通過CreateDC,CreateCompatibleDC,CreateCompatibleBitmap,SelectObject等API將“DISPLAY”驅動器的設備上下文與位圖句柄關聯起來。
然後通過GetStockObject,GetDC,SelectPalette等API處理調色板。
最後在壹個循環中通過GetDIBits將所有水平線像素數據存入到緩沖區中去。
這個緩沖區就是我們想要的位圖數據,只要將這些數據組織壹下,就可以當成位圖顯示出來了。通過連續傳輸位圖,就可以實時對遠程屏幕進行監控了。這個過程比較簡單,就不浪費文字了。
二、窗口站與桌面
首先必須了解幾個重要的概念:
窗口站(WindowsStation)和桌面(Desktop)是Windows操作系統底層暴露給Windows API的執行體對象(Windows內部有兩種類型的對象:執行體對象和內核對象。執行體對象指由執行體的各種組件如進程管理器、內存管理器等等所實現的對象。內核對象是由Windows內核實現的壹組更基本的對象)。
其中,窗口站對象包含了壹個剪貼板、壹組全局原子和壹組桌面對象。桌面對象是壹個被包含在窗口站內部的對象,桌面對象有壹個邏輯顯示器表面,其中包含了窗口、菜單和鉤子。
0號窗口站(WinSta0)和默認的桌面對象(default desktop)是有Winlogon進程創建的。窗口站是會話(Session)的下壹層組織結構。壹個會話可以有多個窗口站,但同壹時刻只能有壹個窗口站可以與用戶進行交互。每個窗口站有自己的剪貼板,可以有多個桌面。Winlogon進程調用NtUserCreateWindowsStation函數創建窗口站,再調用NtUserCreateDesktop來創建桌面。它首先會創建壹個名為Winlogon的桌面供自己使用(Windows登錄界面就屬於屬於這個桌面),然後再創建壹個名為Default的桌面給應用程序使用。創建完桌面後,Winlogon調用SetActiveDesktop函數將Winlogon桌面設置為當前的活動桌面。
之後,Winlogon會創建用於管理系統服務的服務管理器(Service.exe)和本地安全認證子系統(LSASS.exe)。用戶登陸信息被驗證後,Winlogon會將應用程序桌面激活,啟動UserInit程序,UserInit會運行註冊表中定義的登錄腳本,然後啟動操作系統外殼程序(Shell-默認是explorer.exe)。這是SYSTEM權限進程和普通用戶進程邏輯顯示器桌面分離的開始。在以後進程創建CreateProcess的過程中,如果沒有指定桌面,那麽進程就會與調用者的當前桌面關聯在壹起。
在實際測試中,發現services、svchost這些進程似乎沒有關聯任何桌面(截的屏都是黑屏)。普通的進程都是Default桌面,登錄界面是Winlogon桌面。所以,當dll插入到service.exe等進程中的時候,要想實現截屏必須將進程與Default桌面關聯,用戶註銷、離開或未登錄時就要將進程與Winlogon桌面關聯。
Windows給我們提供的壹些API允許我們幹這些事。
首先可以通過OpenWindowStation打開壹個窗口站對象,然後通過SetProcessWindowStation將進程與窗口站關聯,通過OpenDesktop打開壹個桌面對象,再通過SetThreadDesktop將線程與這個桌面關聯。這樣service.exe就可以實現截屏了。但如何才能知道當前用戶在哪個桌面呢?可以通過下列函數實現:
OpenInputDesktop(DF_ALLOWOTHERACCOUNTHOOK, FALSE, MAXIMUM_ALLOWED);//打開輸入桌面
GetUserObjectInformation(hActiveDesktop, UOI_NAME, pvInfo, sizeof(pvInfo), &dwLen); //獲取指定桌面對象的信息,壹般情況和屏保狀態為default,登陸界面為winlogon
pvInfo緩沖區包含的就是當前桌面。這樣就可以放心的調用OpenDesktop打開它了。
完整代碼如下:
BOOL OpenDesktop(LPCWSTR szName)
{
WCHAR pvInfo[128] = {0};
WCHAR tmp[1024] = {0};
if(szName != NULL)
lstrcpy(pvInfo, szName);
else
{
HDESK hActiveDesktop;
DWORD dwLen;
hActiveDesktop = OpenInputDesktop(DF_ALLOWOTHERACCOUNTHOOK, FALSE, MAXIMUM_ALLOWED);
if(!hActiveDesktop)//打開失敗
{
return FALSE;
}
//獲取指定桌面對象的信息,壹般情況和屏保狀態為default,登陸界面為winlogon
GetUserObjectInformation(hActiveDesktop, UOI_NAME, pvInfo, sizeof(pvInfo), &dwLen);
if(dwLen==0)//獲取失敗
{
return FALSE;
}
CloseDesktop(hActiveDesktop);
//打開winsta0
m_hwinsta = OpenWindowStation(_T("winsta0"), FALSE,
WINSTA_ACCESSCLIPBOARD |
WINSTA_ACCESSGLOBALATOMS |
WINSTA_CREATEDESKTOP |
WINSTA_ENUMDESKTOPS |
WINSTA_ENUMERATE |
WINSTA_EXITWINDOWS |
WINSTA_READATTRIBUTES |
WINSTA_READSCREEN |
WINSTA_WRITEATTRIBUTES);
if (m_hwinsta == NULL){
return FALSE;
}
if (!SetProcessWindowStation(m_hwinsta)){
return FALSE;
}
//打開desktop
m_hdesk = OpenDesktop(pvInfo, 0, FALSE,
DESKTOP_CREATEMENU |
DESKTOP_CREATEWINDOW |
DESKTOP_ENUMERATE |
DESKTOP_HOOKCONTROL |
DESKTOP_JOURNALPLAYBACK |
DESKTOP_JOURNALRECORD |
DESKTOP_READOBJECTS |
DESKTOP_SWITCHDESKTOP |
DESKTOP_WRITEOBJECTS);
if (m_hdesk == NULL){
return FALSE;
}
SetThreadDesktop(m_hdesk);
return TRUE;
}
代碼有點亂,將就壹下!
三、後記
上面的代碼只是針對service.exe這樣的進程,要想做的通用還要再加些代碼。