• 如何在SYSTEM权限下实现屏幕监控(转)


    屏幕监控是远控软件的基本功能之一。
    现在很多远控程序的服务端通常为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这样的进程,要想做的通用还要再加些代码。

    http://hi.baidu.com/iprowq/blog/item/9ba8d655c13d3eccb745aeac.html

  • 相关阅读:
    java中如何高效的判断数组中是否包含某个元素---
    反射--
    Json----
    Ajax学习(1)
    Jdbc学习---
    java---内部类
    java中的多态
    spring是什么
    quartz的配置文件
    浅谈Job&JobDetail
  • 原文地址:https://www.cnblogs.com/ryhan/p/2111528.html
Copyright © 2020-2023  润新知