Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Win32/Win64 API (native code) (https://www.delphipraxis.net/17-win32-win64-api-native-code/)
-   -   Delphi Starting a Interactive Process in Vista using TService (https://www.delphipraxis.net/101151-starting-interactive-process-vista-using-tservice.html)

FaNIX 9. Okt 2007 08:13


Starting a Interactive Process in Vista using TService
 
Hallo,

I hope it's not a problem if i type in English. Im have a TService application, running on windows vista. All i want to do is create a process(lets say notepad.exe) and display that on the logged in users desktop.

As most of you know, in Vista Services run on a completely different environment, which is non interactive to the user, so basically all processes started inside the service will be invisible to the user.

After reading A LOT of forums and reading alot about this, i saw a few examples using LSALogonUser and CreateProcessAsUser... But i couldnt get any of them to work... Ive downloaded some source form this forum, pasted that in a service, but it still doesnt work... It does work when i paste it in a Normal Win32 VCL Application, then the process starts correctly, so it's clear that something is wrong, here is my code, please any advice would be much appreciated.

Delphi-Quellcode:

unit uMain;

interface

uses
  JwaWindows,SvcMgr, SysUtils,
  JwaWinType, JwaWinBase, JwaWinNT, JwaNtSecApi, JwaNTStatus,
  JwaNative, JwaUserEnv, JwaWinSta, JwaWtsApi32, Dialogs, uWinStation;

const
  DNLEN     = 15;
  UNLEN     = 256;

type
  TAuthInfo = record
    Header: MSV1_0_INTERACTIVE_LOGON;
    Domain: array[0..DNLEN] of WideChar;
    User: array[0..UNLEN] of WideChar;
    Password: array[0..UNLEN] of WideChar;
  end;

type
  TService1 = class(TService)
    procedure ServiceStart(Sender: TService; var Started: Boolean);
  private
    { Private declarations }
  public
    function GetServiceController: TServiceController; override;
    { Public declarations }
  end;

var
  Service1: TService1;

implementation

{$R *.DFM}

procedure ServiceController(CtrlCode: DWord); stdcall;
begin
  Service1.Controller(CtrlCode);
end;

function TService1.GetServiceController: TServiceController;
begin
  Result := ServiceController;
end;

Procedure LsaInitUnicodeString(Var LsaString: TLsaUnicodeString; Const WS:
  WideString);
Begin
  FillChar(LsaString, SizeOf(LsaString), 0);
  If WS <> '' Then
  Begin
    LsaString.Length:=Length(WS) * SizeOf(WideChar);
    LsaString.MaximumLength:=LsaString.Length + SizeOf(WideChar);
    LsaString.Buffer:=PWideChar(WS);
  End;
End;

procedure GetLogonSID(hToken: THandle; var ppsid: PSID);
var dwLength: DWORD;
    ptg    : ^TOKEN_GROUPS;
    i      : integer;
begin
  dwLength := 0;
  ptg := nil;

  try
    // Get required buffer size and allocate the TOKEN_GROUPS buffer.
    if not GetTokenInformation(hToken, TokenGroups, ptg, 0, dwLength) then
    begin
      if GetLastError <> ERROR_INSUFFICIENT_BUFFER then
      begin
        ShowMessage('GetTokenInformation failed');
        Exit;
      end;

      ptg := HeapAlloc(GetProcessHeap, HEAP_ZERO_MEMORY, dwLength);
      if ptg = nil then
      begin
        Exit;
      end;

      // Get the token group information from the access token.
      if not GetTokenInformation(hToken, TokenGroups, ptg, dwLength, dwLength) then
      begin
        Exit;
      end;

      // Loop through the groups to find the logon SID.
      for i := 0 to ptg.GroupCount-1 do
      begin
       if ptg.Groups[i].Attributes and SE_GROUP_LOGON_ID = SE_GROUP_LOGON_ID then
       begin
         // Found the logon SID; make a copy of it.
         dwLength := GetLengthSid(ptg.Groups[i].Sid);
         ppsid := HeapAlloc(GetProcessHeap, HEAP_ZERO_MEMORY, dwLength);
         if ppsid = nil then
         begin
           Exit;
         end;
         if not CopySid(dwLength, ppsid, ptg.Groups[i].Sid) then
         begin
//           raise exception.Create(Format('CopySid: %s', [SysErrorMessage(GetLastError)]));
           HeapFree(GetProcessHeap, 0, ppsid);
           Exit;
         end;

         Break;
        end;
      end;
    end;
  finally
    // Free the buffer for the token groups.
    if ptg <> nil then
    begin
      HeapFree(GetProcessHeap, 0, ptg);
    end;
  end;
end;
procedure TService1.ServiceStart(Sender: TService; var Started: Boolean);
var hToken: THandle;
  si: _STARTUPINFOA;
  pi: _PROCESS_INFORMATION;
  Res: NTSTATUS;
  hLSA: THandle;
  LSAString: _LSA_STRING;
  AuthenticationPackage: ULONG;
  AuthentificationInfo: TAuthInfo;
  pProfileBuffer: Pointer;
  LogonID: JwaWinType.LUID;
  hLsaToken: THandle;
  QuotaLimits: QUOTA_LIMITS;
  SubStatus: Integer;
  wsDomain: WideString;
  wsUser: WideString;
  wsPwd: WideString;
  TokenSource: TOKEN_SOURCE;
  dwReturnLength: ULONG;
  Mode: LSA_OPERATIONAL_MODE;
  pGroups: PTOKEN_GROUPS;
  AdminSid: PSid;
  dwSizeSid: Cardinal;
  dwSizeDomain: Cardinal;
  SidType: TSidNameUse;
  Domain: String;
  MaxGroups: Integer;
  bRes: Longbool;
begin
  ZeroMemory(@si, SizeOf(si));
  si.cb := SizeOf(si);
  si.lpDesktop := nil;

  if WTSQueryUserToken(WtsGetActiveConsoleSessionID, hToken) then
  begin
    RtlInitString(@LsaString, PCSZ(PChar('Winlogon')));
    Res := LsaRegisterLogonProcess(LsaString, hLSA, @Mode);
    if Failed(Res) then
    begin
      ShowMessageFmt('LsaRegisterLogonProcess: %s', [SysErrorMessage(LsaNtStatusToWinError(Res))]);
    end;

    RtlInitString(@LsaString,PCSZ(PChar(MSV1_0_PACKAGE_NAME)));

    Res := LsaLookupAuthenticationPackage(hLSA, LSAString, AuthenticationPackage);
    if Failed(Res) then
    begin
      ShowMessageFmt('LookupAuthPackage: %s', [SysErrorMessage(LsaNtStatusToWinError(Res))]);
    end;

    TokenSource.SourceName := '**ANON**';
    if not AllocateLocallyUniqueId(TokenSource.SourceIdentifier) then
    begin
      ShowMessageFmt('AllocLocUniqueId: %s', [SysErrorMessage(GetLastError)]);
    end;

    // The number of TOKEN_GROUPS we're going to insert
    MaxGroups := 2;

    // Reserve memory for MaxGroups numbur of PTOKEN_GROUPS
    pGroups := PTOKEN_GROUPS(GlobalAlloc(GPTR, sizeof(_SID_AND_ATTRIBUTES) * MaxGroups));
    pGroups^.GroupCount := MaxGroups;

    // Get the Logon Sid and it to the LocalGroups parameter of LsaLogonUser
    // The Logon Sid has the form S-1-5-5-XXXXXXXX-YYYYYYYY
    // We need it to obtain access to the user's desktop
    GetLogonSid(hToken, pGroups^.Groups[0].Sid);
    pGroups^.Groups[0].Attributes := SE_GROUP_MANDATORY or
                                     SE_GROUP_ENABLED or
                                     SE_GROUP_ENABLED_BY_DEFAULT or
                                     SE_GROUP_LOGON_ID;

    // Now get the Administrator's SID
    dwSizeSid := 0;
    dwSizeDomain := 0;
    bRes := LookupAccountName(nil, 'Administrator', nil, dwSizeSid, nil, dwSizeDomain, SidType);

    if (not bRes) and (GetLastError = ERROR_INSUFFICIENT_BUFFER) then
    begin
      // Reserve memory
      AdminSid := AllocMem(dwSizeSid);
      SetLength(Domain, dwSizeDomain);

      // Lookup Sid from Accountname
      // Assuming that the Admin account has not been renamed!
      bRes := LookUpAccountName(nil, 'Administrator', AdminSid, dwSizeSid, PChar(Domain), dwSizeDomain, SidType);
      if not bRes then
      begin
        // Cleanup
        FreeMem(AdminSid);
        AdminSid := nil;
      end;
    end
    else begin
      RaiseLastOSError;
    end;

    ShowMessageFmt('Administrator Sid: %s, Domain: %s', [SidToStr(AdminSid), Domain]);

    // Add the Administrator's sid to pGroups
    pGroups^.Groups[MaxGroups -1].Sid := AdminSid;
    pGroups^.Groups[MaxGroups -1].Attributes := SE_GROUP_MANDATORY or
                                                SE_GROUP_ENABLED or
                                                SE_GROUP_ENABLED_BY_DEFAULT or
                                                SE_GROUP_LOGON_ID;

    // Fill the AuthentificationInfo structure
    // First convert the EDITs to WideString
    wsDomain:= '';
    wsUser:= 'username';
    wsPwd:= 'password';

    // Fill with zeros
    RtlZeroMemory(@AuthentificationInfo, sizeof(AuthentificationInfo));
    AuthentificationInfo.Header.MessageType := MsV1_0InteractiveLogon;
    //  AuthentificationInfo.Header.MessageType := MsV1_0NetworkLogon;

    // Copy the strings into a buffer.
    RtlCopyMemory(@AuthentificationInfo.Domain, @wsDomain[1], sizeof(WideChar) * Length(wsDomain));
    RtlCopyMemory(@AuthentificationInfo.User, @wsUser[1], sizeof(WideChar) * Length(wsUser));
    RtlCopyMemory(@AuthentificationInfo.Password, @wsPwd[1], sizeof(WideChar) * Length(wsPwd));

    // Now set which buffer we want to use (the arrays of WideChar from the struct)
    RtlInitUnicodeString(@AuthentificationInfo.Header.LogonDomainName, AuthentificationInfo.Domain);
    RtlInitUnicodeString(@AuthentificationInfo.Header.UserName, AuthentificationInfo.User);
    RtlInitUnicodeString(@AuthentificationInfo.Header.Password, AuthentificationInfo.Password);

    Res := JwaNtSecApi.LsaLogonUser(hLSA,
                                    LsaString,
                                    RemoteInteractive,
                                    AuthenticationPackage,
                                    @AuthentificationInfo,
                                    SizeOf(AuthentificationInfo),
                                    pGroups,
                                    @TokenSource,
                                    pProfileBuffer,
                                    dwReturnLength,
                                    LogonID,
                                    hLSAToken,
                                    QuotaLimits,
                                    SubStatus);

    if Failed(Res) then
    begin
      ShowMessageFmt('LsaLogonUser: %s', [SysErrorMessage(LsaNtStatusToWinError(Res))]);
    end;

    ZeroMemory(@si, SizeOf(si));
    si.cb := SizeOf(si);
    si.lpReserved := nil;
    si.lpDesktop := nil;
    si.dwFlags := STARTF_USESHOWWINDOW;;
    si.wShowWindow := SW_SHOWNORMAL;

    if not CreateProcessAsUser(hLsaToken, nil, PChar('notepad.exe'), nil, nil, False,
                               NORMAL_PRIORITY_CLASS or CREATE_NEW_PROCESS_GROUP,
                               nil, nil, &si, &pi) then
    begin
      ShowMessageFmt('CreateProcessAsUser: %s', [SysErrorMessage(GetLastError)]);   end
    else
    begin
    end;

    // Cleanup
    CloseHandle(hToken);
    FreeMem(AdminSid);
    LsaDeregisterLogonProcess(hLSA);
    LsaFreeReturnBuffer(pProfileBuffer);
  end;
  Self.DoStop;


end;

end.

Klaus01 9. Okt 2007 08:15

Re: Sal iemand my kan help?
 
Guten Morgen,

wie wäre es mit deutsch oder englisch, so verstehe ich nicht die Bohne.

Grüße
Klaus

Phoenix 9. Okt 2007 08:18

Re: Sal iemand my kan help?
 
hi Fanix.

Ich bin ein klitzekleines wenig des niederländischen mächtig, aber was ein interaktiver gezüchteter (fokken?) Desktop ist weiss ich nun doch nicht?

Bitte versuche es mal auf englisch oder Deutsch. Dann können Dir sicher viel mehr Leute helfen.

Phoenix 9. Okt 2007 08:32

Re: Starting a Interactive Process in Vista using TService
 
Ah, english is much better. ;-)

I don't think that what you want is possible in the Vista Security System in the way you think of it.

Nevertheless, while I was looking for some samples about WCF i came across an interesting article on Codeproject:
http://www.codeproject.com/WCF/AppWatcher.asp

I know, this is a .NET article, but he author uses a out of process com+ component to start an application in the users context out of a service on a vista machine. The technology he used should be the same with a win/32 service.

FaNIX 9. Okt 2007 08:45

Re: Starting a Interactive Process in Vista using TService
 
Zitat:

Zitat von Phoenix
Ah, english is much better. ;-)

I don't think that what you want is possible in the Vista Security System in the way you think of it.

Nevertheless, while I was looking for some samples about WCF i came across an interesting article on Codeproject:
http://www.codeproject.com/WCF/AppWatcher.asp

I know, this is a .NET article, but he author uses a out of process com+ component to start an application in the users context out of a service on a vista machine. The technology he used should be the same with a win/32 service.

Thanks for your reply, but i think it would take too much time to try and convert that method to delphi/win32

SirThornberry 9. Okt 2007 09:18

Re: Starting a Interactive Process in Vista using TService
 
Is SetThreadDesktop still working in Vista? you can try to set a thread to the users desktop and then create the process in that thread.

OregonGhost 9. Okt 2007 10:22

Re: Starting a Interactive Process in Vista using TService
 
Why does your service need to run an interactive application? In many instances, if there is the need for a service to trigger user interaction, a separate application is run on the user's desktop (mostly with a TNA icon). The service can then contact this GUI application which will perform the needed actions. You may want to have a look at Larry Osterman's article series about applets (search for applet) though.

Dezipaitor 9. Okt 2007 11:04

Re: Starting a Interactive Process in Vista using TService
 
You do not need LsaLogonUser at all.

You already use WTSQueryUserToken which gives you a token, that can be used for CreateProcessAsUser.
That token leads to a process that is started in the logonsession of the user.
Parameter lpStartupInfo (member lpDesktop) you can provide a windowsstation and desktop where the new application is put ("winsta0\default").
That should work.

FaNIX 9. Okt 2007 12:25

Re: Starting a Interactive Process in Vista using TService
 
Zitat:

Zitat von Dezipaitor
You do not need LsaLogonUser at all.

You already use WTSQueryUserToken which gives you a token, that can be used for CreateProcessAsUser.
That token leads to a process that is started in the logonsession of the user.
Parameter lpStartupInfo (member lpDesktop) you can provide a windowsstation and desktop where the new application is put ("winsta0\default").
That should work.

Could you perhaps give me a example?

Tyrael Y. 9. Okt 2007 12:34

Re: Starting a Interactive Process in Vista using TService
 
You can´t start a GUI application from a service in Vista.

To display GUI from service you must start a non visible application with the service and this application have to start the GUI elements.

If you need data of interaction you have to implement communication between service and non-visible application e.g. TCP.



[Service] <------> [Non-Visible-App] <------> [GUI-App]

Dezipaitor 9. Okt 2007 14:22

Re: Starting a Interactive Process in Vista using TService
 
You can start gui from a service. You need to use the logon credentials of the user not the service. You cannot create a window for the user display using session 0 - however you could create window for session 0, but it would not be accessible. Actually Vista do show such a window on a new desktop, but this could change on next update.

This example shows how to start cmd.exe from a service using the user credentials of the interactive logon session the user logged on. This does not work, if no user is logged on!
To use WTSQueryUserToken you must include Jedi Api Lib or just load it from windows dll. (see MSDN for more information) -
http://jedi-apilib.sourceforge.net

Delphi-Quellcode:
procedure TService1.ServiceExecute(Sender: TService);
var hToken : THandle;
  si: TStartupInfo;
  pi: TProcessInformation;
  bTerminate: Boolean;
  ACurrentDir: String;
begin
  if WTSQueryUserToken(WtsGetActiveConsoleSessionID, hToken) then
  begin
    FillChar(si, SizeOf(si), 0);
    with si do
    begin
      cb := SizeOf(si);
      dwFlags := STARTF_USESHOWWINDOW;
      wShowWindow := SW_NORMAL;
    end;


    if not CreateProcessAsUser(hToken, PChar('cmd.exe'), nil, nil, nil, False,
                               NORMAL_PRIORITY_CLASS or CREATE_NEW_PROCESS_GROUP,
                               nil, nil, si, pi) then
      MessageBox(0,PChar(SysErrorMessage(GetLastError)),'',MB_SERVICE_NOTIFICATION or MB_OK)
  end
  else
   MessageBox(0,PChar(SysErrorMessage(GetLastError)),'',MB_SERVICE_NOTIFICATION or MB_OK);

  CloseHandle(hToken);
end;

Dezipaitor 9. Okt 2007 14:28

Re: Starting a Interactive Process in Vista using TService
 
Zitat:

Zitat von Tyrael Y.
You can´t start a GUI application from a service in Vista.

To display GUI from service you must start a non visible application with the service and this application have to start the GUI elements.

If you need data of interaction you have to implement communication between service and non-visible application e.g. TCP.



[Service] <------> [Non-Visible-App] <------> [GUI-App]

1. You can't directly display a gui in a service. Actually Vista displays a service gui in a seperate desktop. This will change and make it useless.
2. You dont need an invisible app
3. MessageBox with MB_SERVICE_NOTIFICATION works as a display.

The correct image is this:

[Service] <---- communication protocol (TCP, Pipe, Shared Memory...) ---> [Gui-App in a seperate process, started by service or user]

OregonGhost 9. Okt 2007 14:38

Re: Starting a Interactive Process in Vista using TService
 
While I still cannot imagine a situation where starting an interactive application from a service is necessary, I have three questions to this approach:
  1. What happens if more than one user is logged on?
  2. Where does the service get the user login password from?
  3. What happens if the service does not run with full rights?

Oh, apart from the question what happens if services and user applications are even more separated from each other in the next version of the operating system :mrgreen:

Apollonius 9. Okt 2007 14:49

Re: Starting a Interactive Process in Vista using TService
 
@2 and 3:
MSDN about WTSQueryUserToken:
The calling application must be running within the context of the LocalSystem account and have the SE_TCB_NAME privilege

Dezipaitor 9. Okt 2007 14:50

Re: Starting a Interactive Process in Vista using TService
 
Zitat:

Zitat von OregonGhost
While I still cannot imagine a situation where starting an interactive application from a service is necessary, I have three questions to this approach:
  1. What happens if more than one user is logged on?
  2. Where does the service get the user login password from?
  3. What happens if the service does not run with full rights?

Oh, apart from the question what happens if services and user applications are even more separated from each other in the next version of the operating system :mrgreen:

1. WtsQueryUserToken with WtsGetActiveConsoleSessionID uses always the lonely console session. The console session is the session which the keyboard and mouse inputs send their data. Its not a terminal session. You can provide other terminal session.
Wts_Functions are only supported in XP and newer.

2. There is no need. A token is a passport of the user. A service has the power to obtain a copy of it and use it for whatever it wants. LogonUser is only necessary if you want to use user credentials for a user who is not logged on.

3. WtsQueryUserToken needs the TCB privilege to be hold by the process. If the service is started with other credentials the function simply fails and none of this will work.

4. How could that be possible? Communication is always needed. Otherwise we could not communicate with hardware which is necceesarry. However the next step is to seperate secure and unsecure apps.

OregonGhost 9. Okt 2007 15:16

Re: Starting a Interactive Process in Vista using TService
 
@4.: What I meant was direct communication between a service and an interactive application. Some time ago, services lost the ability to directly manipulate windows on interactive desktops (should be since XP, if the service was configured appropriately), but I consider starting an interactive application in a user context basically the same and won't be surprised if that can't be done anymore in the future. Indirect communication like TCP/IP or pipes should always be available, but in my opinion a service should not be able to start or manipulate a user application. If the service needs to communicate with the user, maybe it should not have been a service in the first place. But as repeatedly said, that's just my opinion on consequently restricting access and I'm just curious and like to hear why it should stay the way it is.

Dezipaitor 9. Okt 2007 18:35

Re: Starting a Interactive Process in Vista using TService
 
You mean by direct communication : SendMessage ?
TCP, Pipe is also direct communication.

The only real direct communication is that code calls functions in the same process. And that does not work with GUI, too.

OregonGhost 9. Okt 2007 18:44

Re: Starting a Interactive Process in Vista using TService
 
TCP/IP and Pipes are rather indirect communication methods in the context I described above, as the other side has to expect and accept the connection and must explicitly read the data. SendMessage and PostMessage (and all these nice input emulation functions) usually force an action in the application, as most applications delegate most window messages to the default window procedure and there should never be a need for a service to control a GUI application like that. And if just showing a window falls into that category, starting an application which shows a window should fall into the same category.
However, as in a correctly configured environment any application should be properly installed by an administrator and write-protected for an interactive user, there shouldn't be as much of a problem as I initially thought. I'm just thinking that the service can never know if the application is the one it seems to be or not, maybe I'm a little paranoid in these things. Read too much about threat modelling in the last weeks, I guess :mrgreen:

Dezipaitor 9. Okt 2007 19:15

Re: Starting a Interactive Process in Vista using TService
 
Okay thats correct.

However in a good env. a user should also install an application without consultion an admin. Applications which want admin rights do really need a good reason for that - imho.
I hate apps which always want admin rights - i do not install them at all.

A service and its client app is truly a team. Any client app can send messages to the service. However the service must always check the input.
The client app can be signed with a reliable certificate which can be checked by the service. To create such a certifcate is simple but expensive. Reliable organisations want money for that.

OregonGhost 9. Okt 2007 19:19

Re: Starting a Interactive Process in Vista using TService
 
Yes, that's basically true. But as you wrote, the GUI application that is part of a service either must not be writable by the user (which is OK in this case, as you need admin rights to install the service anyway), or the service has to prove that the application is the correct one.

Hopefully this talk doesn't go too far away from the original author's question, though it gets a little off-topic :mrgreen:

Dezipaitor 9. Okt 2007 19:39

Re: Starting a Interactive Process in Vista using TService
 
Zitat:

Zitat von OregonGhost
Yes, that's basically true. But as you wrote, the GUI application that is part of a service either must not be writable by the user (which is OK in this case, as you need admin rights to install the service anyway), or the service has to prove that the application is the correct one.

Hopefully this talk doesn't go too far away from the original author's question, though it gets a little off-topic :mrgreen:

Yeah.
I think this conversation is a lot more informative than many other threads. I also already provided the answer (i think).


Alle Zeitangaben in WEZ +1. Es ist jetzt 00:42 Uhr.

Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz