• Status: Solved
  • Priority: Medium
  • Security: Public
  • Views: 1911
  • Last Modified:

Why does LookupAccountSid fail?

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
ghjm
Asked:
ghjm
  • 5
  • 3
1 Solution
 
Wim ten BrinkSelf-employed developerCommented:
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
 
ghjmAuthor Commented:
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
 
Russell LibbySoftware Engineer, Advisory Commented:

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
Get expert help—faster!

Need expert help—fast? Use the Help Bell for personalized assistance getting answers to your important questions.

 
ghjmAuthor Commented:
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
 
ghjmAuthor Commented:
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
 
Russell LibbySoftware Engineer, Advisory Commented:

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
 
ghjmAuthor Commented:
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
 
Russell LibbySoftware Engineer, Advisory Commented:
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
 
ghjmAuthor Commented:
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

Free Tool: Site Down Detector

Helpful to verify reports of your own downtime, or to double check a downed website you are trying to access.

One of a set of tools we are providing to everyone as a way of saying thank you for being a part of the community.

  • 5
  • 3
Tackle projects and never again get stuck behind a technical roadblock.
Join Now