Link to home
Start Free TrialLog in
Avatar of abb1
abb1

asked on

WinLogon Notification Package -- more details needed!

I've read MSDN article and have created the DLL as it is described. DLL is working perfectly. The only unclear point is the next one: What is the hToken member in the _WLX_NOTIFICATION_INFO structure?

I'd like to use it to have access later from my system service to the user's security context. Particularly I'd like to use CreateProcessAsUser() there. Can I do it? Please note, I don't need to use hToken inside notification package -- I just need to save its value (or duplicate it?) to use later in another process, running as a system service. Is it possible?

Also I've tried to use DuplicateTokenEx() inside DLL to create a primary user token because I thought the hToken member above is an impersonated token. Still no luck -- CreateProcessAsUser fails with the "Access is denied" error. Please help me with a clear code sample showing how to do it.

Please do not answer "It is impossible", because I don't like to spend points for nothing.
Avatar of jkr
jkr
Flag of Germany image

>>What is the hToken member in the _WLX_NOTIFICATION_INFO structure?

As the docs (http://msdn.microsoft.com/library/en-us/secauthn/security/wlx_notification_info.asp) state, it is the "Handle to the user's token", IOW the primary token.

>>I just need to save its value (or duplicate it?) to use later in another process, running
>>as a system service. Is it possible?

That's where the "Access is denied" kicks in. A system service will not have the necessary access rights to that token in question, speaking of 'TOKEN_IMPERSONATE' in particular. You cannot just save or pass these like integers.

>>Please do not answer "It is impossible"

Oh, great. Above you're asking "Is it possible?" and then you don't want to get an answer?

>> because I don't like to spend points for nothing

OK, then I'll stop here until you give a clearer picture of what you actually want to do, since I don't want to spend my time for nothing either.
Avatar of abb1
abb1

ASKER

So, good. I'll tell it in other words: I need a solution. I don't need just a negative answer. Suggest something positive and give your points instantly, OK?

AS for the issue, I need to force logoff of the user logged on. I track the logon/logoff events by WLN. It works OK. The main issue is how to do logoff itself. I saw some tricks with a special screensaver installation for the user, but it wouldn't resolve my issue because I don't need to force logoff for inactive users, I just need to force logoff for all and most probably actively "working" "users" (gamers).

As I can't find a clear solution of how to do it, I've decided to use that WLN data (user token) to run the logoffer on behalf that user.
But you say it is wrong. OK. Can you suggest another solution?

Indeed, I could run a special utility (from HKCU\MS\Windows\Run)which could do all my service does, and it is easy, but this utility can be easily killed by user, right? Then it is not a solution.

So, the issue essence is:
how to force logoff for a local user from the system service? If there are no known direct ways, then how to use CreateProcessAsUser without explicit username/password entering and doing all that in background from the system server, which can't be killed by limited user?

Is such question good for you? If so, am looking forward for an answer! :)
 
abb1,

>>Suggest something positive and give your points instantly, OK?

To me it's more about a rock-solid solution - sorry if I sounded harsh, but your last sentence sounded a bit misleading. All experts on this side are volunteers spending their spare time, and it just sounded too business-like. No offense meant, my apologies if I came across like that.

BTW, regarding your actual problem (and I am glad I asked about more details *g*) - have you thought of sth. like

ImpersonateInteractiveUser()
{
   HANDLE hToken = NULL;                
   HANDLE hProcess = NULL;

   DWORD processID = GetExplorerProcessID();
   if( processID)
    {
    hProcess =
         OpenProcess(  
               PROCESS_ALL_ACCESS,
         TRUE,
          processID );

    if( hProcess)
        {
        if( OpenProcessToken(
                    hProcess,
              TOKEN_EXECUTE |
             TOKEN_READ |
             TOKEN_QUERY |
             TOKEN_ASSIGN_PRIMARY |
             TOKEN_QUERY_SOURCE |
             TOKEN_WRITE |
             TOKEN_DUPLICATE,
             &hToken))
        {
         ImpersonateLoggedOnUser( hToken);
          CloseHandle( hToken );
        }
        CloseHandle( hProcess );
    }
   }
}

Then, call 'CreateProcess()' to launch any application in the context of the logged on user (your logoff utility in particular) and call 'RevertToSelf()'

BTW, 'GetExplorerProcessID();' can be implemented using http://support.microsoft.com/default.aspx?scid=http://support.microsoft.com:80/support/kb/articles/Q175/0/30.ASP&NoWebContent=1 ("HOWTO: Enumerate Applications Using Win32 APIs")
Avatar of abb1

ASKER

Oh, yes, now we're talking! :) But could you be a bit more wordy? In particular, what should I  do after this procedure? What parameters to use to CreateProcess()? At which step user context is opened? And shouldn't I use CreateProcessAsUser rather than CreateProcess()? Also how to identify Explorer's process among others?  Should I check the executable filename or whatever else? I'll have a look at the link as well.

As for my initial slighting phrase, I'm sorry, but I had a negative experience with this site, when there were no really useful answers to my question for a long time, then finally I found my own solution, but I couldn't delete the question and refund the points, because site system didn't allow remove questions with any comments. I needed to talk with the site administration, explaining them the obvious things. Though then I was too limited in points... Now I have them enough :) But I don't like the answers like "AFAIK it is impossible" anyway. Hope you too.
>>In particular, what should I  do after this procedure? What parameters to use to CreateProcess()?
>>At which step user context is opened? And shouldn't I use CreateProcessAsUser rather than CreateProcess()?

Well, after calling 'ImpersonateLoggedOnUser()' your current thread is running in the user's context, so you can use 'CreateProcess()' just as in any other application. 'CreateProcessAsUser()' would work with the token you obtained in the above code also.

>> Also how to identify Explorer's process among others?  Should I check the executable filename or whatever else?
>> I'll have a look at the link as well.

How many users are being logged on at the same time (that to me seems a bit unusual to me except in a TS environment - which is not really what you expect for gamers)? You can always query the process' SD and call 'GetSecurityDescriptorOwner()' to find whose process you are looking at.
Avatar of abb1

ASKER

>>Well, after calling 'ImpersonateLoggedOnUser()' your current thread is running in the user's context

Hmmm... Is it the thread of my system service in which I call this procedure? Could you please explain it for me, at what point the current thread's context is changed? Is it ImpersonateLoggedOnUser() call or whatever else? Please understand, I must know what and why I'm doing.

Yet another aspect: can I directly call ExitWindowsEx(EWX_LOGOFF, 0) from this "user" thread, as soon as it is running in the user's context? How to close (if it is needed) the thread in such case from my system service? What will happen with this thread if I'll call logoff?

And it is strange for me to see that any process at any time can insert its thread into any user context... That's a srange security... Or is it allowed just for system processes (i.e. running in system account)?

>How many users are being logged on at the same time...

Please note, I'd like to have at least minimal usability :) In particular TSC session also can be active. So, I must kill just the user, whose name I have found when I detected (from the service) user account expiration. I know only username, used for logon. Another user (theoretically) may be also logged on. Win XP can switch between users without logoff, agree? So, I'd like to identify user context by username or another info, exactly identifying the username. What info should I use for that? Is LookupAccountSid() what I need? Will it retreeve the username, used at logon?
Avatar of abb1

ASKER

>Hmmm... Is it the thread of my system service in which I call this procedure? Could you please explain it for me, at what point the current thread's context is changed? Is it ImpersonateLoggedOnUser() call or whatever else? Please understand, I must know what and why I'm doing.

Sorry, that was just a stupid question... But all the below are still actual.
Avatar of abb1

ASKER

So, after Win32 help digging  (and indeed with your code sample!) now I can retrieve the explorer.exe owner's username. The only unclear point on this way is the next: when I call GetKernelObjectSecurity() what is the third parameter? As help declares, it is PSECURITY_DESCRIPTOR pointer. But SECURITY_DESCRIPTOR structure takes 20 bytes, at the same time the last parameter in GetKernelObjectSecurity() is returned with value of 48. All that begins to work just perfectly after I've declared the third parameter, as a pointer to array of 48 bytes and assigned the fourth parameter to 48. Where I'm wrong?

Indeed, I'll try your code sample ASAP, but I'd like to know what to do with the impersonated thread after ExitWindowsEx() call. Can I just close it from within itself? Or should I deimpersonate it? What way would be the most correct one?
>>Is it the thread of my system service in which I call this procedure? Could you please explain it for me,
>>at what point the current thread's context is changed? Is it ImpersonateLoggedOnUser() call or whatever else?

Yes, you call that from your system procedure - the effect is that the security context of your thread is changed into the one of the impersonation token you're passing in right after calling 'ImpersonateLoggedOnUser()'.

>>when I call GetKernelObjectSecurity() what is the third parameter? As help declares, it is PSECURITY_DESCRIPTOR
>>pointer. But SECURITY_DESCRIPTOR structure takes 20 bytes

You'd call that API first with a NULL pointer as the pointer to receive the actual lenght of the info you are requesting, then alloce teh amount and call it again, e.g.

DWORD dwNeeded;
PSECURITY_DESCRIPTOR psd;

GetKernelObjectSecurity(hObj,OWNER_SECURITY_INFORMATION,NULL,0,&dwNeeded);

psd = (PSECURITY_DESCRIPTOR) new BYTE[dwNeeded];

GetKernelObjectSecurity(hObj,OWNER_SECURITY_INFORMATION,psd,dwNeeded,&dwNeeded);
Avatar of abb1

ASKER

I understand it, it is widely spread method in Win32 API. My question was just about the type definition. Why help is declaring the SECURITY_DESCRIPTOR as a 20 byte record? Where are extra bytes covered? What is the real structure? Or these pointers (fields of SECURITY_DESCRIPTOR record) point to some positions outside of these 20 byte "header"? I'm just wondering, because I'd like to know what I'm doing. Nothing more.

And you didn't answered my another question. What is the most correct way to close the thread, running in user's context, after it calls ExitWindowsEx? Should I experiment here, or is there a known rule?
ASKER CERTIFIED SOLUTION
Avatar of jkr
jkr
Flag of Germany image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of abb1

ASKER

Finally I've tested your code. After some debugging of my own app. I have the next results:

WIN XP SP2:
All accounts including Administrator's are identified OK but your code works only partially: the only way to cause logoff is just to execute external process, containing ExitWindowsEx(EWX_FORCE or EWX_LOGOFF, 0) as the only line of code, by calling CreateProcessAsUser(hToken) with hToken, got from OpenProcessToken (see your sample). Both direct call to ExitWindowsEx and ImpersonateLoggedOnUser() followed by CreateProcess(), have no success. Direct call is just ignored, CreateProcess seems to be called (cursor is changed quickly to hourglass and back) but has no effect. BTW my progam (service application) also generates sound by PlaySound() before loggof call and this PlaySound goes OK directly from service.

WIN 2003 SP1:
This is just that computer what I'm doing it all for. First of all -- a strange result of processes enumeration: I can't retrieve the Administrator's username. I even have added a special user account and added this user to "Administrators" group. Nevertheless, GetSecurityInfo returns just group name ("Administrators") and "BUILT-IN", as the domain name. It doesn't prevent me to continue, but I'd like to know how to retrieve the user account name for not built-in user. but from Administrators group. In other aspects your code works just perfectly!

So, I definitely accept your answer, you help me a lot, many thanks, though please answer to my question above if you can.
Hmm, can you provide some error codes for the above situations? ;o)
Avatar of abb1

ASKER

I tried the following (all code in Delphi):
....................
ImpersonateLoggedOnUser( hToken);
ExitWindowsEx(EWX_FORCE or EWX_LOGOFF, 0);
RevertToSelf;
....................
Here GetLastError returns "Incorrect function".

When I try CreateProcess() instead of ExitWindowsEx() no error is returned. Though the logoff process has no effect. I assume the same ExitWindowsEx in it returns just the same code, but I did not try it, as I have no free time.

The only way it works in, is to call CreateProcessAsUser(hToken,.....); Then all goes OK.

Now about the processes enumerator. There are no errors here. All goes OK and all results are returned correctly, but I get the Group name as the Owner in GetSecurityInfo() function. Here is my code sample (in Delphi too):

function MyProcessEnumerator(dwPID     : DWORD;
                             wTask     : WORD;
                             szProcess : PChar;
                             lParam    : integer) : boolean; stdcall;
var
  hProc  : THandle;
  Token  : THandle;
  SID    : PPSECURITY_DESCRIPTOR;
  wSize  : DWORD;
  sName  : WideString;
  cbName : DWORD;
  sDomain : WideString;
  cbDomain : DWORD;
  Owner : PSID;
  Group : PSID;
  DACL : PSID;
  SACL : PSID;
  OwDef : bool;
  peUse : SID_NAME_USE;
begin
  sName := '';
  sDomain := '';
  if wTask = 0 then
    write(dwPID:5, ' ', String(szProcess))
  else
    write(wTask:5, ' ', String(szProcess));
  if dwPID <> 0 then
  begin
    hProc := OpenProcess(PROCESS_ALL_ACCESS,
                         true,
                         dwPID);
    if hProc <> 0 then
    begin
      if OpenProcessToken(hProc,
                          TOKEN_EXECUTE or
                          TOKEN_READ or
                          TOKEN_QUERY or
                          TOKEN_ASSIGN_PRIMARY or
                          TOKEN_QUERY_SOURCE or
                          TOKEN_WRITE or
                          TOKEN_DUPLICATE,
                          Token) then
      begin
//////////////////////////////////////////////////////
        if GetSecurityInfo(Token,
                           SE_KERNEL_OBJECT,
                           OWNER_SECURITY_INFORMATION,
                           @Owner,
                           @Group,
                           @Dacl,
                           @Sacl,
                           SID) = ERROR_SUCCESS then
        begin
          cbName := 255;
          cbDomain := 255;
          SetLength(sName, cbName);
          SetLength(sDomain, cbDomain);
          LookupAccountSidW(nil,
                            Owner,
                            @sName[1],
                            cbName,
                            @sDomain[1],
                            cbDomain,
                            peUse);
          SetLength(sName, cbName);
          SetLength(sDomain, cbDomain);
          LocalFree(Cardinal(SID));
        end;
        CloseHandle(Token);
      end;
//////////////////////////////////////////////////////
      CloseHandle(hProc);
    end          else
    begin
      cbName := 0;
      cbDomain := 0;
    end;
    if cbName > 0 then writeln(' ', String(sName), ' ', string(sDomain)) else writeln;
  end else writeln;
  Result := TRUE;
end;

Also I tried another variant of code between /////////// lines:

        wSize := 0;
        GetKernelObjectSecurity(Token,
                                OWNER_SECURITY_INFORMATION,
                                nil,
                                0,
                                wSize);
        SID := PSECURITY_DESCRIPTOR(HeapAlloc(GetProcessHeap, 0, wSize));
        FillChar(SID^, wSize, #0);
        GetKernelObjectSecurity(Token,
                                OWNER_SECURITY_INFORMATION,
                                SID,
                                wSize,
                                wSize);

        if GetSecurityDescriptorOwner(SID,
                                      Owner,
                                      OwDef) then
        begin
          cbName := 255;
          cbDomain := 255;
          SetLength(sName, cbName);
          SetLength(sDomain, cbDomain);
          LookupAccountSidW(nil,
                            Dacl,
                            @sName[1],
                            cbName,
                            @sDomain[1],
                            cbDomain,
                            peUse);
          SetLength(sName, cbName);
          SetLength(sDomain, cbDomain);
        end;
        HeapFree(GetProcessHeap, 0, SID);
        CloseHandle(Token);
      end;

Both versions work OK on WinXP SP2 but work incorrectly on Win2003 SP1. In this case both versions return "Administrators" for any user from "Administrators" group and "BUILT-IN" instead of computer name. I read in MSDN that it should occur just for really built-in accounts like "Administrator" or "Guest". But in fact it works in a bit different way. Users from "Users" group are gecognized OK, so my general task is resolved now. But I'm just wondering, where I was incorrect in my code?
>>ImpersonateLoggedOnUser( hToken);
>>ExitWindowsEx(EWX_FORCE or EWX_LOGOFF, 0);
>>RevertToSelf;
>>....................
>>Here GetLastError returns "Incorrect function".
>>When I try CreateProcess() instead of ExitWindowsEx() no error is returned

After logging off, I wouldn't rely on anything :o)

Actually, it was my understanding that you were spawining a process to log the user off, e.g.
like

ImpersonateLoggedOnUser( hToken);
CreateProcess("logoff.exe", ...);
RevertToSelf;
Avatar of abb1

ASKER

This is not working too. The only version which is working is CreateProcessAsUser(). I read the help article on ExitWindowsEx() and see the next:
.....................
EWX_LOGOFF      Shuts down all processes running in the security context of the process that called the ExitWindowsEx function. Then it logs the user off.
.....................

Please note the phrase "in the security context of the process that called the ExitWindowsEx" -- not "of the thread that called..." but just "of the process that called..."! WE change just thread SID, not the process's SID! I think the error is covered just here. So, it should be just separate _process_, running in the user's SID. Of course it doesn't answer, why CreateProcess() doesn't work. I only can suppose that ExitWindowsEx can't leave the thread with the same SID running but it also have no permissions to kill it, because it the thread of system process.

One explanation or another, but it doesn't bother me anymore. Please answer my another question,  if you can. Why can't I retrieve a valid user login data for administrative user under Win2003 server? Is it a bug or feature or what? Can you give me correct code sample? In fact this is the only unclear point for me at present.
Sorry for getting back so late, but I am experiencing a major email problem at the moment, so I can just manually check the Qs if any new comments have arrived. Will need to dig in some W2k3 details, since right off the top of my head, I have no idea :-(