Solved

Why does LookupAccountSid fail?

Posted on 2004-10-19
10
1,740 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
 
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
Find Ransomware Secrets With All-Source Analysis

Ransomware has become a major concern for organizations; its prevalence has grown due to past successes achieved by threat actors. While each ransomware variant is different, we’ve seen some common tactics and trends used among the authors of the malware.

 
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

Highfive Gives IT Their Time Back

Highfive is so simple that setting up every meeting room takes just minutes and every employee will be able to start or join a call from any room with ease. Never be called into a meeting just to get it started again. This is how video conferencing should work!

Join & Write a Comment

Suggested Solutions

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…
Introduction Raise your hands if you were as upset with FireMonkey as I was when I discovered that there was no TListview.  I use TListView in almost all of my applications I've written, and I was not going to compromise by resorting to TStringGrid…
Polish reports in Access so they look terrific. Take yourself to another level. Equations, Back Color, Alternate Back Color. Write easy VBA Code. Tighten space to use less pages. Launch report from a menu, considering criteria only when it is filled…
This tutorial demonstrates a quick way of adding group price to multiple Magento products.

743 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

Need Help in Real-Time?

Connect with top rated Experts

10 Experts available now in Live!

Get 1:1 Help Now