交互式服务编程的项目心得及总结

交互式编程

                                  ----一个工程的心得总结

我们的项目会在Windows NT/2000/XP/Vista 系统中安装一个系统服务,,这个服务负责以 SYSTEM 权限启动我们的主程序,主程序和服务器通信。其中的两项功能带给我很大的困扰。一是有主程序实现计算机当前屏幕的截图功能,同时要求将当前屏幕通过主程序返回给服务器。另一个是由主程序启动另一个进程,我们称之为副进程吧,该进程同样与服务器通信,接收来自服务器的消息并以窗体的形式表现在桌面上。

我们来看看Windows NT/2000系统。在 Windows NT/2000 中系统服务进程和本机控制台交互式登录的用户都运行于Session0 中,默认用户桌面运行于 WinSta0 窗口站。这时候首先屏幕截图功能出现了问题。问题在当系统出于锁定状态或者登陆状态时,直接的屏幕截图方法所取得的桌面是一个黑色的屏幕。经过分析,这是由于在锁定状态或者登陆状态时,系统是出于winlogon桌面的,而我们的服务仍然按照当前用户的桌面进行抓取,可想而知,必然获得是一片黑色的屏幕。解决的办法就是,在抓取屏幕前进行桌面的选择,方法在白水绕东城大侠http://blog.csdn.net/felixz/archive/2006/10/23/1346380.aspx的文章中已经提到过,就是MSDN OpenInputDesktop, SetThreadDesktop API函数,具体选择桌面的方法可以见另一篇文章“计算机当前屏幕截取”。这里提一下,他用这些Api函数来解决的是系统托盘图标的显示和对话框的弹出所可能存在的问题。至于我们的副进程,在主进程中的启动直接使用的是系统API函数CreateProcess来实现的,因此在系统中运行时也将是从主进程“继承”来的系统权限。因此我们的副进程和本机控制台当前登陆的用户就是同事运行在session0中的,因此是不存在问题的。

再看Windows XP系统。这是白大侠软件中存在的问题,当 XP 具备了快速用户切换功能的时候我们的问题已经出现了。XP 启动后我们以用户 A 登录,我们的图标出现在系统托盘,一切工作都正常,可当我们使用快速用户切换,切换到用户B后(用户A此时也是已登录状态,并没有注销),虽然用户B已经是本地控制台会话(Session 属性为 Console)但我们的图标已经无法出现了,自然菜单和对话框更无从谈起了。我们的程序是和本机控制台桌面相关的,这种情况无疑是个缺陷。这里来看看白大侠的分析,当我们快速切换到用户B的时候,用户A仍然在会话中(Session0),而用户B则处于新启动的会话中(Session1或者其他),此时服务程序和本机控制台程序就不在处于同一会话了,OpenInputDesktop,SetThreadDesktop API的工作范围仅限于本Session,用户A没有退出,Session0也依然存在但是已经是 Disconnected 状态,当进程所处的Session Disconnected 状态的时候调用 OpenInputDesktop 会返回错误无效的API”。进程及线程所属的Session 是由他们的Token 结构中的 TokenSessionId 决定的(参见MSDNSetTokenInformation TOKEN_INFORMATION_CLASS的说明),我尝试以微软提供的相关API修改运行中的进程和线程的TokenSessionId 信息从而达到修改桌面环境的目的,到目前还没有成功过(或许可以尝试参考RootKit 技术,不过即使修改成功到底能不能实现我们的需求也不确定)。我们的进程无法跨越Session的界限,自然无法与当前活动的另外一个Session中的桌面交互了。

从白大侠的分析中,可以看到在XP下与用户桌面交互的缺陷。这里还来看一下我们的项目,我们的项目没有托盘图标,但是同样存在着对话框的弹出。我们的副进程以服务进程(SYSTEM)的权限运行在系统中,当我们首先登陆一个用户A时,我们的项目也是一切正常的,接收到服务器的消息可以立即以对话框的形式弹出,以通知用户。但是当我们切换到用户B时,问题同样出现了,服务器发出的消息副进程接收到了,但是没有在桌面上表现。然后我们回到用户A发现,消息在用户A的桌面上已经表现了。分析可以看出,我们的副进程和用户A同时运行在Session0中,因此才会表现数据。而用户B此时却运行在sessionNN>=1)中,副进程和用户B桌面是无法交互的,这才是导致消息无法通过对话框弹出的原因,这正验证了白大侠的分析。这个问题我们可以先保留,在后面继续分析并讨论解决方法。这里给出一些参考,如uvnc在这方面的说明, In the old model, winlogon was always running in the same session as the services, session0

    Windows vista系统推出了一种新的安全模式。While in the new model, the winlogon run in the same session as the desktop.

The isolation of the session0, now only used for services, prevent winvnc in service mode to access session X and no interaction with the desktop in session X is possible.

出于安全方面及其他因素的考虑,Vista以及将所有的服务程序置于Session0中,而为本机第一个交互登录的用户创建了Session1,快速切换到用户B后则是 Session2,无论是本机登录的用户,快速切换后的用户,还是远程桌面登录的用户再也没有谁和服务进程处于同一个Session中了。那么此时,服务将无法和任何一个用户桌面进行直接的交互了。这时,白大侠的软件和我们的项目同样面临这一个问题,我们的程序必须运行在Session0而我们又没有办法把我们的图标、对话框一下子就抛到隔壁Session的用户桌面上去。当我遇到这个问题的时候,并未想到如何解决,后来有人告诉了我白大侠的这篇文章,于是方案就有了。微软也不提倡我们这种服务程序直接提供GUI与用户直接交互的方式,而他们建议使用C/S架构,Client/Server之间用Socket/Pipe/RPC等方式通讯,这样我们只要把Client整个进程放到当前用户Session去和用户交互,然后将配置信息等内容通过上述途径传递给Server,服务端在作出相应的响应即可。这样我们就有了可以设计的方向,同时,我们可以看到,这个解决方案同样将windows XP系统的Fast user switching带给我们的问题解决了。下面看看我们如何应用这个方案。

这样方案就比较清晰了,我们把需要和当前用户交互的部分从服务中分离出去,作为一个独立的进程。在程序启动时,我们找到当前活动的session,在该session中来启动我们的独立进程。启动该进程时,我们还可以根据需求进程启动权限的判断。如果我们不需要一些比较高级的权限来和当前用户做一些工作,我们可以直接通过该用户所在sessionid获取该session的用户令牌,通过它我们可以以当前用户的权限启动该进程。如果我们需要一些比较高级的权限进行操作,我们可以以当前sessionSYSTEM权限启动该程序。这时候我们就需要复制服务也就是我们的主进程的Tokenup(用户令牌),然后修改其中的TokenSessionId为当前的sessionID,然后通过这个Tokenup创建我们的独立进程,此时,该独立进程即获得了当前session下的SYSTEM权限。独立进程启动以后,如何与服务器通信,笔者就不在这里赘述了。

此时如果认为工作已经完成,那就错了,我们还有一项必须的工作,那就是进行Session变化的监控,也就是用户的登陆,注销和快速切换,以便及时在相应的session中启动我们的独立进程。笔者很认同白大侠给出的三个解决方法:

1.  设置一个定时器,使用WTSGetActiveConsoleSessionId()轮询活动桌面id,当检测到变化的时候让用户交互程序的前一个实例退出,在新活动Session中创建新进程。

2. 使用WTSRegisterSessionNotification()函数注册一个窗口来接收WTSSESSION_NOTIFICATION消息,来判断Session变化。

3. 使用 WTSEnumerateSessions枚举所有Session然后根据返回的WTS_SESSION_INFO结构中的State成员来判断Session状态,找到处于 Active状态的Session.

笔者写这篇文章,主要是出于工作后的心得总结。这里面有两篇资料提供了很大的帮助。这里提供他们的链接,同时笔者也会将他们摘录到另外的文章里。

http://blog.csdn.net/felixz/archive/ 2006/10/23 /1346380.aspx

http://www.uvnc.com/vista/

下面是笔者创建当前session中独立进程的方法:

bool CreateProInCurrentUser(std::string ExePath)创建当前用户下/SYWTEM权限进程

       {

           HANDLE hTokenThis = NULL;

           HANDLE hTokenDup = NULL;

           HMODULE  hlibkernel = NULL;

           HANDLE hThisProcess = GetCurrentProcess();

           bool   bResult = false;

           LPVOID pEnv = NULL;

           string path;

 

           GetDebugPriv();

 

           if(!OpenProcessToken(hThisProcess, TOKEN_ALL_ACCESS, &hTokenThis))

           {

              goto EXITFIN;

           }

if(!DuplicateTokenEx(hTokenThis, MAXIMUM_ALLOWED,NULL, SecurityIdentification, TokenPrimary, &hTokenDup))

           {

              goto EXITFIN;

           }

           DWORD dwSessionId;

           pWTSGetActiveConsoleSessionId WTSGetActiveConsoleSessionIdF=NULL;

           hlibkernel = LoadLibrary("kernel32.dll");

           if (hlibkernel)

{             WTSGetActiveConsoleSessionIdF=(pWTSGetActiveConsoleSessionId)GetProcAddress(hlibkernel, "WTSGetActiveConsoleSessionId");

           }

           else

           {

              goto EXITFIN;

           }

           if (WTSGetActiveConsoleSessionIdF!=NULL)

           {

              dwSessionId = WTSGetActiveConsoleSessionIdF();

           }

           else

           {

              goto EXITFIN;

           }

          

               if(!SetTokenInformation(hTokenDup, TokenSessionId, &dwSessionId, sizeof(DWORD)))

           {

              goto EXITFIN;

           }

           STARTUPINFO si;

           PROCESS_INFORMATION pi;

           ZeroMemory(&si, sizeof(STARTUPINFO));

           ZeroMemory(&pi, sizeof(PROCESS_INFORMATION));

           si.cb = sizeof(STARTUPINFO);

           si.lpDesktop = "WinSta0//Default";

 

           DWORD dwCreationFlag = NORMAL_PRIORITY_CLASS | CREATE_NEW_CONSOLE;

           path = ExePath.c_str();

           if(!CreateProcessAsUser(

              hTokenDup,

              NULL,

              (char *)path.c_str(),

              NULL,

              NULL,

              FALSE,

              dwCreationFlag,

              pEnv,

              NULL,

              &si,

              &pi))

           {

              goto EXITFIN;

           }

           else

           {

              bResult = true;

           }

 

EXITFIN:

           if(hTokenDup != NULL)

           {

              CloseHandle(hTokenDup);

           }

           if(hThisProcess != NULL)

           {

              CloseHandle(hThisProcess);

           }

           if(hlibkernel != NULL)

           {

              FreeLibrary(hlibkernel);

           }

           return bResult;

       }

 

       bool CreateProInCurrentUserAdmin(std::string ExePath)//创建当前用户进程

       {

           HANDLE hTokenThis = NULL;

           HANDLE hTokenDup = NULL;

           HMODULE  hlibkernel = NULL;

           HANDLE hThisProcess = GetCurrentProcess();

           bool   bResult = false;

           LPVOID pEnv = NULL;

           string path;

             

              / GetDebugPriv()::提高权限

           GetDebugPriv();

           DWORD dwSessionId;

           pWTSGetActiveConsoleSessionId WTSGetActiveConsoleSessionIdF=NULL;

           pWTSQueryUserToken WTSQueryUserTokenF = NULL;

           hlibkernel = LoadLibrary("kernel32.dll");

           if (hlibkernel)

           {

               WTSGetActiveConsoleSessionIdF=(pWTSGetActiveConsoleSessionId)GetProcAddress(hlibkernel, "WTSGetActiveConsoleSessionId");

           }

           else

           {

              goto EXITFIN;

           }

           if (WTSGetActiveConsoleSessionIdF!=NULL)

           {

              dwSessionId = WTSGetActiveConsoleSessionIdF();

           }

           else

           {

              goto EXITFIN;

           }

           hlibkernel = LoadLibrary("Wtsapi32.dll");

           if (hlibkernel)

           {

WTSQueryUserTokenF=(pWTSQueryUserToken)GetProcAddress(hlibkernel, "WTSQueryUserToken");

           }

           else

           {

              goto EXITFIN;

           }

 

           if (WTSQueryUserTokenF!=NULL)

           {

              BOOL ret = WTSQueryUserTokenF(dwSessionId,&hTokenDup);

              if(hTokenDup !=NULL)

              {

              }

              else

              {

                  goto EXITFIN;

              }

           }

           else

           {

              goto EXITFIN;

           }

           STARTUPINFO si;

           PROCESS_INFORMATION pi;

           ZeroMemory(&si, sizeof(STARTUPINFO));

           ZeroMemory(&pi, sizeof(PROCESS_INFORMATION));

           si.cb = sizeof(STARTUPINFO);

           si.lpDesktop = "WinSta0//Default";

 

           DWORD dwCreationFlag = NORMAL_PRIORITY_CLASS | CREATE_NEW_CONSOLE;

           path = ExePath.c_str();

           if(!CreateProcessAsUser(

              hTokenDup,

              NULL,

              (char *)path.c_str(),

              NULL,

              NULL,

              FALSE,

              dwCreationFlag,

              pEnv,

              NULL,

              &si,

              &pi))

           {

              goto EXITFIN;

           }

           else

           {

              bResult = true;

           }

 

EXITFIN:

           if(hTokenDup != NULL)

           {

              CloseHandle(hTokenDup);

           }

           if(hThisProcess != NULL)

           {

              CloseHandle(hThisProcess);

           }

           if(hlibkernel != NULL)

           {

              FreeLibrary(hlibkernel);

           }

           return bResult;

       }

       // GetDebugPriv

       // Windows NT/2000/XP 中可能因权限不够导致以上函数失败

       // 如以 System 权限运行的系统进程,服务进程

       // 用本函数取得 debug 权限即可,Winlogon.exe 都可以终止哦:)

       //

       bool GetDebugPriv()

       {

           HANDLE hToken;

           LUID sedebugnameValue;

           TOKEN_PRIVILEGES tkp;

 

           if ( ! OpenProcessToken( GetCurrentProcess(),

              TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken ) )

           {

              return false;

           }

           if ( ! LookupPrivilegeValue( NULL, SE_DEBUG_NAME, &sedebugnameValue ) )

           {

              CloseHandle( hToken );

              return false;

           }

           tkp.PrivilegeCount = 1;

           tkp.Privileges[0].Luid = sedebugnameValue;

           tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;

 

           if (!AdjustTokenPrivileges( hToken, FALSE, &tkp, sizeof tkp, NULL, NULL ) )

           {

              CloseHandle( hToken );

              return false;

           }

           return true;

       }

 

 

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值