Solved

Why does LookupAccountSid fail?

Posted on 2004-10-19
10
1,760 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
Efficient way to get backups off site to Azure

This user guide provides instructions on how to deploy and configure both a StoneFly Scale Out NAS Enterprise Cloud Drive virtual machine and Veeam Cloud Connect in the Microsoft Azure Cloud.

 
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

PRTG Network Monitor: Intuitive Network Monitoring

Network Monitoring is essential to ensure that computer systems and network devices are running. Use PRTG to monitor LANs, servers, websites, applications and devices, bandwidth, virtual environments, remote systems, IoT, and many more. PRTG is easy to set up & use.

Question has a verified solution.

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

Suggested Solutions

Title # Comments Views Activity
Using idhttp to login to instagram 2 89
Intraweb download file link ? 1 133
Working with hours 3 57
tidtcpserver connection lost handle 2 83
Objective: - This article will help user in how to convert their numeric value become words. How to use 1. You can copy this code in your Unit as function 2. than you can perform your function by type this code The Code   (CODE) The Im…
Hello everybody This Article will show you how to validate number with TEdit control, What's the TEdit control? TEdit is a standard Windows edit control on a form, it allows to user to write, read and copy/paste single line of text. Usua…
This tutorial gives a high-level tour of the interface of Marketo (a marketing automation tool to help businesses track and engage prospective customers and drive them to purchase). You will see the main areas including Marketing Activities, Design …
Microsoft Active Directory, the widely used IT infrastructure, is known for its high risk of credential theft. The best way to test your Active Directory’s vulnerabilities to pass-the-ticket, pass-the-hash, privilege escalation, and malware attacks …

770 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