【问题标题】:Execute app from service in current user session在当前用户会话中从服务执行应用程序
【发布时间】:2021-12-28 18:17:11
【问题描述】:

使用 Delphi 10.2.3,我正在实现一项服务,除其他外,需要在数据库恢复之前关闭用户应用程序,然后在之后重新启动应用程序。关闭应用程序没有问题,但出于明显的 session-0 原因,启动应用程序备份是。我在网上找到了以下代码来执行此操作,它运行良好,但有一个例外。

function CreateEnvironmentBlock(var lpEnvironment: Pointer; hToken: THandle; bInherit: BOOL): BOOL; stdcall; external 'userenv.dll';
function DestroyEnvironmentBlock(lpEnvironment: Pointer): BOOL; stdcall; external 'userenv.dll';

function SvcLaunchAppInCurrUserSession(const AppToLaunch: String;
                                       const Params: String = '';
                                       WaitForIt: Boolean = False;
                                       HideIt: Boolean = False): Cardinal;
var
  PI: PROCESS_INFORMATION;
  SI: STARTUPINFO;
  bResult: Boolean;
  dwSessionId: DWORD;
  hUserTokenDup, hPToken: THANDLE;
  dwCreationFlags: DWORD;
  CommandLine: string;
  Directory: string;
  tp: TOKEN_PRIVILEGES;
  pEnv: Pointer;
begin
  Result := S_OK;
  try
    try
      pEnv := nil;
      dwCreationFlags := NORMAL_PRIORITY_CLASS or CREATE_NEW_CONSOLE;
      CommandLine := Trim('"'+Trim(AppToLaunch)+'" '+Params);
      Directory := ExtractFileDir(AppToLaunch);

      // get the current active session and the token
      dwSessionId := WtsGetActiveConsoleSessionID;

      // initialize startup info
      ZeroMemory(@SI, SizeOf(SI));
      SI.cb := SizeOf(STARTUPINFO);
      SI.lpDesktop := nil; //PChar('winsta0\Default');
      SI.dwFlags := STARTF_USESHOWWINDOW;
      if HideIt then
        SI.wShowWindow := SW_HIDE
      else
        SI.wShowWindow := SW_SHOWNORMAL;

      if OpenProcessToken(GetCurrentProcess, TOKEN_ADJUST_PRIVILEGES or
                                             TOKEN_QUERY or
                                             TOKEN_DUPLICATE or
                                             TOKEN_ASSIGN_PRIMARY or
                                             TOKEN_ADJUST_SESSIONID or
                                             TOKEN_READ or
                                             TOKEN_WRITE,
                                             hPToken) then begin
        tp.PrivilegeCount := 1;
        tp.Privileges[0].Attributes := SE_PRIVILEGE_ENABLED;

        if LookupPrivilegeValue(nil, 'SeDebugPrivilege', tp.Privileges[0].Luid) then begin
          DuplicateTokenEx(hPToken, MAXIMUM_ALLOWED, nil, SecurityIdentification, TokenPrimary, hUserTokenDup);
          SetTokenInformation(hUserTokenDup, TokenSessionId, @dwSessionId, SizeOf(DWORD));

          if CreateEnvironmentBlock(pEnv, hUserTokenDup, True) then 
            dwCreationFlags := dwCreationFlags or CREATE_UNICODE_ENVIRONMENT
          else
            pEnv := nil;

          // Launch the process in the client's logon session.
          bResult := CreateProcessAsUser(hUserTokenDup,      // client's access token
                                         nil,                // file to execute
                                         PChar(CommandLine), // command line
                                         nil,                // pointer to process SECURITY_ATTRIBUTES
                                         nil,                // pointer to thread SECURITY_ATTRIBUTES
                                         False,              // handles are not inheritable
                                         dwCreationFlags,    // creation flags
                                         pEnv,               // pointer to new environment block
                                         PChar(Directory),   // name of current directory
                                         si,                 // pointer to STARTUPINFO structure
                                         pi);                // receives information about new process

          if not bResult then begin
            Result := GetLastError;
            Exit;
          end;
        end
        else begin
          Result := GetLastError;
          Exit;
        end;
      end
      else begin
        Result := GetLastError;
        Exit;
      end;

      if WaitForIt then begin
        WaitForSingleObject(PI.hProcess, INFINITE);
        GetExitCodeProcess(PI.hProcess, Result);
      end;
    finally
      // close all handles
      if Assigned(pEnv) then
        DestroyEnvironmentBlock(pEnv);
      CloseHandle(hUserTokenDup);
      CloseHandle(PI.hProcess);
      CloseHandle(PI.hThread);
      CloseHandle(hPToken);
    end;
  except
    on E:Exception do begin
      DbgLogFmt('SvcLaunchApp %s: %s', [E.ClassName, E.Message]);
      raise;
    end;
  end;
end;

这样做的问题是它使用服务的权限 (SYSTEM) 启动应用程序,这是一个巨大的安全漏洞。我希望它以当前用户的权限(用户或管理员)启动应用程序,但不是系统。我知道 Windows 安全的来龙去脉,但我确信有办法做到这一点 - 我只是不知道上面的哪些部分需要调整以便使用正确的权限。或者,如果有更好的方法可以做到这一点,我愿意接受。有什么建议吗?

谢谢。

【问题讨论】:

    标签: delphi service delphi-10.2-tokyo


    【解决方案1】:

    您正在使用您的服务作为 (SYSTEM) 运行的用户令牌。改为使用WTSQueryUserToken() 获取目标会话的用户令牌。

    【讨论】:

    • Remy,听起来我需要在应用程序关闭之前找到它正在运行的会话,然后在该会话中使用WTQueryUserToken() 以确保它在它正在运行的同一会话中重新启动在之前。对吗?
    • @SteveS 您如何确定要关闭哪个应用程序,以及您实际上是如何关闭它的?根据您访问应用程序的方式,您可以直接查询它的会话 ID,甚至是运行它的用户令牌。
    • 主应用程序和服务通过命名管道传递数据和命令。当我告诉它关闭时,我可以通过该通道从主应用程序获取会话 ID。
    【解决方案2】:

    您必须先模拟用户,然后运行应用程序。 I wrote a blog article using impersonation 这可能会对您有所帮助。为此,您需要用户凭据。

    模拟将使您的程序(此处为服务)充当另一个用户。 blog article 使用该功能对当前用户隐藏数据。在您的情况下,您的服务将首先模拟为目标用户,然后启动应用程序,该应用程序将在模拟用户的上下文中运行。

    我发布了source code on github。此代码是StackOverflow discussion 的结果。

    【讨论】:

    • 欢迎提供解决方案的链接,但请确保您的答案在没有它的情况下有用:add context around the link 这样您的其他用户就会知道它是什么以及为什么会出现,然后引用最相关的内容您链接到的页面的一部分,以防目标页面不可用。 Answers that are little more than a link may be deleted.
    • 弗朗索瓦,谢谢,但我不确定这是我想要的。正在运行的应用程序是一个工业过程控制应用程序,除了在此关机/恢复/重启周期之外,只要计算机运行,它就会运行,并且需要在相同的控制台会话中重新启动,并且与以前一样以相同的登录用户身份重新启动关闭,并与服务的任何线程分离。
    猜你喜欢
    • 1970-01-01
    • 2015-11-04
    • 1970-01-01
    • 1970-01-01
    • 2013-01-28
    • 2021-10-26
    • 1970-01-01
    • 2011-10-28
    相关资源
    最近更新 更多