Solved

Why does LookupAccountSid fail?

Posted on 2004-10-19
10
1,775 Views
Last Modified: 2011-09-20
I am trying to enumerate the usernames for all window stations that exist on a machine, in order to locate the window station that is owned by a specific user. My code works, up to the point of calling LookupAccountSid, which fails with "the paramater is incorrect." As far as I can tell, I have retrieved a valid SID - at least, it has a nonzero length and the content is vaguely SID-like. Is it a security issue? Is it the fact that I am calling LookupAccountSid from inside an enumerator callback function? Am I doing something else wrong?

Here is my code:


function EnumWindowStationsCallback(lpszWindowStation: pchar;
  lParam: cardinal): boolean; stdcall;

var
  Username: string;
  UserDomain: string;
  SIDStruct: pointer;
  SIDLength: cardinal;
  hWS: cardinal;
  dwNameSize, dwDomainSize: cardinal;
  eUse: SID_NAME_USE;

begin
  try
    hWS := OpenWindowStation(lpszWindowStation, false, WINSTA_READATTRIBUTES);
    if hWS <> 0 then
      try
        if (not GetUserObjectInformation(hWS,UOI_NAME,nil,0,SIDLength)) and
          (GetLastError <> ERROR_INSUFFICIENT_BUFFER) then
          raise Exception.Create('GetUserObjectInformation(0) - ' +
            SysErrorMessage(GetLastError) + ' ' + inttostr(SIDLength));
        GetMem(SIDStruct,SIDLength);
        try
          if not GetUserObjectInformation(hWS, UOI_NAME, SIDStruct,
            SIDLength, SIDLength) then
            raise Exception.Create('GetUserObjectInformation('
              + inttostr(SIDLength)+') - ' + SysErrorMessage(GetLastError));
          dwNameSize := 0;
          dwDomainSize := 0;
          if (not LookupAccountSid(nil, SIDStruct, pchar(Username), dwNameSize,
            pchar(UserDomain), dwDomainSize, eUse)) and
            (GetLastError <> ERROR_INSUFFICIENT_BUFFER) then
            raise Exception.Create('LookupAccountSid(0,0) - ' +
              SysErrorMessage(GetLastError));
          SetLength(Username, dwNameSize);
          SetLength(UserDomain, dwDomainSize);
          if not LookupAccountSid(nil, SIDStruct, pchar(Username), dwNameSize,
            pchar(UserDomain), dwDomainSize, eUse) then
            raise Exception.Create('LookupAccountSid('+inttostr(dwNameSize) + ','
              + inttostr(dwDomainSize) + ') - ' + SysErrorMessage(GetLastError));
        finally
          FreeMem(SIDStruct);
        end;
      finally
        CloseWindowStation(hWS);
      end
    else
      raise Exception.Create('OpenWindowStation - ' +
        SysErrorMessage(GetLastError));
  except
    on e: Exception do
      begin
        ShowMessage(e.Message);
        Username := '<unknown>';
      end;
  end;
  Form1.Memo1.Lines.Add(lpszWindowStation + ' - ' + Username);
  result := true;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  Form1.Memo1.Clear;
  EnumWindowStations(@EnumWindowStationsCallback, 0);
end;
0
Comment
Question by:ghjm
  • 5
  • 3
10 Comments
 
LVL 17

Expert Comment

by:Wim ten Brink
ID: 12349250
Just a guess...
GetUserObjectInformation(hWS, UOI_NAME, nil, 0, SIDLength) might be wrong, since you want the SID, not the name...

GetUserObjectInformation(hWS, UOI_USER_SID, nil, 0, SIDLength) might be better...
0
 
LVL 1

Author Comment

by:ghjm
ID: 12350144
Thanks. With that change applied, I now get "no mapping" from the call to LookupAccountSid. I suspect that GetUserObjectInformation is returning a login SID, which I need to somehow convert into a user SID. Does anyone know how to do this?
0
 
LVL 26

Accepted Solution

by:
Russell Libby earned 500 total points
ID: 12350172

Give this a try...

Regards,
Russell

-------

const
  SE_UNKNOWN_OBJECT_TYPE     =  0;
  SE_FILE_OBJECT             =  1;
  SE_SERVICE                 =  2;
  SE_PRINTER                 =  3;
  SE_REGISTRY_KEY            =  4;
  SE_LMSHARE                 =  5;
  SE_KERNEL_OBJECT           =  6;
  SE_WINDOW_OBJECT           =  7;
  SE_DS_OBJECT               =  8;
  SE_DS_OBJECT_ALL           =  9;
  SE_PROVIDER_DEFINED_OBJECT =  10;
  SE_WMIGUID_OBJECT          =  11;

type
  SE_OBJECT_TYPE             =  DWORD;

type
  PPSID                      =  ^PSID;
  PPACL                      =  ^PACL;
  PPSECURITY_DESCRIPTOR      =  ^PSECURITY_DESCRIPTOR;

function GetSecurityInfo(handle: THandle;
                         ObjectType: SE_OBJECT_TYPE;
                         SecurityInfo: SECURITY_INFORMATION;
                         ppsidOwner: PPSID;
                         ppsidGroup: PPSID;
                         ppDacl: PPACL;
                         ppSacl: PPACL;
                         ppSecurityDescriptor: PSECURITY_DESCRIPTOR): DWORD; stdcall; external 'advapi32';

implementation
{$R *.DFM}

function EnumWindowStationsCallback(lpszWindowStation: pchar; lParam: cardinal): boolean; stdcall;
var  hWS:           HWINSTA;
     psidOwner:     PSID;
     psidGroup:     PSID;
     pSD:           PSECURITY_DESCRIPTOR;
     lpUser:        Array[0..1023] of Char;
     lpDomain:      Array[0..1023] of Char;
     dwUser:        DWORD;
     dwDomain:      DWORD;
     dwUse:         DWORD;
begin

  // Set defaults
  hWS:=0;

  // Resource protection
  try
     try
        // Open window station
        hWS:=OpenWindowStation(lpszWindowStation, False, READ_CONTROL or ACCESS_SYSTEM_SECURITY);
        // Check window station handle
        if (hWS = 0) then
           // Raise error
           RaiseLastWin32Error
        else
        begin
           // Get security info
           if (GetSecurityInfo(hWS, SE_WINDOW_OBJECT, GROUP_SECURITY_INFORMATION or OWNER_SECURITY_INFORMATION, @psidOwner, @psidGroup, nil, nil, @pSD) <> ERROR_SUCCESS) then
              // Raise error
              RaiseLastWin32Error
           else
           begin
              // Resource protection
              try
                 // Prepare for call to lookup account info
                 dwUser:=SizeOf(lpUser);
                 dwDomain:=SizeOf(lpDomain);
                 // Get the account info
                 if LookupAccountSid(nil, psidOwner, @lpUser, dwUser, @lpDomain, dwDomain, dwUse) then
                 begin
                    // Check Use type
                    case dwUse of
                       SidTypeUser             :  ShowMessage('SidTypeUser');
                       SidTypeGroup            :  ShowMessage('SidTypeGroup');
                       SidTypeDomain           :  ShowMessage('SidTypeDomain');
                       SidTypeAlias            :  ShowMessage('SidTypeAlias');
                       SidTypeWellKnownGroup   :  ShowMessage('SidTypeWellKnownGroup');
                       SidTypeDeletedAccount   :  ShowMessage('SidTypeDeletedAccount');
                       SidTypeInvalid          :  ShowMessage('SidTypeInvalid');
                       SidTypeUnknown          :  ShowMessage('SidTypeUnknown');
                    end;
                 end
                 else
                    // Raise error
                    RaiseLastWin32Error;
              finally
                 // Free the security descriptor
                 LocalFree(HLOCAL(pSD));
              end;
           end;
        end;
     finally
        // Close window station
        if (hWS > 0) then CloseWindowStation(hWS);
     end;
  except
     // Exception trap
     on E: Exception do
     begin
        ShowMessage(E.Message);
        StrCopy(@lpUser, '<unknown>');
     end;
  end;

  Form1.Memo1.Lines.Add(lpszWindowStation + ' - ' + lpUser);
  result:=True;

end;
0
Technology Partners: We Want Your Opinion!

We value your feedback.

Take our survey and automatically be enter to win anyone of the following:
Yeti Cooler, Amazon eGift Card, and Movie eGift Card!

 
LVL 1

Author Comment

by:ghjm
ID: 12350696
Well, I think we're getting closer, but GetSecurityInfo with OWNER_SECURITY_INFORMATION returns "administrators" instead of the name of the user logged in to the window station. Perhaps I need to look at the desktop within the window station?
0
 
LVL 1

Author Comment

by:ghjm
ID: 12351664
Here's a different approach that also isn't working. I get "access denied" on the call to OpenDesktop.


The code:

var
  lpszUsername: pchar;
  hMem: cardinal;

function UsernameGetterThreadProc(lpszDesktop: pchar): cardinal; stdcall;

var
  hD: cardinal;
  UsernameSize: cardinal;

begin
  lpszUsername := nil;
  hD := OpenDesktop(lpszDesktop, 0, true, GENERIC_READ);
  if hD <> 0 then
    begin
      if SetThreadDesktop(hD) then
        begin
          UsernameSize := 0;
          GetUserName(nil,UsernameSize);
          hMem := GlobalAlloc(0,UsernameSize);
          lpszUsername := GlobalLock(hMem);
          GetUserName(lpszUsername,UsernameSize);
        end;
      CloseDesktop(hD);
    end;
  TerminateThread(GetCurrentThread,0);
  result := 0;  // to avoid warning - never executed
end;

var
  DesktopUserList: string;

function EnumDesktopCallback(lpszDesktop: pchar;
  lParam: cardinal): boolean; stdcall;

var
  hThread: cardinal;
  hThread2: cardinal;

begin
  ShowMessage(lpszDesktop);
  lpszUsername := nil;
  hThread := CreateThread(nil, 0, @UsernameGetterThreadProc,
    lpszDesktop, 0, hThread2);
  if hThread <> 0 then
    begin
      WaitForSingleObject(hThread, 0);
      if lpszUsername <> nil then
        begin
          DesktopUserList := DesktopUserList + lpszUsername + ' ';
          GlobalUnlock(hMem);
          GlobalFree(hMem);
        end;
      CloseHandle(hThread);
    end;
  result := true;
end;

function EnumWindowStationsCallback(lpszWindowStation: pchar;
  lParam: cardinal): boolean; stdcall;

var
  hWS: cardinal;

begin
  hWS := OpenWindowStation(lpszWindowStation, false, GENERIC_READ);
  if hWS <> 0 then
    try
      DesktopUserList := '';
      EnumDesktops(hWS, @EnumDesktopCallback, 0);
    finally
      CloseWindowStation(hWS);
    end;
  Form1.Memo1.Lines.Add(lpszWindowStation + ' - ' + DesktopUserList);
  result := true;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  Form1.Memo1.Clear;
  EnumWindowStations(@EnumWindowStationsCallback, 0);
end;
0
 
LVL 26

Expert Comment

by:Russell Libby
ID: 12358041

I have already tried something similar, and for each desktop (and/or window station) it will return the user name that the process executing the above code was started with. I am now looking at a different approach, and will let you know if I have any luck

Regards,
Russell
0
 
LVL 1

Author Comment

by:ghjm
ID: 12358738
Yes, I got it to work and that was the result. Apparently GetUsername depends on something other than the desktop. Which makes sense, since RunAs is capable of running an application in the same desktop, but a different security context.

I still feel there must be a way to get an associated user SID, given a logon SID. But I'm stumped as to what it might be.

-Graham
0
 
LVL 26

Expert Comment

by:Russell Libby
ID: 12358831
Graham,

I am currently working on taking the DACL (discretionary access control list) from the GetSecurityInfo call and breaking it apart so I can walk the ACE entries in it. Each ACE will have a SID associated with it, and I **should** be able to pull out the logon sid from this.

The one issue that I am having though is that the GetSecurityInfo call requires READ_CONTROL access on the handle (window station), but some WindowStations fail to open with this flag set.

eg:

     // Open window station
     hWS:=OpenWindowStation(lpszWindowStation, False, READ_CONTROL);

According to the docs, to get READ_CONTROL access, the caller must be the owner of the object or the object's DACL must grant the access. So, that is where I am currently at with this....

----

Russell


0
 
LVL 1

Author Comment

by:ghjm
ID: 12448006
I do not necessarily need to enumerate the window stations associated with service and system accounts, only those of real-live logged in users. It sounds like you probably already have a solution that would work for me. Please post what you have and I'll award points.

Thanks,

-Graham
0

Featured Post

Technology Partners: We Want Your Opinion!

We value your feedback.

Take our survey and automatically be enter to win anyone of the following:
Yeti Cooler, Amazon eGift Card, and Movie eGift Card!

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

Suggested Solutions

Title # Comments Views Activity
Performance of SQL statement 37 137
FMX and jaudiotracker playing memory stream 29 98
How to Get Images From Server using App Tethering 11 42
Firemonkey Listview item popup menu ? 1 29
The uses clause is one of those things that just tends to grow and grow. Most of the time this is in the main form, as it's from this form that all others are called. If you have a big application (including many forms), the uses clause in the in…
Creating an auto free TStringList The TStringList is a basic and frequently used object in Delphi. On many occasions, you may want to create a temporary list, process some items in the list and be done with the list. In such cases, you have to…
Finds all prime numbers in a range requested and places them in a public primes() array. I've demostrated a template size of 30 (2 * 3 * 5) but larger templates can be built such 210  (2 * 3 * 5 * 7) or 2310  (2 * 3 * 5 * 7 * 11). The larger templa…
Attackers love to prey on accounts that have privileges. Reducing privileged accounts and protecting privileged accounts therefore is paramount. Users, groups, and service accounts need to be protected to help protect the entire Active Directory …

756 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question