屏幕監控是遠控軟件的基本功能之一。
現在很多遠控程序的服務端通常為DLL形式,通過遠程線程注入等方法插入到services、svchost等SYSTEM權限的進程中去,而此時常規的屏幕監控就會失效(這是因為與SYSTEM權限進程關聯的窗口站、桌面與普通進程不同)。
如何才能在SYSTEM權限下實現屏幕監控呢?

一、 屏幕監控的基本原理

屏幕監控簡單說就是對進程的當前桌面進行截屏存成位圖,然后將此位圖數據傳輸到遠程。

對桌面進行截圖需要通過一系列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這樣的進程,要想做的通用還要再加些代碼。

?