diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..e7d25e2 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,154 @@ +# Changelog +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + + +## [1.0.0] - 2020-05-26 + +### Added + +* Added the following commands: + * NTLMSettings, SCCM, WSUS, UserRightAssignments, IdleTime, FileInfo, NamedPipes, NetworkProfile + * AMSIProviders, RPCMappedEndpoints, LocalUsers, CredGuard, LocalGPOs, OutlookDownloads + * AppLocker (thanks @_RastaMouse! https://github.com/GhostPack/Seatbelt/pull/15) + * InstalledProducts and Printers commands, with DACLs included for printers + * SearchIndex - module to search the Windows Search Indexer + * WMIEventFilter/WMIEventConsumer/WMIEventConsumer commands + * ScheduledTasks command (via WMI for win8+) + * AuditPolicies/AuditSettings - classic and advanced audit policy settings + * EnvironmentPath - %ENV:PATH% folder enumeration, along with DACLs + * ProcessCreation - from @djhohnstein's EventLogParser project. Expanded sensitive regexes. + * CredEnum - use CredEnumerate() to enumerate the credentials from the user's credential set (thanks @djhohnstein and @peewpw) + * SecurityPackages - uses EnumerateSecurityPackages() to enumerate available security packages + * WindowsDefender - exclusions for paths/extensions/processes for Windows Defender + * DotNet - detects .NET versions and whether AMSI is enabled/can by bypassed (similar to 'PowerShell') + * ProcessOwners - simplified enumeration of non-session 0 processes/owners that can function remotely + * dir + * Allows recursively enumerating directories and searching for files based on a regex + * Lists user folders by default + * Usage: "dir [path] [depth] [searchRegex] [ignoreErrors? true/false]" + * Default: "dir C:\users\ 2 \\(Documents|Downloads|Desktop) false" + * Shows files in users' documents/downloads/desktop folders + * reg + * Allows recursively listing and searching for registry values on the current machine and remotely (if remote registry is enabled). + * Added additional defensive process checks thanks to @swarleysez, @Ne0nd0g, and @leechristensen. See https://github.com/GhostPack/Seatbelt/pull/17 and https://github.com/GhostPack/Seatbelt/pull/19. + * Added Xen virtual machine detections thanks to @rasta-mouse. See https://github.com/GhostPack/Seatbelt/pull/18 +* Added the following command aliases: + * "Remote" for common commands to run remotely + * "Slack" to run Slack-specific modules + * "Chrome" to run Chrome-specific modules +* Added in ability to give commands arguments (to be expanded in the future). Syntax: `Seatbelt.exe "PoweredOnEvents 30"` +* Added remote support for WMI/registry enumeration modules that are marked with a + + * Usage: computername=COMPUTER.DOMAIN.COM [username=DOMAIN\USER password=PASSWORD] +* Added the "-q" command-line flag to not print the logo +* Added ability to output to a file with the the "-o " parameter + * Providing a file that ends in .json produces JSON-structured output! +* Added in the architecture for different output sinks. Still need to convert a lot of cmdlets to the new format. +* Added a module template. +* Added CHANGELOG.md. + + +### Changed + +* Externalized all commands into their own class/file +* Cleaned up some of the registry querying code +* Commands can now be case-insensitive +* Seatbelt's help message is now dynamically created +* Renamed RebootSchedule to PoweredOnEvents + * Now enumerates events for system startup/shutdown, unexpected shutdown, and sleeping/awaking. +* Modified the output of the Logon and ExplicitLogon event commands to be easier to read/analyze +* LogonEvents, ExplicitLogonEvents, and PoweredOnEvents take an argument of how many days back to collect logs for. Example: Seatbelt.exe "LogonEvents 50" +* Added Added timezone, locale information, MachineGuid, Build number and UBR (if present) to OSInfo command +* Refactored registry enumeration code +* Putty command now lists if agent forwarding is enabled +* Renamed BasicOSInfo to OSInfo +* Simplified IsLocalAdmin code +* Added the member type to localgroupmembership output +* Simplified the RDPSavedConnections code +* Formatted the output of RDPSavedConnections to be prettier +* Formatted the output of RecentFiles to be prettier +* Modified logonevents default so that it only outputs the past day on servers +* Re-wrote the PowerShell command. Added AMSI information and hints for bypassing. +* Add NTLM/Kerberos informational alerts to the LogonEvents command +* Changed the output format of DpapiMasterKeys +* Re-wrote the Registry helper code +* Refactored the helper code +* Incorprated [@mark-s's](https://github.com/mark-s) code to speed up the interestingfiles command. See [#16](https://github.com/GhostPack/Seatbelt/pull/16) +* Added SDDL to the "fileinfo" command +* Added MRUs for all office applications to the RecentFiles command +* RecentFiles now has a paramater that restricts how old the documents are. "RecentFiles 20" - Shows files accessed in the last 20 days. +* Renamed RegistryValue command to "reg" +* Search terms in the "reg" command now match keys, value names, and values. +* Updated the "reg" commands arguments. + * Usage: "reg [depth] [searchTerm] [ignoreErrors]" + * Defaults: "reg HKLM\Software 1 default true" +* Added generic GetSecurityInfos command into SecurityUtil +* Formatting tweak for DPAPIMasterkeys +* WindowsVaults output filtering +* Renamed RecentFiles to ExplorerMRUs, broke out functionality for ExplorerMRUs and OfficeMRUs +* Broke IETriage command into IEUrls and IEFavorites +* Changed FirefoxCommand to FirefoxHistory +* Changed ChromePresence and FirefoxPresence to display last modified timestamps for the history/cred/etc. files +* Split ChromeCommand into ChromeHistoryCommand and ChromeBookmarksCommand +* Broke PuttyCommand into PuttyHostKeys and PuttySessions +* Added SDDL field to InterestingFiles command +* Modified IdleTime to display the current user and time in h:m:s:ms format +* Moved Firewall enumeration to the registry (instead of the COM object). Thanks @Max_68! +* Changed TokenGroups output formatting +* Renamed localgroupmemberships to localgroups +* Changed network firewall enumeration to display "non-builtin" rules instead of deny. Added basic filtering. +* Added IsDotNet property to the FileInfo command +* Renamed "NonstandardProcesses" and "NonstandardServices" to "Processes" and "Services", respectively +* LocalGroups now enumerates all (by default non-empty) local groups and memberships, along with comments +* Added a "modules" argument to the "Processes" command to display non-Microsoft loaded processes +* Notify operator when LSA Protected Mode is enabled (RunAsPPL) +* Updated the EnvironmentVariables command to distinguish between user/system/current process/volatile variables +* Added a user filter to ExplicitLogonEvents. Usage: `ExplicitLogonEvents ` +* Added version check for Chrome (v80+) +* Added analysis messages for the logonevents command +* Rewrote and expanded README.md + + +### Fixed + +* Some timestamp converting code in the ticket extraction section +* Fixed Chrome bookmark command (threw an exception with folders) +* Fixed reboot schedule (xpath query wasn't precise enough, leading to exceptions) +* Fixed an exception that was being thrown in the CloudCredential command +* NonstandardServices command + * Fixed a bug that occurred during enumeration + * Added ServiceDll and User fields + * Partially fixed path parsing in NonstandardServices with some help from OJ (@TheColonial)! See https://github.com/GhostPack/Seatbelt/pull/14 + * Cleaned up the code +* Fixed a bug in localgroupmembership +* Check if it's a Server before running the AntiVirus check (the WMI class isn't on servers) +* Fixed a bug in WindowsCredentialFiles so it wouldn't output null bytes +* Fixed a null reference bug in the PowerShell command +* Fixed the OS version comparisons in WindowsVault command +* Fixed a DWORD parsing bug in the registry util class for big (i.e. negative int) values +* ARPTable bug fix/error handling +* Fixed PuttySession HKCU v. HKU bug +* Fixed a terminating exception bug in the Processes command when obtaining file version info +* More additional bug fixes than we can count >_< + + +### Removed + +* Removed the UserFolder command (replaced by DirectoryList command) + + +## [0.2.0] - 2018-08-20 + +### Added +* @djhohnstein's vault enumeration + + +### Changed +* @ClementNotin/@cnotin's various fixes + + +## [0.1.0] - 2018-07-24 + +* Initial release diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..180919f --- /dev/null +++ b/LICENSE @@ -0,0 +1,14 @@ +Rubeus is provided under the 3-clause BSD license below. + +************************************************************* + +Copyright (c) 2020, Will Schroeder and Lee Christensen +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + The names of its contributors may not be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/README.md b/README.md index e6cc90e..fceb58f 100755 --- a/README.md +++ b/README.md @@ -6,91 +6,404 @@ Seatbelt is a C# project that performs a number of security oriented host-survey [@andrewchiles](https://twitter.com/andrewchiles)' [HostEnum.ps1](https://github.com/threatexpress/red-team-scripts/blob/master/HostEnum.ps1) script and [@tifkin\_](https://twitter.com/tifkin_)'s [Get-HostProfile.ps1](https://github.com/leechristensen/Random/blob/master/PowerShellScripts/Get-HostProfile.ps1) provided inspiration for many of the artifacts to collect. -[@harmj0y](https://twitter.com/harmj0y) is the primary author of this implementation. +[@harmj0y](https://twitter.com/harmj0y) and [@tifkin_](https://twitter.com/tifkin_) are the primary authors of this implementation. Seatbelt is licensed under the BSD 3-Clause license. -## Usage -**SeatBelt.exe system** collects the following system data: +## Table of Contents - BasicOSInfo - Basic OS info (i.e. architecture, OS version, etc.) - RebootSchedule - Reboot schedule (last 15 days) based on event IDs 12 and 13 - TokenGroupPrivs - Current process/token privileges (e.g. SeDebugPrivilege/etc.) - UACSystemPolicies - UAC system policies via the registry - PowerShellSettings - PowerShell versions and security settings - AuditSettings - Audit settings via the registry - WEFSettings - Windows Event Forwarding (WEF) settings via the registry - LSASettings - LSA settings (including auth packages) - UserEnvVariables - Current user environment variables - SystemEnvVariables - Current system environment variables - UserFolders - Folders in C:\Users\ - NonstandardServices - Services with file info company names that don't contain 'Microsoft' - InternetSettings - Internet settings including proxy configs - LapsSettings - LAPS settings, if installed - LocalGroupMembers - Members of local admins, RDP, and DCOM - MappedDrives - Mapped drives - RDPSessions - Current incoming RDP sessions - WMIMappedDrives - Mapped drives via WMI - NetworkShares - Network shares - FirewallRules - Deny firewall rules, "full" dumps all - AntiVirusWMI - Registered antivirus (via WMI) - InterestingProcesses - "Interesting" processes- defensive products and admin tools - RegistryAutoRuns - Registry autoruns - RegistryAutoLogon - Registry autologon information - DNSCache - DNS cache entries (via WMI) - ARPTable - Lists the current ARP table and adapter information (equivalent to arp -a) - AllTcpConnections - Lists current TCP connections and associated processes - AllUdpConnections - Lists current UDP connections and associated processes - NonstandardProcesses - Running processeswith file info company names that don't contain 'Microsoft' - * If the user is in high integrity, the following additional actions are run: - SysmonConfig - Sysmon configuration from the registry +- [Seatbelt](#seatbelt) + * [Table of Contents](#table-of-contents) + * [Command Line Usage](#command-line-usage) + * [Command Groups](#command-groups) + + [system](#system) + + [user](#user) + + [misc](#misc) + + [Additional Command Groups](#additional-command-groups) + * [Command Arguments](#command-arguments) + * [Output](#output) + * [Remote Enumeration](#remote-enumeration) + * [Building Your Own Modules](#building-your-own-modules) + * [Compile Instructions](#compile-instructions) + * [Acknowledgments](#acknowledgments) -**SeatBelt.exe user** collects the following user data: - SavedRDPConnections - Saved RDP connections - TriageIE - Internet Explorer bookmarks and history (last 7 days) - DumpVault - Dump saved credentials in Windows Vault (i.e. logins from Internet Explorer and Edge), from SharpWeb - RecentRunCommands - Recent "run" commands - PuttySessions - Interesting settings from any saved Putty configurations - PuttySSHHostKeys - Saved putty SSH host keys - CloudCreds - AWS/Google/Azure cloud credential files (SharpCloud) - RecentFiles - Parsed "recent files" shortcuts (last 7 days) - MasterKeys - List DPAPI master keys - CredFiles - List Windows credential DPAPI blobs - RDCManFiles - List Windows Remote Desktop Connection Manager settings files - * If the user is in high integrity, this data is collected for ALL users instead of just the current user +## Command Line Usage -Non-default collection options: +``` + %&&@@@&& + &&&&&&&%%%, #&&@@@@@@%%%%%%###############% + &%& %&%% &////(((&%%%%%#%################//((((###%%%%%%%%%%%%%%% + %%%%%%%%%%%######%%%#%%####% &%%**# @////(((&%%%%%%######################((((((((((((((((((( + #%#%%%%%%%#######%#%%####### %&%,,,,,,,,,,,,,,,, @////(((&%%%%%#%#####################((((((((((((((((((( + #%#%%%%%%#####%%#%#%%####### %%%,,,,,, ,,. ,, @////(((&%%%%%%%######################(#(((#(#(((((((((( + #####%%%#################### &%%...... ... .. @////(((&%%%%%%%###############%######((#(#(####(((((((( + #######%##########%######### %%%...... ... .. @////(((&%%%%%#########################(#(#######((##### + ###%##%%#################### &%%............... @////(((&%%%%%%%%##############%#######(#########((##### + #####%###################### %%%.. @////(((&%%%%%%%################ + &%& %%%%% Seatbelt %////(((&%%%%%%%%#############* + &%%&&&%%%%% v1.0.0 ,(((&%%%%%%%%%%%%%%%%%, + #%%%%##, - CurrentDomainGroups - The current user's local and domain groups - Patches - Installed patches via WMI (takes a bit on some systems) - LogonSessions - User logon session data - KerberosTGTData - ALL TEH TGTZ! - InterestingFiles - "Interesting" files matching various patterns in the user's folder - IETabs - Open Internet Explorer tabs - TriageChrome - Chrome bookmarks and history - TriageFirefox - Firefox history (no bookmarks) - RecycleBin - Items in the Recycle Bin deleted in the last 30 days - only works from a user context! - 4624Events - 4624 logon events from the security event log - 4648Events - 4648 explicit logon events from the security event log - KerberosTickets - List Kerberos tickets. If elevated, grouped by all logon sessions. -**SeatBelt.exe all** will run ALL enumeration checks, can be combined with **full**. + Available commands (+ means remote usage is supported): -**SeatBelt.exe [CheckName] full** will prevent any filtering and will return complete results. + + AMSIProviders - Providers registered for AMSI + + AntiVirus - Registered antivirus (via WMI) + AppLocker - AppLocker settings, if installed + ARPTable - Lists the current ARP table and adapter information (equivalent to arp -a) + AuditPolicies - Enumerates classic and advanced audit policy settings + + AuditPolicyRegistry - Audit settings via the registry + + AutoRuns - Auto run executables/scripts/programs + ChromeBookmarks - Parses any found Chrome bookmark files + ChromeHistory - Parses any found Chrome history files + ChromePresence - Checks if interesting Google Chrome files exist + CloudCredentials - AWS/Google/Azure cloud credential files + CredEnum - Enumerates the current user's saved credentials using CredEnumerate() + CredGuard - CredentialGuard configuration + dir - Lists files/folders. By default, lists users' downloads, documents, and desktop folders (arguments == [directory] [depth] [regex] [boolIgnoreErrors] + + DNSCache - DNS cache entries (via WMI) + + DotNet - DotNet versions + DpapiMasterKeys - List DPAPI master keys + EnvironmentPath - Current environment %PATH$ folders and SDDL information + EnvironmentVariables - Current user environment variables + ExplicitLogonEvents - Explicit Logon events (Event ID 4648) from the security event log. Default of 7 days, argument == last X days. + ExplorerMRUs - Explorer most recently used files (last 7 days, argument == last X days) + + ExplorerRunCommands - Recent Explorer "run" commands + FileInfo - Information about a file (version information, timestamps, basic PE info, etc. argument(s) == file path(s) + FirefoxHistory - Parses any found FireFox history files + FirefoxPresence - Checks if interesting Firefox files exist + IdleTime - Returns the number of seconds since the current user's last input. + IEFavorites - Internet Explorer favorites + IETabs - Open Internet Explorer tabs + IEUrls - Internet Explorer typed URLs (last 7 days, argument == last X days) + InstalledProducts - Installed products via the registry + InterestingFiles - "Interesting" files matching various patterns in the user's folder. Note: takes non-trivial time. + + InterestingProcesses - "Interesting" processes - defensive products and admin tools + InternetSettings - Internet settings including proxy configs + + LAPS - LAPS settings, if installed + + LastShutdown - Returns the DateTime of the last system shutdown (via the registry). + LocalGPOs - Local Group Policy settings applied to the machine/local users + + LocalGroups - Non-empty local groups, "-full" displays all groups (argument == computername to enumerate) + + LocalUsers - Local users, whether they're active/disabled, and pwd last set (argument == computername to enumerate) + LogonEvents - Logon events (Event ID 4624) from the security event log. Default of 10 days, argument == last X days. + + LogonSessions - Windows logon sessions + + LSASettings - LSA settings (including auth packages) + + MappedDrives - Users' mapped drives (via WMI) + NamedPipes - Named pipe names and any readable ACL information. + + NetworkProfiles - Windows network profiles + + NetworkShares - Network shares exposed by the machine (via WMI) + + NTLMSettings - NTLM authentication settings + OfficeMRUs - Office most recently used file list (last 7 days) + OSInfo - Basic OS info (i.e. architecture, OS version, etc.) + OutlookDownloads - List files downloaded by Outlook + PoweredOnEvents - Reboot and sleep schedule based on the System event log EIDs 1, 12, 13, 42, and 6008. Default of 7 days, argument == last X days. + + PowerShell - PowerShell versions and security settings + PowerShellEvents - PowerShell script block logs (4104) with sensitive data. + Printers - Installed Printers (via WMI) + ProcessCreationEvents - Process creation logs (4688) with sensitive data. + Processes - Running processes with file info company names that don't contain 'Microsoft', "-full" enumerates all processes + + ProcessOwners - Running non-session 0 process list with owners. For remote use. + + PSSessionSettings - Enumerates PS Session Settings from the registry + + PuttyHostKeys - Saved Putty SSH host keys + + PuttySessions - Saved Putty configuration (interesting fields) and SSH host keys + RDCManFiles - Windows Remote Desktop Connection Manager settings files + + RDPSavedConnections - Saved RDP connections stored in the registry + + RDPSessions - Current incoming RDP sessions (argument == computername to enumerate) + RecycleBin - Items in the Recycle Bin deleted in the last 30 days - only works from a user context! + reg - Registry key values (HKLM\Software by default) argument == [Path] [intDepth] [Regex] [boolIgnoreErrors] + RPCMappedEndpoints - Current RPC endpoints mapped + + SCCM - System Center Configuration Manager (SCCM) settings, if applicable + + ScheduledTasks - Scheduled tasks (via WMI) that aren't authored by 'Microsoft', "-full" dumps all Scheduled tasks + SearchIndex - Query results from the Windows Search Index, default term of 'passsword'. (argument(s) == + SecurityPackages - Enumerates the security packages currently available using EnumerateSecurityPackagesA() + Services - Services with file info company names that don't contain 'Microsoft', "-full" dumps all processes + SlackDownloads - Parses any found 'slack-downloads' files + SlackPresence - Checks if interesting Slack files exist + SlackWorkspaces - Parses any found 'slack-workspaces' files + + Sysmon - Sysmon configuration from the registry + SysmonEvents - Sysmon process creation logs (1) with sensitive data. + TcpConnections - Current TCP connections and their associated processes and services + TokenGroups - The current token's local and domain groups + TokenPrivileges - Currently enabled token privileges (e.g. SeDebugPrivilege/etc.) + + UAC - UAC system policies via the registry + UdpConnections - Current UDP connections and associated processes and services + UserRightAssignments - Configured User Right Assignments (e.g. SeDenyNetworkLogonRight, SeShutdownPrivilege, etc.) argument == computername to enumerate + + WindowsAutoLogon - Registry autologon information + WindowsCredentialFiles - Windows credential DPAPI blobs + + WindowsDefender - Windows Defender settings (including exclusion locations) + + WindowsEventForwarding - Windows Event Forwarding (WEF) settings via the registry + + WindowsFirewall - Non-standard firewall rules, "-full" dumps all (arguments == allow/deny/tcp/udp/in/out/domain/private/public) + WindowsVault - Credentials saved in the Windows Vault (i.e. logins from Internet Explorer and Edge). + WMIEventConsumer - Lists WMI Event Consumers + WMIEventFilter - Lists WMI Event Filters + WMIFilterBinding - Lists WMI Filter to Consumer Bindings + + WSUS - Windows Server Update Services (WSUS) settings, if applicable + + + Seatbelt has the following command groups: All, User, System, Slack, Chrome, Remote, Misc + + You can invoke command groups with "Seatbelt.exe " + + "Seatbelt.exe -group=all" runs all commands + + "Seatbelt.exe -group=user" runs the following commands: + + ChromePresence, CloudCredentials, CredEnum, dir, DpapiMasterKeys, + ExplorerMRUs, ExplorerRunCommands, FirefoxPresence, IdleTime, + IEFavorites, IETabs, IEUrls, MappedDrives, + OfficeMRUs, PuttyHostKeys, PuttySessions, RDCManFiles, + RDPSavedConnections, SlackDownloads, SlackPresence, SlackWorkspaces, + TokenGroups, WindowsCredentialFiles, WindowsVault + + "Seatbelt.exe -group=system" runs the following commands: + + AMSIProviders, AntiVirus, AppLocker, ARPTable, AuditPolicies, + AuditPolicyRegistry, AutoRuns, CredGuard, DNSCache, + DotNet, EnvironmentPath, EnvironmentVariables, InterestingProcesses, + InternetSettings, LAPS, LastShutdown, LocalGPOs, + LocalGroups, LocalUsers, LogonSessions, LSASettings, + NamedPipes, NetworkProfiles, NetworkShares, NTLMSettings, + OSInfo, PoweredOnEvents, PowerShell, Printers, + Processes, PSSessionSettings, RDPSessions, SCCM, + Services, Sysmon, TcpConnections, TokenPrivileges, + UAC, UdpConnections, UserRightAssignments, WindowsAutoLogon, + WindowsDefender, WindowsEventForwarding, WindowsFirewall, WMIEventConsumer, + WMIEventFilter, WMIFilterBinding, WSUS + + "Seatbelt.exe -group=slack" runs the following commands: + + SlackDownloads, SlackPresence, SlackWorkspaces + + "Seatbelt.exe -group=chrome" runs the following commands: + + ChromeBookmarks, ChromeHistory, ChromePresence + + "Seatbelt.exe -group=remote" runs the following commands: + + AMSIProviders, AntiVirus, DotNet, ExplorerRunCommands, InterestingProcesses, + LastShutdown, LogonSessions, LSASettings, MappedDrives, + NetworkProfiles, NetworkShares, NTLMSettings, PowerShell, + ProcessOwners, PuttyHostKeys, PuttySessions, RDPSavedConnections, + RDPSessions, Sysmon, WindowsDefender, WindowsEventForwarding, + WindowsFirewall + + "Seatbelt.exe -group=misc" runs the following commands: + + ChromeBookmarks, ChromeHistory, ExplicitLogonEvents, FileInfo, FirefoxHistory, + InstalledProducts, InterestingFiles, LogonEvents, OutlookDownloads, + PowerShellEvents, ProcessCreationEvents, ProcessOwners, RecycleBin, + reg, RPCMappedEndpoints, ScheduledTasks, SearchIndex, + SecurityPackages, SysmonEvents + + + Examples: + 'Seatbelt.exe [Command2] ...' will run one or more specified checks only + 'Seatbelt.exe -full' will return complete results for a command without any filtering. + 'Seatbelt.exe " [argument]"' will pass an argument to a command that supports it (note the quotes). + 'Seatbelt.exe -group=all' will run ALL enumeration checks, can be combined with "-full". + 'Seatbelt.exe -computername=COMPUTER.DOMAIN.COM [-username=DOMAIN\USER -password=PASSWORD]' will run an applicable check remotely + 'Seatbelt.exe -group=remote -computername=COMPUTER.DOMAIN.COM [-username=DOMAIN\USER -password=PASSWORD]' will run remote specific checks + 'Seatbelt.exe -group=system -outputfile="C:\Temp\out.txt"' will run system checks and output to a .txt file. + 'Seatbelt.exe -group=user -q -outputfile="C:\Temp\out.json"' will run in quiet mode with user checks and output to a .json file. +``` + +**Note:** searches that target users will run for the current user if not-elevated and for ALL users if elevated. + +**A more detailed wiki is coming...** + + +## Command Groups + +**Note:** many commands do some type of filtering by default. Supplying the `-full` argument prevents filtering output. Also, the command group `all` will run all current checks. + +For example, the following command will run ALL checks and returns ALL output: + +`Seatbelt.exe -group=all -full` + +### system + +Runs checks that mine interesting data about the system. + +Executed with: `Seatbelt.exe -group=system` + +| Command | Description | +| ----------- | ----------- | +| AMSIProviders | Providers registered for AMSI | +| AntiVirus | Registered antivirus (via WMI) | +| AppLocker | AppLocker settings, if installed | +| ARPTable | Lists the current ARP table and adapter information(equivalent to arp -a) | +| AuditPolicies | Enumerates classic and advanced audit policy settings | +| AuditSettings | Audit settings via the registry | +| AutoRuns | Auto run executables/scripts/programs | +| CredGuard | CredentialGuard configuration | +| DNSCache | DNS cache entries (via WMI) | +| DotNet | DotNet versions | +| EnvironmentPath | Current environment %PATH$ folders and SDDL information | +| EnvironmentVariables | Current user environment variables | +| InterestingProcesses | "Interesting" processes - defensive products and admin tools | +| InternetSettings | Internet settings including proxy configs | +| LAPS | LAPS settings, if installed | +| LastShutdown | Returns the DateTime of the last system shutdown (via the registry) | +| LocalGPOs | Local Group Policy settings applied to the machine/local users | +| LocalGroups | Non-empty local groups, "full" displays all groups (argument == computername to enumerate) | +| LocalUsers | Local users, whether they're active/disabled, and pwd last set (argument == computername to enumerate) | +| LogonSessions | Logon events (Event ID 4624) from the security event log. Default of 10 days, argument == last X days. | +| LSASettings | LSA settings (including auth packages) | +| NamedPipes | Named pipe names and any readable ACL information | +| NetworkProfiles | Windows network profiles | +| NetworkShares | Network shares exposed by the machine (via WMI) | +| NTLMSettings | NTLM authentication settings | +| OSInfo | Basic OS info (i.e. architecture, OS version, etc.) | +| PoweredOnEvents | Reboot and sleep schedule based on the System event log EIDs 1, 12, 13, 42, and 6008. Default of 7 days, argument == last X days. | +| PowerShell | PowerShell versions and security settings | +| Processes | Running processes with file info company names that don't contain 'Microsoft', "full" enumerates all processes | +| PSSessionSettings | Enumerates PS Session Settings from the registry | +| RDPSessions | Current incoming RDP sessions (argument == computername to enumerate) | +| SCCM | System Center Configuration Manager (SCCM) settings, if applicable | +| Services | Services with file info company names that don't contain 'Microsoft', "full" dumps all processes | +| Sysmon | Sysmon configuration from the registry | +| TcpConnections | Current TCP connections and their associated processes and services | +| TokenPrivileges | Currently enabled token privileges (e.g. SeDebugPrivilege/etc.) | +| UAC | UAC system policies via the registry | +| UdpConnections | Current UDP connections and associated processes and services | +| UserRightAssignments | Configured User Right Assignments (e.g. SeDenyNetworkLogonRight, SeShutdownPrivilege, etc.) argument == computername to enumerate | +| WindowsAutoLogon | Registry autologon information | +| WindowsDefender | Windows Defender settings (including exclusion locations) | +| WindowsEventForwarding | Windows Event Forwarding (WEF) settings via the registry | +| WindowsFirewall | Non-standard firewall rules, "full" dumps all (arguments == allow/deny/tcp/udp/in/out/domain/private/public) | +| WMIEventConsumer | Lists WMI Event Consumers | +| WMIEventFilter | Lists WMI Event Filters | +| WMIFilterBinding | Lists WMI Filter to Consumer Bindings | +| WSUS | Windows Server Update Services (WSUS) settings, if applicable | + + +### user + +Runs checks that mine interesting data about the currently logged on user (if not elevated) or ALL users (if elevated). + +Executed with: `Seatbelt.exe -group=user` + +| Command | Description | +| ----------- | ----------- | +| ChromePresence | Checks if interesting Google Chrome files exist | +| CloudCredentials | AWS/Google/Azure cloud credential files | +| CredEnum | Enumerates the current user's saved credentials using CredEnumerate() | +| dir | Lists files/folders. By default, lists users' downloads, documents, and desktop folders (arguments == \ \ \ | +| DpapiMasterKeys | List DPAPI master keys | +| ExplorerMRUs | Explorer most recently used files (last 7 days, argument == last X days) | +| FirefoxPresence | Checks if interesting Firefox files exist | +| IdleTime | Returns the number of seconds since the current user's last input. | +| IEFavorites | Internet Explorer favorites | +| IETabs | Open Internet Explorer tabs | +| IEUrls| Internet Explorer typed URLs (last 7 days, argument == last X days) | +| MappedDrives | Users' mapped drives (via WMI) | +| OfficeMRUs | Office most recently used file list (last 7 days) | +| PuttyHostKeys | Saved Putty SSH host keys | +| PuttySessions | Saved Putty configuration (interesting fields) and SSH host keys | +| RDCManFiles | Windows Remote Desktop Connection Manager settings files | +| RDPSavedConnections | Saved RDP connections stored in the registry | +| RecentRunCommands | Recent Explorer "run" commands | +| SlackPresence | Checks if interesting Slack files exist | +| TokenGroups | The current token's local and domain groups | +| WindowsCredentialFiles | Windows credential DPAPI blobs | +| WindowsVault | Credentials saved in the Windows Vault (i.e. logins from Internet Explorer and Edge). | + + +### misc + +Runs all miscellaneous checks. + +Executed with: `Seatbelt.exe -group=misc` + +| Command | Description | +| ----------- | ----------- | +| ChromeBookmarks | Parses any found Chrome bookmark files | +| ChromeHistory | Parses any found Chrome history files | +| ExplicitLogonEvents | Explicit Logon events (Event ID 4648) from the security event log. Default of 7 days, argument == last X days. | +| FileInfo | Information about a file (version information, timestamps, basic PE info, etc. argument(s) == file path(s) | +| FirefoxHistory | Parses any found FireFox history files | +| InstalledProducts | Installed products via the registry | +| InterestingFiles | "Interesting" files matching various patterns in the user's folder. Note: takes non-trivial time. | +| LogonEvents | Logon events (Event ID 4624) from the security event log. Default of 10 days, argument == last X days. | +| OutlookDownloads | List files downloaded by Outlook | +| PowerShellEvents | PowerShell script block logs (4104) with sensitive data. | +| Printers | Installed Printers (via WMI) | +| ProcessCreation | Process creation logs (4688) with sensitive data. | +| ProcessOwners | Running non-session 0 process list with owners. For remote use. | +| RecycleBin | Items in the Recycle Bin deleted in the last 30 days - only works from a user context! | +| reg | Registry key values (HKLM\Software by default) argument == [Path] [intDepth] [Regex] [boolIgnoreErrors] | +| RPCMappedEndpoints | Current RPC endpoints mapped | +| ScheduledTasks | Scheduled tasks (via WMI) that aren't authored by 'Microsoft', "full" dumps all Scheduled tasks | +| SearchIndex | Query results from the Windows Search Index, default term of 'passsword'. (argument(s) == \ \ | +| SecurityPackages | Enumerates the security packages currently available using EnumerateSecurityPackagesA() | +| SlackDownloads | Parses any found 'slack-downloads' files | +| SlackWorkspaces | Parses any found 'slack-workspaces' files | +| SysmonEvents | Sysmon process creation logs (1) with sensitive data. | + + +### Additional Command Groups + +Executed with: `Seatbelt.exe -group=GROUPNAME` + +| Alias | Description | +| ----------- | ----------- | +| Slack | Runs modules that start with "Slack*" | +| Chrome | Runs modules that start with "Chrome*" | +| Remote | Runs the following modules (for use against a remote system): AMSIProviders, AntiVirus, AuditSettings, DotNet, InterestingProcesses, LastShutdown, LogonSessions, LSASettings, MappedDrives, NetworkProfiles, NetworkShares, NTLMSettings, PowerShell, PuttyHostKeys, PuttySessions, RDPSavedConnections, RDPSessions, RecentRunCommands, Sysmon, WindowsDefender, WindowsEventForwarding, WindowsFirewall | + + +## Command Arguments + +Command that accept arguments have it noted in their description. To pass an argument to a command, enclose the command an arguments in double quotes. + +For example, the following command returns 4624 logon events for the last 30 days: + +`Seatbelt.exe "LogonEvents 30"` + +The following command queries a registry three levels deep, returning only keys/valueNames/values that match the regex `.*defini.*`, and ignoring any errors that occur. + +`Seatbelt.exe "reg \"HKLM\SOFTWARE\Microsoft\Windows Defender\" 3 .*defini.* true"` + + +## Output + +Seatbelt can redirect its output to a file with the `-outputfile="C:\Path\file.txt"` argument. If the file path ends in .json, the output will be structured json. + +For example, the following command will output the results of system checks to a txt file: + +`Seatbelt.exe -group=system -outputfile="C:\Temp\system.txt"` + + +## Remote Enumeration + +Commands noted with a + in the help menu can be run remotely against another system. This is performed over WMI via queries for WMI classes and WMI's StdRegProv for registry enumeration. + +To enumerate a remote system, supply `-computername=COMPUTER.DOMAIN.COM` - an alternate username and password can be specified with `-username=DOMAIN\USER -password=PASSWORD` + +For example, the following command runs remote-focused checks against a remote system: + +`Seatbelt.exe -group=remote -computername=dc.theshire.local -computername=192.168.230.209 -username=THESHIRE\sam -password="yum \"po-ta-toes\""` + + +## Building Your Own Modules + +Seatbelt's structure is completely modular, allowing for additional command modules to be dropped into the file structure and loaded up dynamically. + +There is a commented command module template at `.\Seatbelt\Commands\Template.cs` for reference. Once built, drop the module in the logical file location, include it in the project in the Visual Studio Solution Explorer, and compile. -**SeatBelt.exe [CheckName] [CheckName2] ...** will run one or more specified checks only (case-sensitive naming!) ## Compile Instructions -We are not planning on releasing binaries for Seatbelt, so you will have to compile yourself :) +We are not planning on releasing binaries for Seatbelt, so you will have to compile yourself. + +Seatbelt has been built against .NET 3.5 and 4.0 with C# 8.0 features and is compatible with [Visual Studio Community Edition](https://go.microsoft.com/fwlink/?LinkId=532606&clcid=0x409). Simply open up the project .sln, choose "release", and build. -Seatbelt has been built against .NET 3.5 and is compatible with [Visual Studio 2015 Community Edition](https://go.microsoft.com/fwlink/?LinkId=532606&clcid=0x409). Simply open up the project .sln, choose "release", and build. ## Acknowledgments -Seatbelt incorporates various code C# snippets and bits of PoCs found throughout research for its capabilities. These snippets and authors are highlighted in the appropriate locations in the source code, and include: +Seatbelt incorporates various collection items, code C# snippets, and bits of PoCs found throughout research for its capabilities. These ideas, snippets, and authors are highlighted in the appropriate locations in the source code, and include: * [@andrewchiles](https://twitter.com/andrewchiles)' [HostEnum.ps1](https://github.com/threatexpress/red-team-scripts/blob/master/HostEnum.ps1) script and [@tifkin\_](https://twitter.com/tifkin_)'s [Get-HostProfile.ps1](https://github.com/leechristensen/Random/blob/master/PowerShellScripts/Get-HostProfile.ps1) provided inspiration for many of the artifacts to collect. * [Boboes' code concerning NetLocalGroupGetMembers](https://stackoverflow.com/questions/33935825/pinvoke-netlocalgroupgetmembers-runs-into-fatalexecutionengineerror/33939889#33939889) @@ -101,11 +414,36 @@ Seatbelt incorporates various code C# snippets and bits of PoCs found throughout * [Rod Stephens' pattern for recursive file enumeration](http://csharphelper.com/blog/2015/06/find-files-that-match-multiple-patterns-in-c/) * [SwDevMan81's snippet for enumerating current token privileges](https://stackoverflow.com/questions/4349743/setting-size-of-token-privileges-luid-and-attributes-array-returned-by-gettokeni) * [Jared Atkinson's PowerShell work on Kerberos ticket caches](https://github.com/Invoke-IR/ACE/blob/master/ACE-Management/PS-ACE/Scripts/ACE_Get-KerberosTicketCache.ps1) -* [Vincent LE TOUX' great C# Kerberos work](https://github.com/vletoux/MakeMeEnterpriseAdmin/blob/master/MakeMeEnterpriseAdmin.ps1#L2939-L2950) * [darkmatter08's Kerberos C# snippet](https://www.dreamincode.net/forums/topic/135033-increment-memory-pointer-issue/) * Numerous [PInvoke.net](https://www.pinvoke.net/) samples <3 * [Jared Hill's awesome CodeProject to use Local Security Authority to Enumerate User Sessions](https://www.codeproject.com/Articles/18179/Using-the-Local-Security-Authority-to-Enumerate-Us) * [Fred's code on querying the ARP cache](https://social.technet.microsoft.com/Forums/lync/en-US/e949b8d6-17ad-4afc-88cd-0019a3ac9df9/powershell-alternative-to-arp-a?forum=ITCG) * [ShuggyCoUk's snippet on querying the TCP connection table](https://stackoverflow.com/questions/577433/which-pid-listens-on-a-given-port-in-c-sharp/577660#577660) * [yizhang82's example of using reflection to interact with COM objects through C#](https://gist.github.com/yizhang82/a1268d3ea7295a8a1496e01d60ada816) +* [@djhohnstein](https://twitter.com/djhohnstein)'s [SharpWeb project](https://github.com/djhohnstein/SharpWeb/blob/master/Edge/SharpEdge.cs) +* [@djhohnstein](https://twitter.com/djhohnstein)'s [EventLogParser project](https://github.com/djhohnstein/EventLogParser) * [@cmaddalena](https://twitter.com/cmaddalena)'s [SharpCloud project](https://github.com/chrismaddalena/SharpCloud), BSD 3-Clause +* [@_RastaMouse](https://twitter.com/_RastaMouse)'s [Watson project](https://github.com/rasta-mouse/Watson/), GPL License +* [@_RastaMouse](https://twitter.com/_RastaMouse)'s [Work on AppLocker enumeration](https://rastamouse.me/2018/09/enumerating-applocker-config/) +* [@peewpw](https://twitter.com/peewpw)'s [Invoke-WCMDump project](https://github.com/peewpw/Invoke-WCMDump/blob/master/Invoke-WCMDump.ps1), GPL License +* TrustedSec's [HoneyBadger project](https://github.com/trustedsec/HoneyBadger/tree/master/modules/post/windows/gather), BSD 3-Clause +* CENTRAL Solutions's [Audit User Rights Assignment Project](https://www.centrel-solutions.com/support/tools.aspx?feature=auditrights), No license +* Collection ideas inspired from [@ukstufus](https://twitter.com/ukstufus)'s [Reconerator](https://github.com/stufus/reconerator) +* Office MRU locations and timestamp parsing information from Dustin Hurlbut's paper [Microsoft Office 2007, 2010 - Registry Artifacts](https://ad-pdf.s3.amazonaws.com/Microsoft_Office_2007-2010_Registry_ArtifactsFINAL.pdf) +* The [Windows Commands list](https://docs.microsoft.com/en-us/windows-server/administration/windows-commands/windows-commands), used for sensitive regex construction +* [Ryan Ries' code for enumeration mapped RPC endpoints](https://stackoverflow.com/questions/21805038/how-do-i-pinvoke-rpcmgmtepeltinqnext) +* [Chris Haas' post on EnumerateSecurityPackages()](https://stackoverflow.com/a/5941873) +* [darkoperator](carlos_perez)'s work [on the HoneyBadger project](https://github.com/trustedsec/HoneyBadger) +* [@airzero24](https://twitter.com/airzero24)'s work on [WMI Registry enumeration](https://github.com/airzero24/WMIReg) +* Alexandru's answer on [RegistryKey.OpenBaseKey alternatives](https://stackoverflow.com/questions/26217199/what-are-some-alternatives-to-registrykey-openbasekey-in-net-3-5) +* Tomas Vera's [post on JavaScriptSerializer](http://www.tomasvera.com/programming/using-javascriptserializer-to-parse-json-objects/) +* Marc Gravell's [note on recursively listing files/folders](https://stackoverflow.com/a/929418) +* [@mattifestation](https://twitter.com/mattifestation)'s [Sysmon rule parser](https://github.com/mattifestation/PSSysmonTools/blob/master/PSSysmonTools/Code/SysmonRuleParser.ps1#L589-L595) +* Some inspiration from spolnik's [Simple.CredentialsManager project](https://github.com/spolnik/Simple.CredentialsManager), Apache 2 license +* [This post on Credential Guard settings](https://www.tenforums.com/tutorials/68926-verify-if-device-guard-enabled-disabled-windows-10-a.html) +* [This thread](https://social.technet.microsoft.com/Forums/windows/en-US/b0e13a16-51a6-4aca-8d44-c85e097f882b/nametype-in-nla-information-for-a-network-profile) on network profile information +* Mark McKinnon's post on [decoding the DateCreated and DateLastConnected SSID values](http://cfed-ttf.blogspot.com/2009/08/decoding-datecreated-and.html) +* This Specops [post on group policy caching](https://specopssoft.com/blog/things-work-group-policy-caching/) +* sa_ddam213's StackOverflow post on [enumerating items in the Recycle Bin](https://stackoverflow.com/questions/18071412/list-filenames-in-the-recyclebin-with-c-sharp-without-using-any-external-files) + +We've tried to do our due diligence for citations, but if we've left someone/something out, please let us know! diff --git a/Seatbelt.sln b/Seatbelt.sln index ab55245..82bbbc7 100755 --- a/Seatbelt.sln +++ b/Seatbelt.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 14 -VisualStudioVersion = 14.0.25420.1 +# Visual Studio 15 +VisualStudioVersion = 15.0.28010.0 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Seatbelt", "Seatbelt\Seatbelt.csproj", "{AEC32155-D589-4150-8FE7-2900DF4554C8}" EndProject @@ -19,4 +19,7 @@ Global GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {582494D9-B9F0-40F9-B7B3-4483685E6732} + EndGlobalSection EndGlobal diff --git a/Seatbelt/Commands/Browser/ChromeBookmarksCommand.cs b/Seatbelt/Commands/Browser/ChromeBookmarksCommand.cs new file mode 100644 index 0000000..329bfa2 --- /dev/null +++ b/Seatbelt/Commands/Browser/ChromeBookmarksCommand.cs @@ -0,0 +1,123 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Web.Script.Serialization; +using Seatbelt.Output.Formatters; +using Seatbelt.Output.TextWriters; + + +namespace Seatbelt.Commands.Browser +{ + // TODO: Listing bookmarks does not account for bookmark folders. It lists folders, but not nested bookmarks/folders inside another folder ) + internal class Bookmark + { + public Bookmark(string name, string url) + { + Name = name; + Url = url; + } + public string Name { get; } + public string Url { get; } + } + + internal class ChromeBookmarksCommand : CommandBase + { + public override string Command => "ChromeBookmarks"; + public override string Description => "Parses any found Chrome bookmark files"; + public override CommandGroup[] Group => new[] { CommandGroup.Misc, CommandGroup.Chrome }; + public override bool SupportRemote => false; + + public ChromeBookmarksCommand(Runtime runtime) : base(runtime) + { + } + public override IEnumerable Execute(string[] args) + { + var userFolder = $"{Environment.GetEnvironmentVariable("SystemDrive")}\\Users\\"; + var dirs = Directory.GetDirectories(userFolder); + foreach (var dir in dirs) + { + var parts = dir.Split('\\'); + var userName = parts[parts.Length - 1]; + if (dir.EndsWith("Public") || dir.EndsWith("Default") || dir.EndsWith("Default User") || + dir.EndsWith("All Users")) + { + continue; + } + + // TODO: Account for other profiles + var userChromeBookmarkPath = $"{dir}\\AppData\\Local\\Google\\Chrome\\User Data\\Default\\Bookmarks"; + + // parses a Chrome bookmarks file + if (!File.Exists(userChromeBookmarkPath)) + continue; + + var bookmarks = new List(); + + try + { + var contents = File.ReadAllText(userChromeBookmarkPath); + + // reference: http://www.tomasvera.com/programming/using-javascriptserializer-to-parse-json-objects/ + var json = new JavaScriptSerializer(); + var deserialized = json.Deserialize>(contents); + var roots = (Dictionary)deserialized["roots"]; + var bookmarkBar = (Dictionary)roots["bookmark_bar"]; + var children = (ArrayList)bookmarkBar["children"]; + + foreach (Dictionary entry in children) + { + var bookmark = new Bookmark( + $"{entry["name"].ToString().Trim()}", + entry.ContainsKey("url") ? $"{entry["url"]}" : "(Bookmark Folder?)" + ); + + bookmarks.Add(bookmark); + } + } + catch (Exception exception) + { + WriteError(exception.ToString()); + } + + yield return new ChromeBookmarksDTO( + userName, + bookmarks + ); + } + } + + internal class ChromeBookmarksDTO : CommandDTOBase + { + public ChromeBookmarksDTO(string userName, List bookmarks) + { + UserName = userName; + Bookmarks = bookmarks; + } + public string UserName { get; } + public List Bookmarks { get; } + } + + [CommandOutputType(typeof(ChromeBookmarksDTO))] + internal class ChromeBookmarksFormatter : TextFormatterBase + { + public ChromeBookmarksFormatter(ITextWriter writer) : base(writer) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + var dto = (ChromeBookmarksDTO)result; + + WriteLine($"Bookmarks ({dto.UserName}):\n"); + + foreach (var bookmark in dto.Bookmarks) + { + WriteLine($" Name : {bookmark.Name}"); + WriteLine($" URL : {bookmark.Url}\n"); + } + WriteLine(); + } + } + } +} diff --git a/Seatbelt/Commands/Browser/ChromeHistoryCommand.cs b/Seatbelt/Commands/Browser/ChromeHistoryCommand.cs new file mode 100644 index 0000000..fe14770 --- /dev/null +++ b/Seatbelt/Commands/Browser/ChromeHistoryCommand.cs @@ -0,0 +1,108 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text.RegularExpressions; +using Seatbelt.Output.Formatters; +using Seatbelt.Output.TextWriters; + + +namespace Seatbelt.Commands.Browser +{ + internal class ChromeHistoryCommand : CommandBase + { + public ChromeHistoryCommand(Runtime runtime) : base(runtime) + { + } + + public override string Command => "ChromeHistory"; + public override string Description => "Parses any found Chrome history files"; + public override CommandGroup[] Group => new[] { CommandGroup.Misc, CommandGroup.Chrome }; + public override bool SupportRemote => false; + + public override IEnumerable Execute(string[] args) + { + var userFolder = $"{Environment.GetEnvironmentVariable("SystemDrive")}\\Users\\"; + var dirs = Directory.GetDirectories(userFolder); + foreach (var dir in dirs) + { + var parts = dir.Split('\\'); + var userName = parts[parts.Length - 1]; + if (dir.EndsWith("Public") || dir.EndsWith("Default") || dir.EndsWith("Default User") || + dir.EndsWith("All Users")) + { + continue; + } + + var userChromeHistoryPath = $"{dir}\\AppData\\Local\\Google\\Chrome\\User Data\\Default\\History"; + + // parses a Chrome history file via regex + if (File.Exists(userChromeHistoryPath)) + { + var historyRegex = new Regex(@"(http|ftp|https|file)://([\w_-]+(?:(?:\.[\w_-]+)+))([\w.,@?^=%&:/~+#-]*[\w@?^=%&/~+#-])?"); + var URLs = new List(); + + try + { + using (var r = new StreamReader(userChromeHistoryPath)) + { + string line; + while ((line = r.ReadLine()) != null) + { + var m = historyRegex.Match(line); + if (m.Success) + { + URLs.Add($"{m.Groups[0].ToString().Trim()}"); + } + } + } + } + catch (IOException exception) + { + WriteError($"IO exception, history file likely in use (i.e. Browser is likely running): {exception.Message}"); + } + catch (Exception exception) + { + WriteError(exception.ToString()); + } + + yield return new ChromeHistoryDTO( + userName, + URLs + ); + } + } + } + + internal class ChromeHistoryDTO : CommandDTOBase + { + public ChromeHistoryDTO(string userName, List urLs) + { + UserName = userName; + URLs = urLs; + } + public string UserName { get; } + public List URLs { get; } + } + + [CommandOutputType(typeof(ChromeHistoryDTO))] + internal class ChromeHistoryFormatter : TextFormatterBase + { + public ChromeHistoryFormatter(ITextWriter writer) : base(writer) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + var dto = (ChromeHistoryDTO)result; + + WriteLine($"History ({dto.UserName}):\n"); + + foreach (var url in dto.URLs) + { + WriteLine($" {url}"); + } + WriteLine(); + } + } + } +} diff --git a/Seatbelt/Commands/Browser/ChromePresenceCommand.cs b/Seatbelt/Commands/Browser/ChromePresenceCommand.cs new file mode 100644 index 0000000..ceb789f --- /dev/null +++ b/Seatbelt/Commands/Browser/ChromePresenceCommand.cs @@ -0,0 +1,135 @@ +using System; +using System.Collections.Generic; +using System.IO; +using Seatbelt.Output.TextWriters; +using Seatbelt.Output.Formatters; +using System.Diagnostics; +using Seatbelt.Util; +using Microsoft.Win32; + +namespace Seatbelt.Commands.Browser +{ + internal class ChromePresenceCommand : CommandBase + { + public override string Command => "ChromePresence"; + public override string Description => "Checks if interesting Google Chrome files exist"; + public override CommandGroup[] Group => new[] { CommandGroup.User, CommandGroup.Chrome }; + public override bool SupportRemote => false; + + public ChromePresenceCommand(Runtime runtime) : base(runtime) + { + } + + public override IEnumerable Execute(string[] args) + { + string chromeVersion = ""; + + var chromePath = RegistryUtil.GetStringValue(RegistryHive.LocalMachine, @"SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\chrome.exe", ""); + if (chromePath != null) + { + chromeVersion = FileVersionInfo.GetVersionInfo(chromePath).ProductVersion; + } + + var userFolder = $"{Environment.GetEnvironmentVariable("SystemDrive")}\\Users\\"; + var dirs = Directory.GetDirectories(userFolder); + foreach (var dir in dirs) + { + if (dir.EndsWith("Public") || dir.EndsWith("Default") || dir.EndsWith("Default User") || + dir.EndsWith("All Users")) + { + continue; + } + + var chromeBasePath = $"{dir}\\AppData\\Local\\Google\\Chrome\\User Data\\Default\\"; + if (!Directory.Exists(chromeBasePath)) + { + continue; + } + + var history = new DateTime(); + var cookies = new DateTime(); + var loginData = new DateTime(); + + var userChromeHistoryPath = $"{dir}\\AppData\\Local\\Google\\Chrome\\User Data\\Default\\History"; + if (File.Exists(userChromeHistoryPath)) + { + history = File.GetLastWriteTime(userChromeHistoryPath); + } + + var userChromeCookiesPath = $"{dir}\\AppData\\Local\\Google\\Chrome\\User Data\\Default\\Cookies"; + if (File.Exists(userChromeCookiesPath)) + { + cookies = File.GetLastWriteTime(userChromeCookiesPath); + } + + var userChromeLoginDataPath = $"{dir}\\AppData\\Local\\Google\\Chrome\\User Data\\Default\\Login Data"; + if (File.Exists(userChromeLoginDataPath)) + { + loginData = File.GetLastWriteTime(userChromeLoginDataPath); + } + + if (history != DateTime.MinValue || cookies != DateTime.MinValue || loginData != DateTime.MinValue) + { + yield return new ChromePresenceDTO( + $"{dir}\\AppData\\Local\\Google\\Chrome\\User Data\\Default\\", + history, + cookies, + loginData, + chromeVersion + ); + } + } + } + + internal class ChromePresenceDTO : CommandDTOBase + { + public ChromePresenceDTO(string folder, DateTime historyLastModified, DateTime cookiesLastModified, DateTime loginDataLastModified, string chromeVersion) + { + Folder = folder; + HistoryLastModified = historyLastModified; + CookiesLastModified = cookiesLastModified; + LoginDataLastModified = loginDataLastModified; + ChromeVersion = chromeVersion; + } + + public string Folder { get; } + public DateTime HistoryLastModified { get; } + public DateTime CookiesLastModified { get; } + public DateTime LoginDataLastModified { get; } + public string ChromeVersion { get; } + } + + [CommandOutputType(typeof(ChromePresenceDTO))] + internal class ChromePresenceFormatter : TextFormatterBase + { + public ChromePresenceFormatter(ITextWriter writer) : base(writer) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + var dto = (ChromePresenceDTO)result; + + WriteLine(" {0}\n", dto.Folder); + if (dto.HistoryLastModified != DateTime.MinValue) + { + WriteLine(" 'History' ({0}) : Run the 'ChromeHistory' command", dto.HistoryLastModified); + } + if (dto.CookiesLastModified != DateTime.MinValue) + { + WriteLine(" 'Cookies' ({0}) : Run SharpDPAPI/SharpChrome or the Mimikatz \"dpapi::chrome\" module", dto.CookiesLastModified); + } + if (dto.LoginDataLastModified != DateTime.MinValue) + { + WriteLine(" 'Login Data' ({0}) : Run SharpDPAPI/SharpChrome or the Mimikatz \"dpapi::chrome\" module", dto.LoginDataLastModified); + } + + WriteLine(" Chrome Version : {0}", dto.ChromeVersion); + if (dto.ChromeVersion.StartsWith("8")) + { + WriteLine(" Version is 80+, new DPAPI scheme must be used"); + } + } + } + } +} diff --git a/Seatbelt/Commands/Browser/FirefoxHistoryCommand.cs b/Seatbelt/Commands/Browser/FirefoxHistoryCommand.cs new file mode 100644 index 0000000..cbf71de --- /dev/null +++ b/Seatbelt/Commands/Browser/FirefoxHistoryCommand.cs @@ -0,0 +1,116 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text.RegularExpressions; +using Seatbelt.Output.TextWriters; +using Seatbelt.Output.Formatters; + + +namespace Seatbelt.Commands.Browser +{ + internal class FirefoxHistoryCommand : CommandBase + { + public override string Command => "FirefoxHistory"; + public override string Description => "Parses any found FireFox history files"; + public override CommandGroup[] Group => new[] { CommandGroup.Misc }; + public override bool SupportRemote => false; + + public FirefoxHistoryCommand(Runtime runtime) : base(runtime) + { + } + + public override IEnumerable Execute(string[] args) + { + var userFolder = $"{Environment.GetEnvironmentVariable("SystemDrive")}\\Users\\"; + var dirs = Directory.GetDirectories(userFolder); + foreach (var dir in dirs) + { + var parts = dir.Split('\\'); + var userName = parts[parts.Length - 1]; + + if (dir.EndsWith("Public") || dir.EndsWith("Default") || dir.EndsWith("Default User") || + dir.EndsWith("All Users")) + { + continue; + } + + var userFirefoxBasePath = $"{dir}\\AppData\\Roaming\\Mozilla\\Firefox\\Profiles\\"; + + // parses a Firefox history file via regex + if (Directory.Exists(userFirefoxBasePath)) + { + var history = new List(); + var directories = Directory.GetDirectories(userFirefoxBasePath); + + foreach (var directory in directories) + { + var firefoxHistoryFile = $"{directory}\\places.sqlite"; + var historyRegex = new Regex(@"(http|ftp|https|file)://([\w_-]+(?:(?:\.[\w_-]+)+))([\w.,@?^=%&:/~+#-]*[\w@?^=%&/~+#-])?"); + + try + { + using (var r = new StreamReader(firefoxHistoryFile)) + { + string line; + while ((line = r.ReadLine()) != null) + { + var m = historyRegex.Match(line); + if (m.Success) + { + // WriteHost(" " + m.Groups[0].ToString().Trim()); + history.Add(m.Groups[0].ToString().Trim()); + } + } + } + } + catch (IOException exception) + { + WriteError($"IO exception, places.sqlite file likely in use (i.e. Firefox is likely running). {exception.Message}"); + } + catch (Exception e) + { + WriteError(e.ToString()); + } + } + + yield return new FirefoxHistoryDTO( + userName, + history + ); + } + } + } + + internal class FirefoxHistoryDTO : CommandDTOBase + { + public FirefoxHistoryDTO(string userName, List history) + { + UserName = userName; + History = history; + } + public string UserName { get; } + public List History { get; } + } + + [CommandOutputType(typeof(FirefoxHistoryDTO))] + internal class InternetExplorerFavoritesFormatter : TextFormatterBase + { + public InternetExplorerFavoritesFormatter(ITextWriter writer) : base(writer) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + var dto = (FirefoxHistoryDTO)result; + + WriteLine($"\n History ({dto.UserName}):\n"); + + foreach (var history in dto.History) + { + WriteLine($" {history}"); + } + WriteLine(); + } + } + } +} diff --git a/Seatbelt/Commands/Browser/FirefoxPresenceCommand.cs b/Seatbelt/Commands/Browser/FirefoxPresenceCommand.cs new file mode 100644 index 0000000..27bbd9d --- /dev/null +++ b/Seatbelt/Commands/Browser/FirefoxPresenceCommand.cs @@ -0,0 +1,116 @@ +using System; +using System.Collections.Generic; +using System.IO; +using Seatbelt.Output.TextWriters; +using Seatbelt.Output.Formatters; + + +namespace Seatbelt.Commands.Browser +{ + internal class FirefoxPresenceCommand : CommandBase + { + public override string Command => "FirefoxPresence"; + public override string Description => "Checks if interesting Firefox files exist"; + public override CommandGroup[] Group => new[] { CommandGroup.User }; + public override bool SupportRemote => false; + + public FirefoxPresenceCommand(Runtime runtime) : base(runtime) + { + } + + public override IEnumerable Execute(string[] args) + { + + var userFolder = $"{Environment.GetEnvironmentVariable("SystemDrive")}\\Users\\"; + var dirs = Directory.GetDirectories(userFolder); + foreach (var dir in dirs) + { + if (dir.EndsWith("Public") || dir.EndsWith("Default") || dir.EndsWith("Default User") || dir.EndsWith("All Users")) + continue; + + var userFirefoxBasePath = $"{dir}\\AppData\\Roaming\\Mozilla\\Firefox\\Profiles\\"; + if (!Directory.Exists(userFirefoxBasePath)) + continue; + + var historyLastModified = new DateTime(); + var credentialFile3LastModified = new DateTime(); + var credentialFile4LastModified = new DateTime(); + + var directories = Directory.GetDirectories(userFirefoxBasePath); + foreach (var directory in directories) + { + var firefoxHistoryFile = $"{directory}\\places.sqlite"; + if (File.Exists(firefoxHistoryFile)) + { + historyLastModified = File.GetLastWriteTime(firefoxHistoryFile); + } + + var firefoxCredentialFile3 = $"{directory}\\key3.db"; + if (File.Exists(firefoxCredentialFile3)) + { + credentialFile3LastModified = File.GetLastWriteTime(firefoxCredentialFile3); + } + + var firefoxCredentialFile4 = $"{directory}\\key4.db"; + if (File.Exists(firefoxCredentialFile4)) + { + credentialFile4LastModified = File.GetLastWriteTime(firefoxCredentialFile4); + } + + if (historyLastModified != DateTime.MinValue || credentialFile3LastModified != DateTime.MinValue || credentialFile4LastModified != DateTime.MinValue) + { + yield return new FirefoxPresenceDTO( + directory, + historyLastModified, + credentialFile3LastModified, + credentialFile4LastModified + ); + } + } + } + } + + internal class FirefoxPresenceDTO : CommandDTOBase + { + public FirefoxPresenceDTO(string folder, DateTime historyLastModified, DateTime credentialFile3LastModified, DateTime credentialFile4LastModified) + { + Folder = folder; + HistoryLastModified = historyLastModified; + CredentialFile3LastModified = credentialFile3LastModified; + CredentialFile4LastModified = credentialFile4LastModified; + } + public string Folder { get; } + public DateTime HistoryLastModified { get; } + public DateTime CredentialFile3LastModified { get; } + public DateTime CredentialFile4LastModified { get; } + } + + [CommandOutputType(typeof(FirefoxPresenceDTO))] + internal class FirefoxPresenceFormatter : TextFormatterBase + { + public FirefoxPresenceFormatter(ITextWriter writer) : base(writer) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + var dto = (FirefoxPresenceDTO)result; + + WriteLine(" {0}\\\n", dto.Folder); + if (dto.HistoryLastModified != DateTime.MinValue) + { + WriteLine(" 'places.sqlite' ({0}) : History file, run the 'FirefoxTriage' command", dto.HistoryLastModified); + } + if (dto.CredentialFile3LastModified != DateTime.MinValue) + { + WriteLine(" 'key3.db' ({0}) : Credentials file, run SharpWeb (https://github.com/djhohnstein/SharpWeb)", dto.CredentialFile3LastModified); + } + if (dto.CredentialFile4LastModified != DateTime.MinValue) + { + WriteLine(" 'key4.db' ({0}) : Credentials file, run SharpWeb (https://github.com/djhohnstein/SharpWeb)", dto.CredentialFile4LastModified); + } + WriteLine(); + } + } + } +} diff --git a/Seatbelt/Commands/Browser/InternetExplorerFavoritesCommand.cs b/Seatbelt/Commands/Browser/InternetExplorerFavoritesCommand.cs new file mode 100644 index 0000000..1998666 --- /dev/null +++ b/Seatbelt/Commands/Browser/InternetExplorerFavoritesCommand.cs @@ -0,0 +1,115 @@ +#nullable disable +using System; +using System.Collections.Generic; +using System.IO; +using Seatbelt.Output.Formatters; +using Seatbelt.Output.TextWriters; + + +namespace Seatbelt.Commands.Browser +{ + // TODO: Bookmarks get collected, but Bookmark folders are not collected + // ^ what does this mean exactly? - @harmj0y + internal class InternetExplorerFavoritesCommand : CommandBase + { + public override string Command => "IEFavorites"; + public override string Description => "Internet Explorer favorites"; + public override CommandGroup[] Group => new[] { CommandGroup.User }; + public override bool SupportRemote => false; + + + public InternetExplorerFavoritesCommand(Runtime runtime) : base(runtime) + { + } + + public override IEnumerable Execute(string[] args) + { + var userFolder = $"{Environment.GetEnvironmentVariable("SystemDrive")}\\Users\\"; + var dirs = Directory.GetDirectories(userFolder); + foreach (var dir in dirs) + { + var parts = dir.Split('\\'); + var userName = parts[parts.Length - 1]; + if (dir.EndsWith("Public") || dir.EndsWith("Default") || dir.EndsWith("Default User") || + dir.EndsWith("All Users")) + { + continue; + } + + var userIEBookmarkPath = $"{dir}\\Favorites\\"; + if (!Directory.Exists(userIEBookmarkPath)) + { + continue; + } + + var bookmarkPaths = Directory.GetFiles(userIEBookmarkPath, "*.url", SearchOption.AllDirectories); + if (bookmarkPaths.Length == 0) + { + continue; + } + + var Favorites = new List(); + + foreach (var bookmarkPath in bookmarkPaths) + { + using (var rdr = new StreamReader(bookmarkPath)) + { + string line; + var url = ""; + + while ((line = rdr.ReadLine()) != null) + { + if (!line.StartsWith("URL=", StringComparison.InvariantCultureIgnoreCase)) + { + continue; + } + + if (line.Length > 4) + { + url = line.Substring(4); + } + + break; + } + + Favorites.Add(url.Trim()); + } + } + + yield return new InternetExplorerFavoritesDTO() + { + UserName = userName, + Favorites = Favorites + }; + } + } + + internal class InternetExplorerFavoritesDTO : CommandDTOBase + { + public string UserName { get; set; } + public List Favorites { get; set; } + } + + [CommandOutputType(typeof(InternetExplorerFavoritesDTO))] + internal class InternetExplorerFavoritesFormatter : TextFormatterBase + { + public InternetExplorerFavoritesFormatter(ITextWriter writer) : base(writer) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + var dto = (InternetExplorerFavoritesDTO)result; + + WriteLine($"Favorites ({dto.UserName}):\n"); + + foreach (var favorite in dto.Favorites) + { + WriteLine($" {favorite}"); + } + WriteLine(); + } + } + } +} +#nullable enable \ No newline at end of file diff --git a/Seatbelt/Commands/Browser/InternetExplorerTabCommand.cs b/Seatbelt/Commands/Browser/InternetExplorerTabCommand.cs new file mode 100644 index 0000000..8a0874f --- /dev/null +++ b/Seatbelt/Commands/Browser/InternetExplorerTabCommand.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Text.RegularExpressions; + + +namespace Seatbelt.Commands.Browser +{ + internal class InternetExplorerTabCommand : CommandBase + { + public override string Command => "IETabs"; + public override string Description => "Open Internet Explorer tabs"; + public override CommandGroup[] Group => new[] { CommandGroup.User }; + public override bool SupportRemote => false; + + public InternetExplorerTabCommand(Runtime runtime) : base(runtime) + { + } + + public override IEnumerable Execute(string[] args) + { + // Lists currently open Internet Explorer tabs, via COM + // Notes: + // https://searchcode.com/codesearch/view/9859954/ + // https://gist.github.com/yizhang82/a1268d3ea7295a8a1496e01d60ada816 + + // Shell.Application COM GUID + var shell = Type.GetTypeFromProgID("Shell.Application"); + + // actually instantiate the Shell.Application COM object + var shellObj = Activator.CreateInstance(shell); + + // grab all the current windows + var windows = shellObj.GetType().InvokeMember("Windows", BindingFlags.InvokeMethod, null, shellObj, null); + + // grab the open tab count + var openTabs = windows.GetType().InvokeMember("Count", BindingFlags.GetProperty, null, windows, null); + var openTabsCount = int.Parse(openTabs.ToString()); + + for (var i = 0; i < openTabsCount; i++) + { + // grab the acutal tab + object? item = windows.GetType().InvokeMember("Item", BindingFlags.InvokeMethod, null, windows, new object[] { i }); + string locationName = "", locationUrl = ""; + + try + { + // extract the tab properties + locationName = (string)item.GetType().InvokeMember("LocationName", BindingFlags.GetProperty, null, item, null); + locationUrl = (string)item.GetType().InvokeMember("LocationUrl", BindingFlags.GetProperty, null, item, null); + + Marshal.ReleaseComObject(item); + item = null; + } + catch { } + + // ensure we have a site address + if (Regex.IsMatch(locationUrl, @"(^https?://.+)|(^ftp://)")) + { + yield return new InternetExplorerTabsDTO( + locationName, + locationUrl + ); + } + } + + Marshal.ReleaseComObject(windows); + Marshal.ReleaseComObject(shellObj); + } + + internal class InternetExplorerTabsDTO : CommandDTOBase + { + public InternetExplorerTabsDTO(string locationName, string locationUrl) + { + LocationName = locationName; + LocationUrl = locationUrl; + } + public string LocationName { get; } + public string LocationUrl { get; } + } + } +} diff --git a/Seatbelt/Commands/Browser/InternetExplorerTypedURLsCommand.cs b/Seatbelt/Commands/Browser/InternetExplorerTypedURLsCommand.cs new file mode 100644 index 0000000..a9298e7 --- /dev/null +++ b/Seatbelt/Commands/Browser/InternetExplorerTypedURLsCommand.cs @@ -0,0 +1,127 @@ +using Microsoft.Win32; +using Seatbelt.Util; +using System; +using System.Collections.Generic; +using Seatbelt.Output.Formatters; +using Seatbelt.Output.TextWriters; + + +namespace Seatbelt.Commands.Browser +{ + class TypedUrl + { + public TypedUrl(DateTime time, string url) + { + Time = time; + Url = url; + } + public DateTime Time { get; } + public string Url { get; } + } + + internal class InternetExplorerTypedUrlsCommand : CommandBase + { + public override string Command => "IEUrls"; + public override string Description => "Internet Explorer typed URLs (last 7 days, argument == last X days)"; + public override CommandGroup[] Group => new[] { CommandGroup.User }; + public override bool SupportRemote => false; // TODO remote , though not sure how useful this would be + + public InternetExplorerTypedUrlsCommand(Runtime runtime) : base(runtime) + { + } + + public override IEnumerable Execute(string[] args) + { + // lists Internet explorer history (last 7 days by default) + var lastDays = 7; + + if (!Runtime.FilterResults) + { + lastDays = 90; + } + + if (args.Length >= 1) + { + if (!int.TryParse(args[0], out lastDays)) + { + throw new ArgumentException("Argument is not an integer"); + } + } + + var startTime = DateTime.Now.AddDays(-lastDays); + + WriteHost($"Internet Explorer typed URLs for the last {lastDays} days\n"); + + var SIDs = Registry.Users.GetSubKeyNames(); + foreach (var sid in SIDs) + { + if (!sid.StartsWith("S-1-5") || sid.EndsWith("_Classes")) + { + continue; + } + + var settings = RegistryUtil.GetValues(RegistryHive.Users, $"{sid}\\SOFTWARE\\Microsoft\\Internet Explorer\\TypedURLs"); + if ((settings == null) || (settings.Count <= 1)) + { + continue; + } + + var URLs = new List(); + + foreach (var kvp in settings) + { + var timeBytes = RegistryUtil.GetBinaryValue(RegistryHive.Users, $"{sid}\\SOFTWARE\\Microsoft\\Internet Explorer\\TypedURLsTime", kvp.Key.Trim()); + if (timeBytes == null) + continue; + + var timeLong = BitConverter.ToInt64(timeBytes, 0); + var urlTime = DateTime.FromFileTime(timeLong); + if (urlTime > startTime) + { + URLs.Add(new TypedUrl( + urlTime, + kvp.Value.ToString().Trim() + )); + } + } + + yield return new InternetExplorerTypedURLsDTO( + sid, + URLs + ); + } + } + + internal class InternetExplorerTypedURLsDTO : CommandDTOBase + { + public InternetExplorerTypedURLsDTO(string sid, List urls) + { + Sid = sid; + Urls = urls; + } + public string Sid { get; } + public List Urls { get; } + } + + [CommandOutputType(typeof(InternetExplorerTypedURLsDTO))] + internal class InternetExplorerTypedURLsFormatter : TextFormatterBase + { + public InternetExplorerTypedURLsFormatter(ITextWriter writer) : base(writer) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + var dto = (InternetExplorerTypedURLsDTO)result; + + WriteLine("\n {0}", dto.Sid); + + foreach (var url in dto.Urls) + { + WriteLine($" {url.Time,-23} : {url.Url}"); + } + WriteLine(); + } + } + } +} diff --git a/Seatbelt/Commands/CommandBase.cs b/Seatbelt/Commands/CommandBase.cs new file mode 100644 index 0000000..7235784 --- /dev/null +++ b/Seatbelt/Commands/CommandBase.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; + +namespace Seatbelt.Commands +{ + internal abstract class CommandBase + { + public abstract string Command { get; } + public abstract string Description { get; } + public abstract CommandGroup[] Group { get; } + public abstract bool SupportRemote { get; } + + public Runtime Runtime { get; set; } + + protected CommandBase(Runtime runtime) + { + Runtime = runtime; + } + + public abstract IEnumerable Execute(string[] args); + + + + public void WriteOutput(CommandDTOBase dto) => throw new NotImplementedException(); + + public void WriteVerbose(string message) => Runtime.OutputSink.WriteVerbose(message); + + public void WriteWarning(string message) => Runtime.OutputSink.WriteWarning(message); + + public void WriteError(string message) => Runtime.OutputSink.WriteError(message); + + public void WriteHost(string format = "", params object[] args) => Runtime.OutputSink.WriteHost(string.Format(format, args)); + } +} diff --git a/Seatbelt/Commands/CommandDTOBase.cs b/Seatbelt/Commands/CommandDTOBase.cs new file mode 100644 index 0000000..0ec3f9d --- /dev/null +++ b/Seatbelt/Commands/CommandDTOBase.cs @@ -0,0 +1,6 @@ +namespace Seatbelt.Commands +{ + public interface CommandDTOBase + { + } +} \ No newline at end of file diff --git a/Seatbelt/Commands/CommandGroup.cs b/Seatbelt/Commands/CommandGroup.cs new file mode 100644 index 0000000..4d0df5c --- /dev/null +++ b/Seatbelt/Commands/CommandGroup.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Seatbelt.Commands +{ + [Flags] + public enum CommandGroup + { + All, + User, + System, + Slack, + Chrome, + Remote, + Misc, + } +} diff --git a/Seatbelt/Commands/CommandOutputTypeAttribute.cs b/Seatbelt/Commands/CommandOutputTypeAttribute.cs new file mode 100644 index 0000000..bedbf93 --- /dev/null +++ b/Seatbelt/Commands/CommandOutputTypeAttribute.cs @@ -0,0 +1,20 @@ +using System; + +namespace Seatbelt.Commands +{ + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] + class CommandOutputTypeAttribute : Attribute + { + public Type Type { get; set; } + + public CommandOutputTypeAttribute(Type outputDTO) + { + if (!typeof(CommandDTOBase).IsAssignableFrom(outputDTO)) + { + throw new Exception($"CommandOutputTypeAttribute: the specified output DTO({outputDTO}) does not inherit from CommandDTOBase"); + } + + Type = outputDTO; + } + } +} diff --git a/Seatbelt/Commands/ErrorDTO.cs b/Seatbelt/Commands/ErrorDTO.cs new file mode 100644 index 0000000..e8d0b51 --- /dev/null +++ b/Seatbelt/Commands/ErrorDTO.cs @@ -0,0 +1,29 @@ +using Seatbelt.Output.Formatters; +using Seatbelt.Output.TextWriters; + +namespace Seatbelt.Commands +{ + internal class ErrorDTO : CommandDTOBase + { + public ErrorDTO(string message) + { + Message = message; + } + + public string Message { get; } + } + + [CommandOutputType(typeof(ErrorDTO))] + internal class ErrorTextFormatter : TextFormatterBase + { + public ErrorTextFormatter(ITextWriter writer) : base(writer) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + var dto = (ErrorDTO)result; + WriteLine("ERROR: " + dto.Message); + } + } +} diff --git a/Seatbelt/Commands/HostDTO.cs b/Seatbelt/Commands/HostDTO.cs new file mode 100644 index 0000000..f6a1427 --- /dev/null +++ b/Seatbelt/Commands/HostDTO.cs @@ -0,0 +1,29 @@ +using Seatbelt.Output.Formatters; +using Seatbelt.Output.TextWriters; + +namespace Seatbelt.Commands +{ + internal class HostDTO : CommandDTOBase + { + public HostDTO(string message) + { + Message = message; + } + + public string Message { get; } + } + + [CommandOutputType(typeof(HostDTO))] + internal class HostTextFormatter : TextFormatterBase + { + public HostTextFormatter(ITextWriter writer) : base(writer) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + var dto = (HostDTO)result; + WriteLine(dto.Message); + } + } +} diff --git a/Seatbelt/Commands/Misc/CloudCredentialsCommand.cs b/Seatbelt/Commands/Misc/CloudCredentialsCommand.cs new file mode 100644 index 0000000..0b7b2e8 --- /dev/null +++ b/Seatbelt/Commands/Misc/CloudCredentialsCommand.cs @@ -0,0 +1,153 @@ +#nullable disable +using System; +using System.Collections.Generic; +using System.IO; + +namespace Seatbelt.Commands +{ + internal class CloudCredentialsCommand : CommandBase + { + public override string Command => "CloudCredentials"; + public override string Description => "AWS/Google/Azure cloud credential files"; + public override CommandGroup[] Group => new[] {CommandGroup.User}; + public override bool SupportRemote => false; // though I *really* want to figure an effective way to do this one remotely + + public CloudCredentialsCommand(Runtime runtime) : base(runtime) + { + } + + public override IEnumerable Execute(string[] args) + { + // checks for various cloud credential files (AWS, Microsoft Azure, and Google Compute) + // adapted from https://twitter.com/cmaddalena's SharpCloud project (https://github.com/chrismaddalena/SharpCloud/) + + var userFolder = $"{Environment.GetEnvironmentVariable("SystemDrive")}\\Users\\"; + var dirs = Directory.GetDirectories(userFolder); + + foreach (var dir in dirs) + { + if (dir.EndsWith("Public") || dir.EndsWith("Default") || dir.EndsWith("Default User") || + dir.EndsWith("All Users")) + { + continue; + } + + var awsKeyFile = $"{dir}\\.aws\\credentials"; + if (File.Exists(awsKeyFile)) + { + var lastAccessed = File.GetLastAccessTime(awsKeyFile); + var lastModified = File.GetLastWriteTime(awsKeyFile); + var size = new FileInfo(awsKeyFile).Length; + + yield return new CloudCredentialsDTO() + { + Type = "AWS", + FileName = awsKeyFile, + LastAccessed = lastAccessed, + LastModified = lastModified, + Size = size + }; + } + + var computeCredsDb = $"{dir}\\AppData\\Roaming\\gcloud\\credentials.db"; + if (File.Exists(computeCredsDb)) + { + var lastAccessed = File.GetLastAccessTime(computeCredsDb); + var lastModified = File.GetLastWriteTime(computeCredsDb); + var size = new FileInfo(computeCredsDb).Length; + + yield return new CloudCredentialsDTO() + { + Type = "Google", + FileName = computeCredsDb, + LastAccessed = lastAccessed, + LastModified = lastModified, + Size = size + }; + } + + var computeLegacyCreds = $"{dir}\\AppData\\Roaming\\gcloud\\legacy_credentials"; + if (File.Exists(computeLegacyCreds)) + { + var lastAccessed = File.GetLastAccessTime(computeLegacyCreds); + var lastModified = File.GetLastWriteTime(computeLegacyCreds); + var size = new FileInfo(computeLegacyCreds).Length; + + yield return new CloudCredentialsDTO() + { + Type = "Google", + FileName = computeLegacyCreds, + LastAccessed = lastAccessed, + LastModified = lastModified, + Size = size + }; + } + + var computeAccessTokensDb = $"{dir}\\AppData\\Roaming\\gcloud\\access_tokens.db"; + if (File.Exists(computeAccessTokensDb)) + { + var lastAccessed = File.GetLastAccessTime(computeAccessTokensDb); + var lastModified = File.GetLastWriteTime(computeAccessTokensDb); + var size = new FileInfo(computeAccessTokensDb).Length; + + yield return new CloudCredentialsDTO() + { + Type = "Google", + FileName = computeAccessTokensDb, + LastAccessed = lastAccessed, + LastModified = lastModified, + Size = size + }; + } + + var azureTokens = $"{dir}\\.azure\\accessTokens.json"; + if (File.Exists(azureTokens)) + { + var lastAccessed = File.GetLastAccessTime(azureTokens); + var lastModified = File.GetLastWriteTime(azureTokens); + var size = new FileInfo(azureTokens).Length; + + yield return new CloudCredentialsDTO() + { + Type = "Azure", + FileName = azureTokens, + LastAccessed = lastAccessed, + LastModified = lastModified, + Size = size + }; + } + + var azureProfile = $"{dir}\\.azure\\azureProfile.json"; + if (File.Exists(azureProfile)) + { + var lastAccessed = File.GetLastAccessTime(azureProfile); + var lastModified = File.GetLastWriteTime(azureProfile); + var size = new FileInfo(azureProfile).Length; + + yield return new CloudCredentialsDTO() + { + Type = "Azure", + FileName = azureProfile, + LastAccessed = lastAccessed, + LastModified = lastModified, + Size = size + }; + } + } + } + + internal class CloudCredentialsDTO : CommandDTOBase + { + public string Type { get; set; } + + public string FileName { get; set; } + + public DateTime LastAccessed { get; set; } + + public DateTime LastModified { get; set; } + + public long Size { get; set; } + } + } +} +#nullable enable \ No newline at end of file diff --git a/Seatbelt/Commands/Misc/DirectoryListCommand.cs b/Seatbelt/Commands/Misc/DirectoryListCommand.cs new file mode 100644 index 0000000..aee91b7 --- /dev/null +++ b/Seatbelt/Commands/Misc/DirectoryListCommand.cs @@ -0,0 +1,207 @@ +#nullable disable +using System; +using System.Collections.Generic; +using System.IO; +using System.Text.RegularExpressions; +using Seatbelt.Output.Formatters; +using Seatbelt.Output.TextWriters; + +namespace Seatbelt.Commands +{ + internal class DirectoryListCommand : CommandBase + { + public override string Command => "dir"; + + public override string Description => + "Lists files/folders. By default, lists users' downloads, documents, and desktop folders (arguments == [directory] [depth] [regex] [boolIgnoreErrors]"; + + public override CommandGroup[] Group => new[] { CommandGroup.User }; + public override bool SupportRemote => false; + + public DirectoryListCommand(Runtime runtime) : base(runtime) + { + } + + public override IEnumerable Execute(string[] args) + { + string directory = null; + Regex regex = null; + int depth; + var ignoreErrors = false; + + WriteHost(" {0,-10} {1,-10} {2,-9} {3}\n", "LastAccess", "LastWrite", "Size", "Path"); + + if (args.Length == 0) + { + directory = $"{Environment.GetEnvironmentVariable("SystemDrive")}\\Users\\"; + regex = new Regex(@"^(?!.*desktop\.ini).*\\(Documents|Downloads|Desktop)\\", RegexOptions.IgnoreCase); + depth = 2; + ignoreErrors = true; + } + else if (args.Length == 1) + { + directory = args[0]; + depth = 0; + } + else if (args.Length == 2) + { + directory = args[0]; + depth = int.Parse(args[1]); + } + else if (args.Length == 3) + { + directory = args[0]; + depth = int.Parse(args[1]); + regex = new Regex(args[2], RegexOptions.IgnoreCase); + } + else + { + directory = args[0]; + depth = int.Parse(args[1]); + regex = new Regex(args[2], RegexOptions.IgnoreCase); + ignoreErrors = true; + } + + foreach (var file in ListDirectory(directory, regex, depth, ignoreErrors)) + { + yield return file; + } + } + + private IEnumerable ListDirectory(string path, Regex regex, int depth, + bool ignoreErrors) + { + if (depth < 0) + { + yield break; + } + + var dirList = new List(); + string[] directories = null; + try + { + directories = Directory.GetDirectories(path); + } + catch (Exception e) + { + if (!ignoreErrors) + { + WriteError(e.ToString()); + } + + yield break; + } + + foreach (var dir in directories) + { + dirList.Add(dir); + if (regex != null && !regex.IsMatch(dir)) + { + continue; + } + + if (!dir.EndsWith("\\")) + { + yield return WriteOutput(dir + "\\", 0); + } + else + { + yield return WriteOutput(dir, 0); + } + } + + + + string[] files = null; + try + { + files = Directory.GetFiles(path); + } + catch (Exception e) + { + if (!ignoreErrors) + { + throw e; + } + + yield break; + } + + foreach (var file in files) + { + if (regex != null && !regex.IsMatch(file)) + { + continue; + } + + long size = 0; + try + { + var info = new FileInfo(file); + size = info.Length; + } + catch + { + } + + yield return WriteOutput(file, size); + } + + foreach (var dir in dirList) + { + foreach (var file in ListDirectory(dir, regex, (depth - 1), ignoreErrors)) + { + yield return file; + } + } + } + + private DirectoryListDTO WriteOutput(string path, long size) + { + var lastAccess = Directory.GetLastAccessTime(path); + var lastWrite = Directory.GetLastWriteTime(path); + + return new DirectoryListDTO() + { + LastAccess = lastAccess, + LastWrite = lastWrite, + Size = size, + Path = path + }; + } + } + + internal class DirectoryListDTO : CommandDTOBase + { + public DateTime LastAccess { get; set; } + public DateTime LastWrite { get; set; } + public long Size { get; set; } + public string Path { get; set; } + } + + [CommandOutputType(typeof(DirectoryListDTO))] + internal class DirectoryListTextFormatter : TextFormatterBase + { + public DirectoryListTextFormatter(ITextWriter writer) : base(writer) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + var dto = (DirectoryListDTO)result; + WriteLine(" {0,-10} {1,-10} {2,-9} {3}", dto.LastWrite.ToString("yy-MM-dd"), dto.LastAccess.ToString("yy-MM-dd"), BytesToString(dto.Size), dto.Path); + } + + private string BytesToString(long byteCount) + { + string[] suf = { "B", "KB", "MB", "GB", "TB", "PB", "EB" }; //Longs run out around EB + if (byteCount == 0) + return "0" + suf[0]; + var bytes = Math.Abs(byteCount); + var place = Convert.ToInt32(Math.Floor(Math.Log(bytes, 1024))); + var num = Math.Round(bytes / Math.Pow(1024, place), 1); + return (Math.Sign(byteCount) * num) + suf[place]; + } + } +} +#nullable enable \ No newline at end of file diff --git a/Seatbelt/Commands/Misc/FileInfoCommand.cs b/Seatbelt/Commands/Misc/FileInfoCommand.cs new file mode 100644 index 0000000..113bca8 --- /dev/null +++ b/Seatbelt/Commands/Misc/FileInfoCommand.cs @@ -0,0 +1,171 @@ +using Seatbelt.Util; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Security.AccessControl; + +namespace Seatbelt.Commands.Windows +{ + // TODO: Include filetype custom properties (e.g. Author and last-saved by fields for Word docs) + internal class FileInfoCommand : CommandBase + { + public override string Command => "FileInfo"; + public override string Description => "Information about a file (version information, timestamps, basic PE info, etc. argument(s) == file path(s)"; + public override CommandGroup[] Group => new[] { CommandGroup.Misc }; + public override bool SupportRemote => false; + public FileInfoCommand(Runtime runtime) : base(runtime) + { + } + + public override IEnumerable Execute(string[] args) + { + if (args.Length == 0) + { + var WinDir = Environment.GetEnvironmentVariable("WINDIR"); + if (!Runtime.FilterResults) + { + // Oct. 2018 - File list partially taken from https://github.com/rasta-mouse/Watson/tree/04f029aa35c360a8a49c7b162d51604253996eb6/Watson/SuspectFiles + args = new string[] + { + $"{WinDir}\\system32\\drivers\\afd.sys", + $"{WinDir}\\system32\\coremessaging.dll", + $"{WinDir}\\system32\\dssvc.dll", + $"{WinDir}\\system32\\gdiplus.dll", + $"{WinDir}\\system32\\gpprefcl.dll", + $"{WinDir}\\system32\\drivers\\mrxdav.sys", + $"{WinDir}\\system32\\ntoskrnl.exe", + $"{WinDir}\\system32\\pcadm.dll", + $"{WinDir}\\system32\\rpcrt4.dll", + $"{WinDir}\\system32\\schedsvc.dll", + $"{WinDir}\\system32\\seclogon.dll", + $"{WinDir}\\system32\\win32k.sys", + $"{WinDir}\\system32\\win32kfull.sys", + $"{WinDir}\\system32\\winload.exe", + $"{WinDir}\\system32\\winsrv.dll", + }; + } + else + { + args = new string[] + { + $"{WinDir}\\system32\\ntoskrnl.exe" + }; + } + } + + foreach (var file in args) + { + FileVersionInfo versionInfo; + FileInfo fileInfo; + FileSecurity security; + + try + { + versionInfo = FileVersionInfo.GetVersionInfo(file); + fileInfo = new FileInfo(file); + security = File.GetAccessControl(file); + } + catch + { + // TODO: Properly process error + WriteError($" [!] Error accessing {file}\n"); + continue; + } + + if (versionInfo != null && fileInfo != null && security != null) // TODO: Account for cases when any of these aren't null + { + var isDotNet = FileUtil.IsDotNetAssembly(file); + + yield return new FileInfoDTO( + versionInfo.Comments, + versionInfo.CompanyName, + versionInfo.FileDescription, + versionInfo.FileName, + versionInfo.FileVersion, + versionInfo.InternalName, + versionInfo.IsDebug, + isDotNet, + versionInfo.IsPatched, + versionInfo.IsPreRelease, + versionInfo.IsPrivateBuild, + versionInfo.IsSpecialBuild, + versionInfo.Language, + versionInfo.LegalCopyright, + versionInfo.LegalTrademarks, + versionInfo.OriginalFilename, + versionInfo.PrivateBuild, + versionInfo.ProductName, + versionInfo.ProductVersion, + versionInfo.SpecialBuild, + fileInfo.Attributes, + fileInfo.CreationTimeUtc, + fileInfo.LastAccessTimeUtc, + fileInfo.LastWriteTimeUtc, + fileInfo.Length, + security.GetSecurityDescriptorSddlForm(AccessControlSections.Access | AccessControlSections.Owner) + ); + } + } + } + } + + internal class FileInfoDTO : CommandDTOBase + { + public FileInfoDTO(string comments, string companyName, string fileDescription, string fileName, string fileVersion, string internalName, bool isDebug, bool isDotNet, bool isPatched, bool isPreRelease, bool isPrivateBuild, bool isSpecialBuild, string language, string legalCopyright, string legalTrademarks, string originalFilename, string privateBuild, string productName, string productVersion, string specialBuild, FileAttributes attributes, DateTime creationTimeUtc, DateTime lastAccessTimeUtc, DateTime lastWriteTimeUtc, long length, string sddl) + { + Comments = comments; + CompanyName = companyName; + FileDescription = fileDescription; + FileName = fileName; + FileVersion = fileVersion; + InternalName = internalName; + IsDebug = isDebug; + IsDotNet = isDotNet; + IsPatched = isPatched; + IsPreRelease = isPreRelease; + IsPrivateBuild = isPrivateBuild; + IsSpecialBuild = isSpecialBuild; + Language = language; + LegalCopyright = legalCopyright; + LegalTrademarks = legalTrademarks; + OriginalFilename = originalFilename; + PrivateBuild = privateBuild; + ProductName = productName; + ProductVersion = productVersion; + SpecialBuild = specialBuild; + Attributes = attributes; + CreationTimeUtc = creationTimeUtc; + LastAccessTimeUtc = lastAccessTimeUtc; + LastWriteTimeUtc = lastWriteTimeUtc; + Length = length; + SDDL = sddl; + } + public string Comments { get; } + public string CompanyName { get; } + public string FileDescription { get; } + public string FileName { get; } + public string FileVersion { get; } + public string InternalName { get; } + public bool IsDebug { get; } + public bool IsDotNet { get; } + public bool IsPatched { get; } + public bool IsPreRelease { get; } + public bool IsPrivateBuild { get; } + public bool IsSpecialBuild { get; } + public string Language { get; } + public string LegalCopyright { get; } + public string LegalTrademarks { get; } + public string OriginalFilename { get; } + public string PrivateBuild { get; } + public string ProductName { get; } + public string ProductVersion { get; } + public string SpecialBuild { get; } + public FileAttributes Attributes { get; } + public DateTime CreationTimeUtc { get; } + public DateTime LastAccessTimeUtc { get; } + public DateTime LastWriteTimeUtc { get; } + public long Length { get; } + public string SDDL { get; } + } +} \ No newline at end of file diff --git a/Seatbelt/Commands/Misc/InterestingFilesCommand.cs b/Seatbelt/Commands/Misc/InterestingFilesCommand.cs new file mode 100644 index 0000000..7315f03 --- /dev/null +++ b/Seatbelt/Commands/Misc/InterestingFilesCommand.cs @@ -0,0 +1,198 @@ +#nullable disable +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using Seatbelt.Output.Formatters; +using Seatbelt.Output.TextWriters; + + +namespace Seatbelt.Commands +{ + internal class InterestingFilesCommand : CommandBase + { + public override string Command => "InterestingFiles"; + public override string Description => "\"Interesting\" files matching various patterns in the user's folder. Note: takes non-trivial time."; + public override CommandGroup[] Group => new[] { CommandGroup.Misc }; + public override bool SupportRemote => false; + + public InterestingFilesCommand(Runtime runtime) : base(runtime) + { + } + + public override IEnumerable Execute(string[] args) + { + // TODO: accept patterns on the command line + + // returns files (w/ modification dates) that match the given pattern below + var patterns = new string[]{ + // Wildcards + "*pass*", + "*diagram*", + "*_rsa*", // SSH keys + + // Extensions + "*.doc", + "*.docx", + "*.pem", + "*.pdf", + "*.pfx", // Certificate (Code signing, SSL, etc.) + "*.p12", // Certificate (Code signing, SSL, etc.) - Mac/Firefox + "*.ppt", + "*.pptx", + "*.vsd", // Visio Diagrams + "*.xls", + "*.xlsx", + "*.kdb", // KeePass database + "*.kdbx", // KeePass database + "*.key", + + // Specific file names + "KeePass.config" + }; + + var searchPath = $"{Environment.GetEnvironmentVariable("SystemDrive")}\\Users\\"; + var files = FindFiles(searchPath, string.Join(";", patterns)); + + WriteHost("\nAccessed Modified Path"); + WriteHost("---------- ---------- -----"); + + foreach (var file in files) + { + var info = new FileInfo(file); + + var owner = ""; + var SDDL = ""; + try + { + SDDL = info.GetAccessControl(System.Security.AccessControl.AccessControlSections.All).GetSecurityDescriptorSddlForm(System.Security.AccessControl.AccessControlSections.All); + owner = File.GetAccessControl(file).GetOwner(typeof(System.Security.Principal.NTAccount)).ToString(); + } + catch { } + + yield return new InterestingFileDTO() + { + Path = $"{file}", + FileOwner = owner, + Size = info.Length, + DateCreated = info.CreationTime, + DateAccessed = info.LastAccessTime, + DateModified = info.LastWriteTime, + Sddl = SDDL + }; + } + } + + public static List FindFiles(string path, string patterns) + { + // finds files matching one or more patterns under a given path, recursive + // adapted from http://csharphelper.com/blog/2015/06/find-files-that-match-multiple-patterns-in-c/ + // pattern: "*pass*;*.png;" + + var files = new List(); + try + { + var filesUnfiltered = GetFiles(path).ToList(); + + // search every pattern in this directory's files + foreach (var pattern in patterns.Split(';')) + { + files.AddRange(filesUnfiltered.Where(f => f.Contains(pattern.Trim('*')))); + } + + //// go recurse in all sub-directories + //foreach (var directory in Directory.GetDirectories(path)) + // files.AddRange(FindFiles(directory, patterns)); + } + catch (UnauthorizedAccessException) { } + catch (PathTooLongException) { } + + return files; + } + + // FROM: https://stackoverflow.com/a/929418 + private static IEnumerable GetFiles(string path) + { + var queue = new Queue(); + queue.Enqueue(path); + while (queue.Count > 0) + { + path = queue.Dequeue(); + try + { + foreach (var subDir in Directory.GetDirectories(path)) + { + queue.Enqueue(subDir); + } + } + catch (Exception) + { + // Eat it + } + string[] files = null; + try + { + files = Directory.GetFiles(path); + } + catch (Exception) + { + // Eat it + } + + if (files == null) continue; + foreach (var f in files) + { + yield return f; + } + } + } + + public static IEnumerable Split(string text, int partLength, StringBuilder sb) + { + if (text == null) { sb.AppendLine("[ERROR] Split() - singleLineString"); } + if (partLength < 1) { sb.AppendLine("[ERROR] Split() - 'columns' must be greater than 0."); } + + var partCount = Math.Ceiling((double)text.Length / partLength); + if (partCount < 2) + { + yield return text; + } + + for (var i = 0; i < partCount; i++) + { + var index = i * partLength; + var lengthLeft = Math.Min(partLength, text.Length - index); + var line = text.Substring(index, lengthLeft); + yield return line; + } + } + + internal class InterestingFileDTO : CommandDTOBase + { + public string Path { get; set; } + public string FileOwner { get; set; } + public long Size { get; set; } + public DateTime DateCreated { get; set; } + public DateTime DateAccessed { get; set; } + public DateTime DateModified { get; set; } + public string Sddl { get; set; } + } + + [CommandOutputType(typeof(InterestingFileDTO))] + internal class InterestingFileFormatter : TextFormatterBase + { + public InterestingFileFormatter(ITextWriter writer) : base(writer) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + var dto = (InterestingFileDTO)result; + + WriteLine($"{dto.DateAccessed:yyyy-MM-dd} {dto.DateModified:yyyy-MM-dd} {dto.Path}"); + } + } + } +} +#nullable enable \ No newline at end of file diff --git a/Seatbelt/Commands/Misc/SearchIndexCommand.cs b/Seatbelt/Commands/Misc/SearchIndexCommand.cs new file mode 100644 index 0000000..e616ff1 --- /dev/null +++ b/Seatbelt/Commands/Misc/SearchIndexCommand.cs @@ -0,0 +1,147 @@ +#nullable disable +using System; +using System.Collections.Generic; +using System.Data.OleDb; +using Seatbelt.Output.Formatters; +using Seatbelt.Output.TextWriters; + +namespace Seatbelt.Commands +{ + internal class SearchIndexCommand : CommandBase + { + public override string Command => "SearchIndex"; + + public override string Description => "Query results from the Windows Search Index, default term of 'passsword'. (argument(s) == "; + + public override CommandGroup[] Group => new[] { CommandGroup.Misc }; + public override bool SupportRemote => false; // maybe? + + public SearchIndexCommand(Runtime runtime) : base(runtime) + { + } + + private IEnumerable SearchWindowsIndex(string searchPath = @"C:\Users\", string criteria = "password") + { + var Query = @"SELECT System.ItemPathDisplay,System.FileOwner,System.Size,System.DateCreated,System.DateAccessed,System.Search.Autosummary FROM SystemIndex WHERE Contains(*, '""*{0}*""') AND SCOPE = '{1}' AND (System.FileExtension = '.txt' OR System.FileExtension = '.doc' OR System.FileExtension = '.docx' OR System.FileExtension = '.ppt' OR System.FileExtension = '.pptx' OR System.FileExtension = '.xls' OR System.FileExtension = '.xlsx' OR System.FileExtension = '.ps1' OR System.FileExtension = '.vbs' OR System.FileExtension = '.config' OR System.FileExtension = '.ini')"; + + var connectionString = "Provider=Search.CollatorDSO;Extended Properties=\"Application=Windows\""; + using (var connection = new OleDbConnection(connectionString)) + { + var query = string.Format(Query, criteria, searchPath); +#pragma warning disable CA2100 + var command = new OleDbCommand(query, connection); +#pragma warning restore CA2100 + connection.Open(); + + var result = new List(); + + OleDbDataReader reader = null; + try + { + reader = command.ExecuteReader(); + } + catch + { + WriteError("Unable to query the Search Indexer, Search Index is likely not running."); + yield break; + } + + while (reader.Read()) + { + var AutoSummary = ""; + var FileOwner = ""; + try { AutoSummary = reader.GetString(5); } catch { } + try { FileOwner = reader.GetString(1); } catch { } + + yield return new WindowsSearchIndexDTO() + { + Path = reader.GetString(0), + FileOwner = FileOwner, + Size = Decimal.ToUInt64((Decimal)reader.GetValue(2)), + DateCreated = reader.GetDateTime(3), + DateAccessed = reader.GetDateTime(4), + AutoSummary = AutoSummary + }; + } + + connection.Close(); + } + } + + + public override IEnumerable Execute(string[] args) + { + if (args.Length == 0) + { + foreach (var result in SearchWindowsIndex()) + { + yield return result; + } + } + else if (args.Length == 1) + { + if (System.IO.Directory.Exists(args[0])) + { + foreach (var result in SearchWindowsIndex(args[0])) + { + yield return result; + } + } + else + { + var terms = args[0].Split(','); + foreach (var term in terms) + { + foreach (var result in SearchWindowsIndex(@"C:\Users\", term)) + { + yield return result; + } + } + } + } + else if (args.Length == 2) + { + var terms = args[1].Split(','); + foreach (var term in terms) + { + foreach (var result in SearchWindowsIndex(args[0], term)) + { + yield return result; + } + } + } + } + } + + internal class WindowsSearchIndexDTO : CommandDTOBase + { + public string Path { get; set; } + public string FileOwner { get; set; } + public UInt64 Size { get; set; } + public DateTime DateCreated { get; set; } + public DateTime DateAccessed { get; set; } + public string AutoSummary { get; set; } + } + + [CommandOutputType(typeof(WindowsSearchIndexDTO))] + internal class WindowsSearchIndexTextFormatter : TextFormatterBase + { + public WindowsSearchIndexTextFormatter(ITextWriter writer) : base(writer) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + var dto = (WindowsSearchIndexDTO)result; + + WriteLine("ItemUrl : {0}", dto.Path); + WriteLine("FileOwner : {0}", dto.FileOwner); + WriteLine("Size : {0}", dto.Size); + WriteLine("DateCreated : {0}", dto.DateCreated); + WriteLine("DateAccessed : {0}", dto.DateAccessed); + WriteLine("AutoSummary :"); + WriteLine("{0}\n\n", dto.AutoSummary); + } + } +} +#nullable enable \ No newline at end of file diff --git a/Seatbelt/Commands/Products/InstalledProductsCommand.cs b/Seatbelt/Commands/Products/InstalledProductsCommand.cs new file mode 100644 index 0000000..0fc2737 --- /dev/null +++ b/Seatbelt/Commands/Products/InstalledProductsCommand.cs @@ -0,0 +1,91 @@ +using Microsoft.Win32; +using System; +using System.Collections.Generic; + + +namespace Seatbelt.Commands.Windows +{ + internal class InstalledProductsCommand : CommandBase + { + public override string Command => "InstalledProducts"; + public override string Description => "Installed products via the registry"; + public override CommandGroup[] Group => new[] { CommandGroup.Misc }; + public override bool SupportRemote => false; // TODO remote , though the method of this one will be a bit more difficult + + public InstalledProductsCommand(Runtime runtime) : base(runtime) + { + } + + public override IEnumerable Execute(string[] args) + { + string[] productKeys = { @"SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\", @"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall" }; + + foreach (var productKey in productKeys) + { + var architecture = "x86"; + if (productKey.Equals(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall")) + { + architecture = "x64"; + } + + using (var key = Registry.LocalMachine.OpenSubKey(productKey)) + { + foreach (var subkeyName in key.GetSubKeyNames()) + { + var DisplayName = $"{key.OpenSubKey(subkeyName).GetValue("DisplayName")}"; + var DisplayVersion = $"{key.OpenSubKey(subkeyName).GetValue("DisplayVersion")}"; + var Publisher = $"{key.OpenSubKey(subkeyName).GetValue("Publisher")}"; + var InstallDateStr = $"{key.OpenSubKey(subkeyName).GetValue("InstallDate")}"; + var InstallDate = new DateTime(); + + if (InstallDateStr != null && !String.IsNullOrEmpty(InstallDateStr)) + { + try + { + var year = InstallDateStr.Substring(0, 4); + var month = InstallDateStr.Substring(4, 2); + var day = InstallDateStr.Substring(6, 2); + var date = $"{year}-{month}-{day}"; + InstallDate = DateTime.Parse(date); + } + catch { } + } + + if (DisplayName != null && !String.IsNullOrEmpty(DisplayName)) + { + yield return new InstalledProductsDTO( + DisplayName, + DisplayVersion, + Publisher, + InstallDate, + architecture + ); + } + } + WriteHost("\n"); + } + } + } + + internal class InstalledProductsDTO : CommandDTOBase + { + public InstalledProductsDTO(string displayName, string displayVersion, string publisher, DateTime installDate, string architecture) + { + DisplayName = displayName; + DisplayVersion = displayVersion; + Publisher = publisher; + InstallDate = installDate; + Architecture = architecture; + } + public string DisplayName { get; } + + public string DisplayVersion { get; } + + public string Publisher { get; } + + public DateTime InstallDate { get; } + + public string Architecture { get; } + } + } +} diff --git a/Seatbelt/Commands/Products/LAPSCommand.cs b/Seatbelt/Commands/Products/LAPSCommand.cs new file mode 100644 index 0000000..45571e5 --- /dev/null +++ b/Seatbelt/Commands/Products/LAPSCommand.cs @@ -0,0 +1,103 @@ +using System.Collections.Generic; +using Microsoft.Win32; +using Seatbelt.Output.Formatters; +using Seatbelt.Output.TextWriters; + + +namespace Seatbelt.Commands +{ + internal class LAPSCommand : CommandBase + { + public override string Command => "LAPS"; + public override string Description => "LAPS settings, if installed"; + public override CommandGroup[] Group => new[] {CommandGroup.System}; + public override bool SupportRemote => true; + public Runtime ThisRunTime; + + public LAPSCommand(Runtime runtime) : base(runtime) + { + ThisRunTime = runtime; + } + + public override IEnumerable Execute(string[] args) + { + var AdmPwdEnabled = ThisRunTime.GetStringValue(RegistryHive.LocalMachine, "Software\\Policies\\Microsoft Services\\AdmPwd", "AdmPwdEnabled"); + + if (AdmPwdEnabled != null && !AdmPwdEnabled.Equals("")) + { + var LAPSAdminAccountName = ThisRunTime.GetStringValue(RegistryHive.LocalMachine, "Software\\Policies\\Microsoft Services\\AdmPwd", "AdminAccountName"); + + var LAPSPasswordComplexity = ThisRunTime.GetStringValue(RegistryHive.LocalMachine, "Software\\Policies\\Microsoft Services\\AdmPwd", "PasswordComplexity"); + + var LAPSPasswordLength = ThisRunTime.GetStringValue(RegistryHive.LocalMachine, "Software\\Policies\\Microsoft Services\\AdmPwd", "PasswordLength"); + + var LASPwdExpirationProtectionEnabled = ThisRunTime.GetStringValue(RegistryHive.LocalMachine, "Software\\Policies\\Microsoft Services\\AdmPwd", "PwdExpirationProtectionEnabled"); + + yield return new LapsDTO( + bool.Parse(AdmPwdEnabled), + LAPSAdminAccountName, + LAPSPasswordComplexity, + LAPSPasswordLength, + LASPwdExpirationProtectionEnabled + ); + } + else + { + yield return new LapsDTO( + admPwdEnabled: false, + lapsAdminAccountName: null, + lapsPasswordComplexity: null, + lapsPasswordLength: null, + lapsPwdExpirationProtectionEnabled: null + ); + } + } + + class LapsDTO : CommandDTOBase + { + public LapsDTO(bool admPwdEnabled, string? lapsAdminAccountName, string? lapsPasswordComplexity, string? lapsPasswordLength, string? lapsPwdExpirationProtectionEnabled) + { + AdmPwdEnabled = admPwdEnabled; + LAPSAdminAccountName = lapsAdminAccountName; + LAPSPasswordComplexity = lapsPasswordComplexity; + LAPSPasswordLength = lapsPasswordLength; + LapsPwdExpirationProtectionEnabled = lapsPwdExpirationProtectionEnabled; + } + public bool AdmPwdEnabled { get; } + + public string? LAPSAdminAccountName { get; } + + public string? LAPSPasswordComplexity { get; } + + public string? LAPSPasswordLength { get; } + + public string? LapsPwdExpirationProtectionEnabled { get; } + } + + [CommandOutputType(typeof(LapsDTO))] + internal class LAPSFormatter : TextFormatterBase + { + public LAPSFormatter(ITextWriter writer) : base(writer) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + var dto = (LapsDTO)result; + + if(dto.AdmPwdEnabled.Equals("false")) + { + WriteLine(" [*] LAPS not installed"); + } + else + { + WriteLine(" {0,-37} : {1}", "LAPS Enabled", dto.AdmPwdEnabled); + WriteLine(" {0,-37} : {1}", "LAPS Admin Account Name", dto.LAPSAdminAccountName); + WriteLine(" {0,-37} : {1}", "LAPS Password Complexity", dto.LAPSPasswordComplexity); + WriteLine(" {0,-37} : {1}", "LAPS Password Length", dto.LAPSPasswordLength); + WriteLine(" {0,-37} : {1}", "LAPS Expiration Protection Enabled", dto.LapsPwdExpirationProtectionEnabled); + } + } + } + } +} \ No newline at end of file diff --git a/Seatbelt/Commands/Products/OfficeMRUsCommand.cs b/Seatbelt/Commands/Products/OfficeMRUsCommand.cs new file mode 100644 index 0000000..a4d4aa3 --- /dev/null +++ b/Seatbelt/Commands/Products/OfficeMRUsCommand.cs @@ -0,0 +1,202 @@ +#nullable disable +using Microsoft.Win32; +using Seatbelt.Util; +using System; +using System.Collections.Generic; +using Seatbelt.Output.TextWriters; +using Seatbelt.Output.Formatters; +using Seatbelt.Interop; +using System.Linq; +using System.Text.RegularExpressions; +using System.Globalization; + +namespace Seatbelt.Commands.Windows +{ + class OfficeMRU + { + public string Product { get; set; } + + public string Type { get; set; } + + public string FileName { get; set; } + } + + internal class OfficeMRUsCommand : CommandBase + { + public override string Command => "OfficeMRUs"; + public override string Description => "Office most recently used file list (last 7 days)"; + public override CommandGroup[] Group => new[] { CommandGroup.User }; + public override bool SupportRemote => false; // could, but won't for now + + public OfficeMRUsCommand(Runtime runtime) : base(runtime) + { + } + + + public override IEnumerable Execute(string[] args) + { + var lastDays = 7; + + // parses recent file shortcuts via COM + if (args.Length == 1) + { + lastDays = int.Parse(args[0]); + } + else if (!Runtime.FilterResults) + { + lastDays = 30; + } + + WriteHost("Enumerating Office most recently used files for the last {0} days", lastDays); + WriteHost("\n {0,-8} {1,-23} {2,-12} {3}", "App", "User", "LastAccess", "FileName"); + WriteHost(" {0,-8} {1,-23} {2,-12} {3}", "---", "----", "----------", "--------"); + + foreach (var file in EnumRecentOfficeFiles(lastDays).OrderByDescending(e => ((OfficeRecentFilesDTO)e).LastAccessDate)) + { + yield return file; + } + } + + private IEnumerable EnumRecentOfficeFiles(int lastDays) + { + foreach (var sid in Registry.Users.GetSubKeyNames()) + { + if (!sid.StartsWith("S-1") || sid.EndsWith("_Classes")) + { + continue; + } + + string userName = null; + try + { + userName = Advapi32.TranslateSid(sid); + } + catch + { + userName = sid; + } + + var officeVersion = + RegistryUtil.GetSubkeyNames(RegistryHive.Users, $"{sid}\\Software\\Microsoft\\Office") + ?.Where(k => float.TryParse(k, out _)); + + if (officeVersion is null) + continue; + + foreach (var version in officeVersion) + { + foreach (OfficeRecentFilesDTO mru in GetMRUsFromVersionKey($"{sid}\\Software\\Microsoft\\Office\\{version}")) + { + if (mru.LastAccessDate <= DateTime.Now.AddDays(-lastDays)) continue; + + mru.User = userName; + yield return mru; + } + } + } + } + + private IEnumerable GetMRUsFromVersionKey(string officeVersionSubkeyPath) + { + var officeApplications = RegistryUtil.GetSubkeyNames(RegistryHive.Users, officeVersionSubkeyPath); + if (officeApplications == null) + { + yield break; + } + + foreach (var app in officeApplications) + { + // 1) HKEY_CURRENT_USER\Software\Microsoft\Office\16.0\\File MRU + foreach (var mru in GetMRUsValues($"{officeVersionSubkeyPath}\\{app}\\File MRU")) + { + yield return mru; + } + + // 2) HKEY_CURRENT_USER\Software\Microsoft\Office\16.0\Word\User MRU\ADAL_B7C22499E768F03875FA6C268E771D1493149B23934326A96F6CDFEEEE7F68DA72\File MRU + // or HKEY_CURRENT_USER\Software\Microsoft\Office\16.0\Word\User MRU\LiveId_CC4B824314B318B42E93BE93C46A61575D25608BBACDEEEA1D2919BCC2CF51FF\File MRU + + var logonAapps = RegistryUtil.GetSubkeyNames(RegistryHive.Users, $"{officeVersionSubkeyPath}\\{app}\\User MRU"); + if (logonAapps == null) + continue; + + foreach (var logonApp in logonAapps) + { + foreach (var mru in GetMRUsValues($"{officeVersionSubkeyPath}\\{app}\\User MRU\\{logonApp}\\File MRU")) + { + ((OfficeRecentFilesDTO)mru).Application = app; + yield return mru; + } + } + } + } + + private IEnumerable GetMRUsValues(string fileMRUKeyPath) + { + var values = RegistryUtil.GetValues(RegistryHive.Users, fileMRUKeyPath); + if (values == null) yield break; + foreach (string mru in values.Values) + { + var m = ParseMruString(mru); + if (m != null) + { + yield return m; + } + } + } + + + private OfficeRecentFilesDTO? ParseMruString(string mru) + { + var matches = Regex.Matches(mru, "\\[[a-zA-Z0-9]+?\\]\\[T([a-zA-Z0-9]+?)\\](\\[[a-zA-Z0-9]+?\\])?\\*(.+)"); + if (matches.Count == 0) + { + return null; + } + + long timestamp = 0; + var dateHexString = matches[0].Groups[1].Value; + var filename = matches[0].Groups[matches[0].Groups.Count - 1].Value; + + try + { + timestamp = long.Parse(dateHexString, NumberStyles.HexNumber); + } + catch + { + WriteError($"Could not parse MRU timestamp. Parsed timestamp: {dateHexString} MRU value: {mru}"); + } + + return new OfficeRecentFilesDTO() + { + Application = "Office", + LastAccessDate = DateTime.FromFileTimeUtc(timestamp), + User = null, + Target = filename + }; + } + + internal class OfficeRecentFilesDTO : CommandDTOBase + { + public string Application { get; set; } + public string Target { get; set; } + public DateTime LastAccessDate { get; set; } + public string User { get; set; } + } + + [CommandOutputType(typeof(OfficeRecentFilesDTO))] + internal class OfficeMRUsCommandFormatter : TextFormatterBase + { + public OfficeMRUsCommandFormatter(ITextWriter writer) : base(writer) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + var dto = (OfficeRecentFilesDTO)result; + + WriteLine(" {0,-8} {1,-23} {2,-12} {3}", dto.Application, dto.User, dto.LastAccessDate.ToString("yyyy-MM-dd"), dto.Target); + } + } + } +} +#nullable enable \ No newline at end of file diff --git a/Seatbelt/Commands/Products/OutlookDownloadsCommand.cs b/Seatbelt/Commands/Products/OutlookDownloadsCommand.cs new file mode 100644 index 0000000..808812f --- /dev/null +++ b/Seatbelt/Commands/Products/OutlookDownloadsCommand.cs @@ -0,0 +1,105 @@ +#nullable disable +using System; +using System.Collections.Generic; +using System.IO; +using Seatbelt.Output.TextWriters; +using Seatbelt.Output.Formatters; + + +namespace Seatbelt.Commands.Windows +{ + class OutlookDownload + { + public string FileName { get; set; } + + public DateTime LastAccessed { get; set; } + + public DateTime LastModified { get; set; } + } + + internal class OutlookDownloadsCommand : CommandBase + { + public override string Command => "OutlookDownloads"; + public override string Description => "List files downloaded by Outlook"; + public override CommandGroup[] Group => new[] { CommandGroup.Misc }; + public override bool SupportRemote => false; // could, but won't for now + + public OutlookDownloadsCommand(Runtime runtime) : base(runtime) + { + } + + public override IEnumerable Execute(string[] args) + { + var userFolder = $"{Environment.GetEnvironmentVariable("SystemDrive")}\\Users\\"; + var dirs = Directory.GetDirectories(userFolder); + + foreach (var dir in dirs) + { + if (dir.EndsWith("Public") || dir.EndsWith("Default") || dir.EndsWith("Default User") || dir.EndsWith("All Users")) + { + continue; + } + + var userOutlookBasePath = $"{dir}\\AppData\\Local\\Microsoft\\Windows\\INetCache\\Content.Outlook\\"; + if (!Directory.Exists(userOutlookBasePath)) + { + continue; + } + + var directories = Directory.GetDirectories(userOutlookBasePath); + foreach (var directory in directories) + { + var files = Directory.GetFiles(directory); + + var Downloads = new List(); + + foreach (var file in files) + { + var download = new OutlookDownload(); + download.FileName = Path.GetFileName(file); + download.LastAccessed = File.GetLastAccessTime(file); + download.LastModified = File.GetLastAccessTime(file); + Downloads.Add(download); + } + + yield return new OutlookDownloadsDTO() + { + Folder = $"{directory}", + Downloads = Downloads + }; + } + } + } + + internal class OutlookDownloadsDTO : CommandDTOBase + { + public string Folder { get; set; } + public List Downloads { get; set; } + } + + [CommandOutputType(typeof(OutlookDownloadsDTO))] + internal class OutlookDownloadsFormatter : TextFormatterBase + { + public OutlookDownloadsFormatter(ITextWriter writer) : base(writer) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + var dto = (OutlookDownloadsDTO)result; + + WriteLine(" Folder : {0}\n", dto.Folder); + WriteLine($" LastAccessed LastModified FileName"); + WriteLine($" ------------ ------------ --------"); + + foreach (var download in dto.Downloads) + { + WriteLine(" {0,-22} {1,-22} {2}", download.LastAccessed, download.LastModified, download.FileName); + } + + WriteLine(); + } + } + } +} +#nullable enable \ No newline at end of file diff --git a/Seatbelt/Commands/Products/PuttyHostKeysCommand.cs b/Seatbelt/Commands/Products/PuttyHostKeysCommand.cs new file mode 100644 index 0000000..502dbc3 --- /dev/null +++ b/Seatbelt/Commands/Products/PuttyHostKeysCommand.cs @@ -0,0 +1,85 @@ +using Microsoft.Win32; +using System.Collections.Generic; +using Seatbelt.Output.TextWriters; +using Seatbelt.Output.Formatters; + + +namespace Seatbelt.Commands +{ + internal class PuttyHostKeysCommand : CommandBase + { + public override string Command => "PuttyHostKeys"; + public override string Description => "Saved Putty SSH host keys"; + public override CommandGroup[] Group => new[] { CommandGroup.User, CommandGroup.Remote }; + public override bool SupportRemote => true; + public Runtime ThisRunTime; + + public PuttyHostKeysCommand(Runtime runtime) : base(runtime) + { + ThisRunTime = runtime; + } + + public override IEnumerable Execute(string[] args) + { + var SIDs = ThisRunTime.GetUserSIDs(); + + foreach (var sid in SIDs) + { + if (!sid.StartsWith("S-1-5") || sid.EndsWith("_Classes")) + { + continue; + } + + var hostKeys = ThisRunTime.GetValues(RegistryHive.Users, $"{sid}\\Software\\SimonTatham\\PuTTY\\SshHostKeys\\"); + if (hostKeys == null || hostKeys.Count == 0) + { + continue; + } + + var keys = new List(); + + foreach (var kvp in hostKeys) + { + keys.Add($"{kvp.Key}"); + } + + yield return new PuttyHostKeysDTO( + sid, + keys + ); + } + } + + internal class PuttyHostKeysDTO : CommandDTOBase + { + public PuttyHostKeysDTO(string sid, List hostKeys) + { + Sid = sid; + HostKeys = hostKeys; + } + public string Sid { get; } + public List HostKeys { get; } + } + + [CommandOutputType(typeof(PuttyHostKeysDTO))] + internal class PuttyHostKeysFormatter : TextFormatterBase + { + public PuttyHostKeysFormatter(ITextWriter writer) : base(writer) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + var dto = (PuttyHostKeysDTO)result; + + WriteLine(" {0} :", dto.Sid); + + foreach (var hostKey in dto.HostKeys) + { + WriteLine($" {hostKey}"); + } + WriteLine(); + } + } + } +} diff --git a/Seatbelt/Commands/Products/PuttySessionsCommand.cs b/Seatbelt/Commands/Products/PuttySessionsCommand.cs new file mode 100644 index 0000000..fd2268c --- /dev/null +++ b/Seatbelt/Commands/Products/PuttySessionsCommand.cs @@ -0,0 +1,121 @@ +using Microsoft.Win32; +using System.Collections.Generic; +using Seatbelt.Output.TextWriters; +using Seatbelt.Output.Formatters; + +// TODO: Need to create a DTO +namespace Seatbelt.Commands +{ + internal class PuttySessionsCommand : CommandBase + { + public override string Command => "PuttySessions"; + public override string Description => "Saved Putty configuration (interesting fields) and SSH host keys"; + public override CommandGroup[] Group => new[] { CommandGroup.User, CommandGroup.Remote }; + public override bool SupportRemote => true; + public Runtime ThisRunTime; + + public PuttySessionsCommand(Runtime runtime) : base(runtime) + { + ThisRunTime = runtime; + } + + public override IEnumerable Execute(string[] args) + { + var SIDs = ThisRunTime.GetUserSIDs(); + foreach (var sid in SIDs) + { + if (!sid.StartsWith("S-1-5") || sid.EndsWith("_Classes")) + { + continue; + } + + var subKeys = ThisRunTime.GetSubkeyNames(RegistryHive.Users, $"{sid}\\Software\\SimonTatham\\PuTTY\\Sessions\\"); + if (subKeys == null) + continue; + + var Sessions = new List>(); + + foreach (var sessionName in subKeys) + { + var Settings = new Dictionary + { + ["SessionName"] = sessionName + }; + + string[] keys = + { + "HostName", + "UserName", + "PublicKeyFile", + "PortForwardings", + "ConnectionSharing", + "AgentFwd" + }; + + foreach (var key in keys) + { +#nullable disable + var result = ThisRunTime.GetStringValue(RegistryHive.Users, $"{sid}\\Software\\SimonTatham\\PuTTY\\Sessions\\{sessionName}", key); + if (!string.IsNullOrEmpty(result)) + { + Settings[key] = result; + } +#nullable restore + } + + Sessions.Add(Settings); + } + + if (Sessions.Count != 0) + { + yield return new PuttySessionsDTO( + sid, + Sessions + ); + } + } + } + + internal class PuttySessionsDTO : CommandDTOBase + { + public PuttySessionsDTO(string sid, List> sessions) + { + SID = sid; + Sessions = sessions; + } + public string SID { get; } + + public List> Sessions { get; } + } + + [CommandOutputType(typeof(PuttySessionsDTO))] + internal class ExplorerRunCommandFormatter : TextFormatterBase + { + public ExplorerRunCommandFormatter(ITextWriter writer) : base(writer) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + var dto = (PuttySessionsDTO)result; + + WriteLine(" {0} :\n", dto.SID); + + foreach (var session in dto.Sessions) + { + WriteLine(" {0,-20} : {1}", "SessionName", session["SessionName"]); + + foreach (var key in session.Keys) + { + if(!key.Equals("SessionName")) + { + WriteLine(" {0,-20} : {1}", key, session[key]); + } + } + WriteLine(); + } + WriteLine(); + } + } + } +} diff --git a/Seatbelt/Commands/Products/RemoteDesktopConnectionManagerCommand.cs b/Seatbelt/Commands/Products/RemoteDesktopConnectionManagerCommand.cs new file mode 100644 index 0000000..cf1b522 --- /dev/null +++ b/Seatbelt/Commands/Products/RemoteDesktopConnectionManagerCommand.cs @@ -0,0 +1,113 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Xml; +using Seatbelt.Output.TextWriters; +using Seatbelt.Output.Formatters; + + +namespace Seatbelt.Commands +{ + internal class RemoteDesktopConnectionManagerCommand : CommandBase + { + public override string Command => "RDCManFiles"; + public override string Description => "Windows Remote Desktop Connection Manager settings files"; + public override CommandGroup[] Group => new[] { CommandGroup.User }; + public override bool SupportRemote => false; + + public RemoteDesktopConnectionManagerCommand(Runtime runtime) : base(runtime) + { + } + + public override IEnumerable Execute(string[] args) + { + var userFolder = $"{Environment.GetEnvironmentVariable("SystemDrive")}\\Users\\"; + var dirs = Directory.GetDirectories(userFolder); + var found = false; + + foreach (var dir in dirs) + { + if (dir.EndsWith("Public") || dir.EndsWith("Default") || dir.EndsWith("Default User") || dir.EndsWith("All Users")) + continue; + + var userRDManFile = $"{dir}\\AppData\\Local\\Microsoft\\Remote Desktop Connection Manager\\RDCMan.settings"; + if (!File.Exists(userRDManFile)) + continue; + + + var xmlDoc = new XmlDocument(); + xmlDoc.Load(userRDManFile); + + // grab the recent RDG files + var filesToOpen = xmlDoc.GetElementsByTagName("FilesToOpen"); + var items = filesToOpen[0].ChildNodes; + + var lastAccessed = File.GetLastAccessTime(userRDManFile); + var lastModified = File.GetLastWriteTime(userRDManFile); + + var rdgFiles = new List(); + + foreach (XmlNode rdgFile in items) + { + found = true; + rdgFiles.Add(rdgFile.InnerText); + } + + yield return new RemoteDesktopConnectionManagerDTO( + userRDManFile, + lastAccessed, + lastModified, + rdgFiles + ); + } + + if (found) + { + WriteHost(" [*] You can use SharpDPAPI or the Mimikatz \"dpapi::rdg\" module to decrypt any found .rdg files"); + } + } + + internal class RemoteDesktopConnectionManagerDTO : CommandDTOBase + { + public RemoteDesktopConnectionManagerDTO(string fileName, DateTime lastAccessed, DateTime lastModified, List rdgFiles) + { + FileName = fileName; + LastAccessed = lastAccessed; + LastModified = lastModified; + RdgFiles = rdgFiles; + } + public string FileName { get; } + + public DateTime LastAccessed { get; } + + public DateTime LastModified { get; } + + public List RdgFiles { get; } + } + + + [CommandOutputType(typeof(RemoteDesktopConnectionManagerDTO))] + internal class RemoteDesktopConnectionManagerFormatter : TextFormatterBase + { + public RemoteDesktopConnectionManagerFormatter(ITextWriter writer) : base(writer) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + var dto = (RemoteDesktopConnectionManagerDTO)result; + + WriteLine(" RDCManFile : {0}", dto.FileName); + WriteLine(" Accessed : {0}", dto.LastAccessed); + WriteLine(" Modified : {0}", dto.LastModified); + + foreach(var rdgFile in dto.RdgFiles) + { + WriteLine(" .RDG File : {0}", rdgFile); + } + + WriteLine(); + } + } + } +} diff --git a/Seatbelt/Commands/Products/SccmClientCommand.cs b/Seatbelt/Commands/Products/SccmClientCommand.cs new file mode 100644 index 0000000..75a2906 --- /dev/null +++ b/Seatbelt/Commands/Products/SccmClientCommand.cs @@ -0,0 +1,40 @@ +#nullable disable +using System.Collections.Generic; +using Microsoft.Win32; + +namespace Seatbelt.Commands +{ + internal class SccmClientCommand : CommandBase + { + public override string Command => "SCCM"; + public override string Description => "System Center Configuration Manager (SCCM) settings, if applicable"; + public override CommandGroup[] Group => new[] { CommandGroup.System }; + public override bool SupportRemote => true; + public Runtime ThisRunTime; + + public SccmClientCommand(Runtime runtime) : base(runtime) + { + ThisRunTime = runtime; + } + + public override IEnumerable Execute(string[] args) + { + yield return new SccmClientDTO() + { + Server = ThisRunTime.GetStringValue(RegistryHive.LocalMachine, @"SOFTWARE\Microsoft\CCMSetup", "LastValidMP"), + SiteCode = ThisRunTime.GetStringValue(RegistryHive.LocalMachine, @"SOFTWARE\Microsoft\SMS\Mobile Client", "AssignedSiteCode"), + ProductVersion = ThisRunTime.GetStringValue(RegistryHive.LocalMachine, @"SOFTWARE\Microsoft\SMS\Mobile Client", "ProductVersion"), + LastSuccessfulInstallParams = ThisRunTime.GetStringValue(RegistryHive.LocalMachine, @"SOFTWARE\Microsoft\SMS\Mobile Client", "LastSuccessfulInstallParams"), // Sometimes contains the fallback server's hostname + }; + } + } + + internal class SccmClientDTO : CommandDTOBase + { + public string Server { get; set; } + public string SiteCode { get; set; } + public string ProductVersion { get; set; } + public string LastSuccessfulInstallParams { get; set; } + } +} +#nullable enable \ No newline at end of file diff --git a/Seatbelt/Commands/Products/SlackDownloadsCommand.cs b/Seatbelt/Commands/Products/SlackDownloadsCommand.cs new file mode 100644 index 0000000..f7cda27 --- /dev/null +++ b/Seatbelt/Commands/Products/SlackDownloadsCommand.cs @@ -0,0 +1,143 @@ +#nullable disable +using System; +using System.Collections.Generic; +using System.IO; +using System.Web.Script.Serialization; +using Seatbelt.Output.Formatters; +using Seatbelt.Output.TextWriters; +using Seatbelt.Util; + + +namespace Seatbelt.Commands +{ + class Download + { + public string TeamID { get; set; } + public string UserID { get; set; } + public string DownloadPath { get; set; } + public DateTime StartTime { get; set; } + } + + internal class SlackDownloadsCommand : CommandBase + { + public override string Command => "SlackDownloads"; + public override string Description => "Parses any found 'slack-downloads' files"; + public override CommandGroup[] Group => new[] { CommandGroup.User, CommandGroup.Slack }; + public override bool SupportRemote => false; + + public SlackDownloadsCommand(Runtime runtime) : base(runtime) + { + } + + public override IEnumerable Execute(string[] args) + { + var userFolder = $"{Environment.GetEnvironmentVariable("SystemDrive")}\\Users\\"; + var dirs = Directory.GetDirectories(userFolder); + foreach (var dir in dirs) + { + var parts = dir.Split('\\'); + var userName = parts[parts.Length - 1]; + if (dir.EndsWith("Public") || dir.EndsWith("Default") || dir.EndsWith("Default User") || + dir.EndsWith("All Users")) + { + continue; + } + + var userSlackDownloadsPath = $"{dir}\\AppData\\Roaming\\Slack\\storage\\slack-downloads"; + + // parses a Slack downloads file + if (File.Exists(userSlackDownloadsPath)) + { + var Downloads = new List(); + + try + { + var contents = File.ReadAllText(userSlackDownloadsPath); + + // reference: http://www.tomasvera.com/programming/using-javascriptserializer-to-parse-json-objects/ + var json = new JavaScriptSerializer(); + var deserialized = json.Deserialize>(contents); + + foreach (var w in deserialized) + { + var dls = (Dictionary)w.Value; + foreach (var x in dls) + { + var dl = (Dictionary)x.Value; + var download = new Download(); + if (dl.ContainsKey("teamId")) + { + download.TeamID = $"{dl["teamId"]}"; + } + if (dl.ContainsKey("userId")) + { + download.UserID = $"{dl["userId"]}"; + } + if (dl.ContainsKey("downloadPath")) + { + download.DownloadPath = $"{dl["downloadPath"]}"; + } + if (dl.ContainsKey("startTime")) + { + try + { + download.StartTime = MiscUtil.UnixEpochToDateTime(long.Parse($"{dl["startTime"]}")); + } + catch + { + } + } + Downloads.Add(download); + } + } + } + catch (IOException exception) + { + WriteError(exception.ToString()); + } + catch (Exception exception) + { + WriteError(exception.ToString()); + } + + yield return new SlackDownloadsDTO() + { + UserName = userName, + Downloads = Downloads + }; + } + } + } + + internal class SlackDownloadsDTO : CommandDTOBase + { + public string UserName { get; set; } + public List Downloads { get; set; } + } + + [CommandOutputType(typeof(SlackDownloadsDTO))] + internal class SlackDownloadsFormatter : TextFormatterBase + { + public SlackDownloadsFormatter(ITextWriter writer) : base(writer) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + var dto = (SlackDownloadsDTO)result; + + WriteLine($" Downloads ({dto.UserName}):\n"); + + foreach (var download in dto.Downloads) + { + WriteLine($" TeamID : {download.TeamID}"); + WriteLine($" UserId : {download.UserID}"); + WriteLine($" DownloadPath : {download.DownloadPath}"); + WriteLine($" StartTime : {download.StartTime}\n"); + } + WriteLine(); + } + } + } +} +#nullable enable \ No newline at end of file diff --git a/Seatbelt/Commands/Products/SlackPresenceCommand.cs b/Seatbelt/Commands/Products/SlackPresenceCommand.cs new file mode 100644 index 0000000..b856d8e --- /dev/null +++ b/Seatbelt/Commands/Products/SlackPresenceCommand.cs @@ -0,0 +1,118 @@ +using System; +using System.Collections.Generic; +using System.IO; +using Seatbelt.Output.TextWriters; +using Seatbelt.Output.Formatters; + + +namespace Seatbelt.Commands +{ + internal class SlackPresenceCommand : CommandBase + { + public override string Command => "SlackPresence"; + public override string Description => "Checks if interesting Slack files exist"; + public override CommandGroup[] Group => new[] { CommandGroup.User, CommandGroup.Slack }; + public override bool SupportRemote => false; + + public SlackPresenceCommand(Runtime runtime) : base(runtime) + { + } + + public override IEnumerable Execute(string[] args) + { + + var userFolder = $"{Environment.GetEnvironmentVariable("SystemDrive")}\\Users\\"; + var dirs = Directory.GetDirectories(userFolder); + foreach (var dir in dirs) + { + if (dir.EndsWith("Public") || dir.EndsWith("Default") || dir.EndsWith("Default User") || + dir.EndsWith("All Users")) + { + continue; + } + + var slackBasePath = $"{dir}\\AppData\\Roaming\\Slack\\"; + if (!Directory.Exists(slackBasePath)) + { + continue; + } + + DateTime? cookiesLastWriteTime = null, + workspacesLastWriteTime = null, + downloadsLastWriteTime = null; + + var userSlackCookiesPath = $"{dir}\\AppData\\Roaming\\Slack\\Cookies"; + if (File.Exists(userSlackCookiesPath)) + { + cookiesLastWriteTime = File.GetLastWriteTime(userSlackCookiesPath); + } + + var userSlackWorkspacesPath = $"{dir}\\AppData\\Roaming\\Slack\\storage\\slack-workspaces"; + if (File.Exists(userSlackWorkspacesPath)) + { + workspacesLastWriteTime = File.GetLastWriteTime(userSlackWorkspacesPath); + } + + var userSlackDownloadsPath = $"{dir}\\AppData\\Roaming\\Slack\\storage\\slack-downloads"; + if (File.Exists(userSlackDownloadsPath)) + { + downloadsLastWriteTime = File.GetLastWriteTime(userSlackDownloadsPath); + } + + if (cookiesLastWriteTime != null || workspacesLastWriteTime != null || downloadsLastWriteTime != null) + { + yield return new SlackPresenceDTO( + folder: $"{dir}\\AppData\\Roaming\\Slack\\", + cookiesLastWriteTime, + workspacesLastWriteTime, + downloadsLastWriteTime + ); + } + } + } + + internal class SlackPresenceDTO : CommandDTOBase + { + public SlackPresenceDTO(string folder, DateTime? cookiesLastModified, DateTime? workspacesLastModified, DateTime? downloadsLastModified) + { + Folder = folder; + CookiesLastModified = cookiesLastModified; + WorkspacesLastModified = workspacesLastModified; + DownloadsLastModified = downloadsLastModified; + } + public string? Folder { get; } + public DateTime? CookiesLastModified { get; } + public DateTime? WorkspacesLastModified { get; } + public DateTime? DownloadsLastModified { get; } + } + + [CommandOutputType(typeof(SlackPresenceDTO))] + internal class SlackPresenceFormatter : TextFormatterBase + { + public SlackPresenceFormatter(ITextWriter writer) : base(writer) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + var dto = (SlackPresenceDTO)result; + + WriteLine(" {0}\n", dto.Folder); + if (dto.CookiesLastModified != DateTime.MinValue) + { + WriteLine(" 'Cookies' ({0}) : Download the 'Cookies' and 'storage\\slack-workspaces' files to clone Slack access", dto.CookiesLastModified); + } + if (dto.WorkspacesLastModified != DateTime.MinValue) + { + WriteLine(" '\\storage\\slack-workspaces' ({0}) : Run the 'SlackWorkspaces' command", dto.WorkspacesLastModified); + } + if (dto.DownloadsLastModified != DateTime.MinValue) + { + WriteLine(" '\\storage\\slack-downloads' ({0}) : Run the 'SlackDownloads' command", dto.DownloadsLastModified); + } + + WriteLine(); + } + } + } +} diff --git a/Seatbelt/Commands/Products/SlackWorkspacesCommand.cs b/Seatbelt/Commands/Products/SlackWorkspacesCommand.cs new file mode 100644 index 0000000..5908332 --- /dev/null +++ b/Seatbelt/Commands/Products/SlackWorkspacesCommand.cs @@ -0,0 +1,133 @@ +#nullable disable +using System; +using System.Collections.Generic; +using System.IO; +using System.Web.Script.Serialization; +using Seatbelt.Output.Formatters; +using Seatbelt.Output.TextWriters; + + +namespace Seatbelt.Commands.Products +{ + class Workspace + { + public string Name { get; set; } + public string Domain { get; set; } + public string ID { get; set; } + } + + internal class SlackWorkspacesCommand : CommandBase + { + public override string Command => "SlackWorkspaces"; + public override string Description => "Parses any found 'slack-workspaces' files"; + public override CommandGroup[] Group => new[] { CommandGroup.User, CommandGroup.Slack }; + public override bool SupportRemote => false; + + public SlackWorkspacesCommand(Runtime runtime) : base(runtime) + { + } + + public override IEnumerable Execute(string[] args) + { + var userFolder = $"{Environment.GetEnvironmentVariable("SystemDrive")}\\Users\\"; + var dirs = Directory.GetDirectories(userFolder); + foreach (var dir in dirs) + { + var parts = dir.Split('\\'); + var userName = parts[parts.Length - 1]; + if (dir.EndsWith("Public") || dir.EndsWith("Default") || dir.EndsWith("Default User") || + dir.EndsWith("All Users")) + { + continue; + } + + var userSlackWorkspacesPath = $"{dir}\\AppData\\Roaming\\Slack\\storage\\slack-workspaces"; + + // parses a Slack workspaces file + if (File.Exists(userSlackWorkspacesPath)) + { + var workspaces = new List(); + + try + { + var contents = File.ReadAllText(userSlackWorkspacesPath); + + // reference: http://www.tomasvera.com/programming/using-javascriptserializer-to-parse-json-objects/ + var json = new JavaScriptSerializer(); + var deserialized = json.Deserialize>(contents); + + foreach (var w in deserialized) + { + var settings = (Dictionary)w.Value; + + var workspace = new Workspace(); + if (settings.ContainsKey("name")) + { + workspace.Name = $"{settings["name"]}"; + } + if (settings.ContainsKey("domain")) + { + workspace.Domain = $"{settings["domain"]}"; + } + if (settings.ContainsKey("id")) + { + workspace.ID = $"{settings["id"]}"; + } + + workspaces.Add(workspace); + } + } + catch (IOException exception) + { + WriteError(exception.ToString()); + } + catch (Exception exception) + { + WriteError(exception.ToString()); + } + + yield return new SlackWorkspacesDTO( + userName, + workspaces + ); + } + } + } + + internal class SlackWorkspacesDTO : CommandDTOBase + { + public SlackWorkspacesDTO(string userName, List workspaces) + { + UserName = userName; + Workspaces = workspaces; + } + + public string UserName { get; } + public List Workspaces { get; } + } + + [CommandOutputType(typeof(SlackWorkspacesDTO))] + internal class SlackWorkspacesFormatter : TextFormatterBase + { + public SlackWorkspacesFormatter(ITextWriter writer) : base(writer) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + var dto = (SlackWorkspacesDTO)result; + + WriteLine($" Workspaces ({dto.UserName}):\n"); + + foreach (var workspace in dto.Workspaces) + { + WriteLine($" Name : {workspace.Name}"); + WriteLine($" Domain : {workspace.Domain}"); + WriteLine($" ID : {workspace.ID}\n"); + } + WriteLine(); + } + } + } +} +#nullable enable \ No newline at end of file diff --git a/Seatbelt/Commands/Products/SysmonCommand.cs b/Seatbelt/Commands/Products/SysmonCommand.cs new file mode 100644 index 0000000..2f8fdd3 --- /dev/null +++ b/Seatbelt/Commands/Products/SysmonCommand.cs @@ -0,0 +1,103 @@ +#nullable disable +using System; +using System.Collections.Generic; +using Microsoft.Win32; +using Seatbelt.Util; + + +namespace Seatbelt.Commands +{ + // TODO: Grab the version of Sysmon from its binary + internal class SysmonCommand : CommandBase + { + public override string Command => "Sysmon"; + public override string Description => "Sysmon configuration from the registry"; + public override CommandGroup[] Group => new[] { CommandGroup.System, CommandGroup.Remote }; + public override bool SupportRemote => true; + public Runtime ThisRunTime; + + // hashing algorithm reference from @mattifestation's SysmonRuleParser.ps1 + // ref - https://github.com/mattifestation/PSSysmonTools/blob/master/PSSysmonTools/Code/SysmonRuleParser.ps1#L589-L595 + [Flags] + public enum SysmonHashAlgorithm + { + NotDefined = 0, + SHA1 = 1, + MD5 = 2, + SHA256 = 4, + IMPHASH = 8 + } + + [Flags] + public enum SysmonOptions + { + NotDefined = 0, + NetworkConnection = 1, + ImageLoading = 2 + } + + public SysmonCommand(Runtime runtime) : base(runtime) + { + ThisRunTime = runtime; + } + + public override IEnumerable Execute(string[] args) + { + + if (!SecurityUtil.IsHighIntegrity() && !ThisRunTime.ISRemote()) + { + WriteError("Unable to collect. Must be an administrator."); + yield break; + } + + var hashAlg = ThisRunTime.GetDwordValue(RegistryHive.LocalMachine, @"SYSTEM\CurrentControlSet\Services\SysmonDrv\Parameters", "HashingAlgorithm"); + var options = ThisRunTime.GetDwordValue(RegistryHive.LocalMachine, @"SYSTEM\CurrentControlSet\Services\SysmonDrv\Parameters", "Options"); + var sysmonRules = ThisRunTime.GetBinaryValue(RegistryHive.LocalMachine, @"SYSTEM\CurrentControlSet\Services\SysmonDrv\Parameters", "Rules"); + var installed = false; + var HashingAlgorithm = (SysmonHashAlgorithm)0; + var Options = (SysmonOptions)0; + var b64SysmonRules = ""; + + if ((hashAlg != null) || (options != null) || (sysmonRules != null)) + { + installed = true; + } + + if (hashAlg != null && hashAlg != 0) + { + hashAlg = hashAlg & 15; // we only care about the last 4 bits + HashingAlgorithm = (SysmonHashAlgorithm)hashAlg; + } + + if (options != null) + { + Options = (SysmonOptions)options; + } + + if (sysmonRules != null) + { + b64SysmonRules = Convert.ToBase64String(sysmonRules); + } + + yield return new SysmonDTO() + { + Installed = installed, + HashingAlgorithm = HashingAlgorithm, + Options = Options, + Rules = b64SysmonRules + }; + } + + internal class SysmonDTO : CommandDTOBase + { + public bool Installed { get; set; } + + public SysmonHashAlgorithm HashingAlgorithm { get; set; } + + public SysmonOptions Options { get; set; } + + public string Rules { get; set; } + } + } +} +#nullable enable \ No newline at end of file diff --git a/Seatbelt/Commands/Products/WsusClientCommand.cs b/Seatbelt/Commands/Products/WsusClientCommand.cs new file mode 100644 index 0000000..7c38979 --- /dev/null +++ b/Seatbelt/Commands/Products/WsusClientCommand.cs @@ -0,0 +1,40 @@ +#nullable disable +using System.Collections.Generic; +using Microsoft.Win32; + +namespace Seatbelt.Commands +{ + internal class WsusClientCommand : CommandBase + { + public override string Command => "WSUS"; + public override string Description => "Windows Server Update Services (WSUS) settings, if applicable"; + public override CommandGroup[] Group => new[] {CommandGroup.System}; + public override bool SupportRemote => true; + public Runtime ThisRunTime; + + public WsusClientCommand(Runtime runtime) : base(runtime) + { + ThisRunTime = runtime; + } + + public override IEnumerable Execute(string[] args) + { + yield return new WsusClientDTO() + { + UseWUServer = (ThisRunTime.GetDwordValue(RegistryHive.LocalMachine, @"SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU", "UseWUServer") == 1), + Server = ThisRunTime.GetStringValue(RegistryHive.LocalMachine, @"SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate", "WUServer"), + AlternateServer = ThisRunTime.GetStringValue(RegistryHive.LocalMachine, @"SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate", "UpdateServiceUrlAlternate"), + StatisticsServer = ThisRunTime.GetStringValue(RegistryHive.LocalMachine, @"SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate", "WUStatusServer"), + }; + } + } + + internal class WsusClientDTO : CommandDTOBase + { + public bool UseWUServer { get; set; } + public string Server { get; set; } + public string AlternateServer { get; set; } + public string StatisticsServer { get; set; } + } +} +#nullable enable \ No newline at end of file diff --git a/Seatbelt/Commands/Template.cs b/Seatbelt/Commands/Template.cs new file mode 100644 index 0000000..f6280ee --- /dev/null +++ b/Seatbelt/Commands/Template.cs @@ -0,0 +1,93 @@ +#if DEBUG +using Microsoft.Win32; +using System.Collections.Generic; +using Seatbelt.Output.TextWriters; +using Seatbelt.Output.Formatters; + +// Any command you create should not generate compiler warnings +namespace Seatbelt.Commands.Windows +{ + // Replace all instances of "TEMPLATE" with the command name you're building + internal class TEMPLATECommand : CommandBase + { + public override string Command => "TEMPLATE"; + public override string Description => "Description for your command"; + public override CommandGroup[] Group => new[] {CommandGroup.User}; // either CommandGroup.System, CommandGroup.User, or CommandGroup.Misc + public override bool SupportRemote => true; // set to true if you want to signal that your module supports remote operations + public Runtime ThisRunTime; + + public TEMPLATECommand(Runtime runtime) : base(runtime) + { + // use a constructor of this type if you want to support remote operations + ThisRunTime = runtime; + } + + public override IEnumerable Execute(string[] args) + { + // .\Seatbelt\Runtime.cs contains a number of helper WMI/Registry functions that lets you implicitly perform enumeration locally or remotely. + // GetManagementObjectSearcher(string nameSpace, string query) ==> easy WMI namespace searching. See DNSCacheCommand.cs + // GetSubkeyNames(RegistryHive hive, string path) ==> registry subkey enumeration via WMI StdRegProv. See PuttySessions.cs + // GetStringValue(RegistryHive hive, string path, string value) ==> retrieve a string registry value via WMI StdRegProv. See PuttySessions.cs + // GetDwordValue(RegistryHive hive, string path, string value) ==> retrieve an uint registry value via WMI StdRegProv. See NtlmSettingsCommand.cs + // GetBinaryValue(RegistryHive hive, string path, string value) ==> retrieve an binary registry value via WMI StdRegProv. See SysmonCommand.cs + // GetValues(RegistryHive hive, string path) ==> retrieve the values under a path. See PuttyHostKeys.cs. + // GetUserSIDs() ==> return all user SIDs under HKU. See PuttyHostKeys.cs. + + var providers = ThisRunTime.GetSubkeyNames(RegistryHive.LocalMachine, @"SOFTWARE\Microsoft\AMSI\Providers"); + if(providers == null) + yield break; // Exit the function and don't return anything + + foreach (var provider in providers) + { + var providerPath = ThisRunTime.GetStringValue(RegistryHive.LocalMachine, $"SOFTWARE\\Classes\\CLSID\\{provider}\\InprocServer32", ""); + + // Avoid writing output inside this function. + // If you want to format your output in a special way, use a text formatter class (see below) + // You _can_ using the following function, however it's not recommended and will be going away in the future. + // If you do, this data will not be serialized. + // WriteHost("OUTPUT"); + + // yield your DTO objects. If you need to yield a _collection_ of multiple objects, set one of the DTO properties to be a List or something similar. + yield return new TEMPLATEDTO( + provider, + providerPath + ); + } + } + + // This is the output data transfer object (DTO). + // Properties in this class should only have getters or private setters, and should be initialized in the constructor. + // Some of the existing commands are migrating to this format (in case you see ones that do not conform). + internal class TEMPLATEDTO : CommandDTOBase + { + public TEMPLATEDTO(string property, string? propertyPath) + { + Property = property; + PropertyPath = propertyPath; + } + public string Property { get; } + public string? PropertyPath { get; } + } + + + // This is optional. + // If you want to format the output in a particular way, implement it here. + // A good example is .\Seatbelt\Commands\Windows\NtlmSettingsCommand.cs + // If this class does not exist, Seatbelt will use the DefaultTextFormatter class + [CommandOutputType(typeof(TEMPLATEDTO))] + internal class TEMPLATEFormatter : TextFormatterBase + { + public TEMPLATEFormatter(ITextWriter writer) : base(writer) + { + // nothing goes here + } + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + // use the following function here if you want to write out to the cmdline. This data will not be serialized. + WriteLine("OUTPUT"); + } + } + } +} +#endif \ No newline at end of file diff --git a/Seatbelt/Commands/VerboseDTO.cs b/Seatbelt/Commands/VerboseDTO.cs new file mode 100644 index 0000000..76d4ae8 --- /dev/null +++ b/Seatbelt/Commands/VerboseDTO.cs @@ -0,0 +1,30 @@ + +using Seatbelt.Output.Formatters; +using Seatbelt.Output.TextWriters; + +namespace Seatbelt.Commands +{ + class VerboseDTO : CommandDTOBase + { + public VerboseDTO(string message) + { + Message = message; + } + + public string Message { get; } + } + + [CommandOutputType(typeof(VerboseDTO))] + internal class VerboseTextFormatter : TextFormatterBase + { + public VerboseTextFormatter(ITextWriter writer) : base(writer) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase dto, bool filterResults) + { + //WriteLine("VERBOSE: " + ((VerboseDTO)dto).Message); + WriteLine(((VerboseDTO)dto).Message); + } + } +} diff --git a/Seatbelt/Commands/WarningDTO.cs b/Seatbelt/Commands/WarningDTO.cs new file mode 100644 index 0000000..9474e7a --- /dev/null +++ b/Seatbelt/Commands/WarningDTO.cs @@ -0,0 +1,29 @@ + +using Seatbelt.Output.Formatters; +using Seatbelt.Output.TextWriters; + +namespace Seatbelt.Commands +{ + class WarningDTO : CommandDTOBase + { + public WarningDTO(string message) + { + Message = message; + } + + public string Message { get; } + } + + [CommandOutputType(typeof(WarningDTO))] + internal class WarningTextFormatter : TextFormatterBase + { + public WarningTextFormatter(ITextWriter writer) : base(writer) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase dto, bool filterResults) + { + WriteLine("WARNING: " + ((WarningDTO)dto).Message); + } + } +} diff --git a/Seatbelt/Commands/Windows/AMSIProvidersCommand.cs b/Seatbelt/Commands/Windows/AMSIProvidersCommand.cs new file mode 100644 index 0000000..40aafba --- /dev/null +++ b/Seatbelt/Commands/Windows/AMSIProvidersCommand.cs @@ -0,0 +1,42 @@ +#nullable disable +using Microsoft.Win32; +using System.Collections.Generic; + +namespace Seatbelt.Commands.Windows +{ + internal class AMSIProviderCommand : CommandBase + { + public override string Command => "AMSIProviders"; + public override string Description => "Providers registered for AMSI"; + public override CommandGroup[] Group => new[] { CommandGroup.System, CommandGroup.Remote }; + public override bool SupportRemote => true; + public Runtime ThisRunTime; + + public AMSIProviderCommand(Runtime runtime) : base(runtime) + { + ThisRunTime = runtime; + } + + public override IEnumerable Execute(string[] args) + { + var providers = ThisRunTime.GetSubkeyNames(RegistryHive.LocalMachine, @"SOFTWARE\Microsoft\AMSI\Providers"); + foreach (var provider in providers) + { + var ProviderPath = ThisRunTime.GetStringValue(RegistryHive.LocalMachine, $"SOFTWARE\\Classes\\CLSID\\{provider}\\InprocServer32", ""); + + yield return new AMSIProviderDTO() + { + GUID = provider, + ProviderPath = ProviderPath + }; + } + } + + internal class AMSIProviderDTO : CommandDTOBase + { + public string GUID { get; set; } + public string? ProviderPath { get; set; } + } + } +} +#nullable enable \ No newline at end of file diff --git a/Seatbelt/Commands/Windows/ARPTableCommand.cs b/Seatbelt/Commands/Windows/ARPTableCommand.cs new file mode 100644 index 0000000..79fa96d --- /dev/null +++ b/Seatbelt/Commands/Windows/ARPTableCommand.cs @@ -0,0 +1,231 @@ +#nullable disable +using Seatbelt.Interop; +using static Seatbelt.Interop.Iphlpapi; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Net.NetworkInformation; +using System.Net; +using System.Text.RegularExpressions; +using Seatbelt.Output.TextWriters; +using Seatbelt.Output.Formatters; + + +namespace Seatbelt.Commands.Windows +{ + class ARPEntry + { + public ARPEntry(string ipAddress, string physicalAddress, string entryType) + { + IPAddress = ipAddress; + PhysicalAddress = physicalAddress; + EntryType = entryType; + } + public string IPAddress { get; set; } + public string PhysicalAddress { get; set; } + public string EntryType { get; set; } + } + + internal class ARPTableCommand : CommandBase + { + public override string Command => "ARPTable"; + public override string Description => "Lists the current ARP table and adapter information (equivalent to arp -a)"; + public override CommandGroup[] Group => new[] {CommandGroup.System}; + public override bool SupportRemote => false; + + public ARPTableCommand(Runtime runtime) : base(runtime) + { + } + + public override IEnumerable Execute(string[] args) + { + // adapted from Fred's code at https://social.technet.microsoft.com/Forums/lync/en-US/e949b8d6-17ad-4afc-88cd-0019a3ac9df9/powershell-alternative-to-arp-a?forum=ITCG + + var adapters = new SortedDictionary(); + + // build a mapping of index -> interface information + foreach (var ni in NetworkInterface.GetAllNetworkInterfaces()) + { + var adapter = new ARPTableDTO(); + + var adapterProperties = ni?.GetIPProperties(); + if (adapterProperties == null) + { + continue; + } + var dnsServerList = new List(); + var dnsServerCollection = adapterProperties.DnsAddresses; + + if (dnsServerCollection.Count > 0) + { + foreach (var dns in dnsServerCollection) + { + dnsServerList.Add(dns.ToString()); + } + } + + adapter.DNSServers = dnsServerList; + + try + { + var p = adapterProperties.GetIPv4Properties(); + if (p == null) + { + continue; + } + + var ips = new List(); + + foreach (var info in adapterProperties.UnicastAddresses) + { + if (Regex.IsMatch(info.Address.ToString(), @"^(\d+)\.(\d+)\.(\d+)\.(\d+)$")) + { + // grab all the IPv4 addresses + ips.Add(info.Address.ToString()); + } + } + + adapter.InterfaceIPs = ips; + adapter.InterfaceName = ni.Name; + adapter.InterfaceIndex = p.Index; + + adapters.Add(p.Index, adapter); + } + catch + { + // ignored + } + } + + var bytesNeeded = 0; + + var result = GetIpNetTable(IntPtr.Zero, ref bytesNeeded, false); + + // call the function, expecting an insufficient buffer. + if (result != Win32Error.InsufficientBuffer) + { + WriteError($"GetIpNetTable: Expected insufficent buffer but got {result}"); + } + + // allocate sufficient memory for the result structure + var buffer = Marshal.AllocCoTaskMem(bytesNeeded); + + result = GetIpNetTable(buffer, ref bytesNeeded, false); + + if (result != 0) + { + WriteError($"GetIpNetTable: {result}"); + } + + // now we have the buffer, we have to marshal it. We can read the first 4 bytes to get the length of the buffer + var entries = Marshal.ReadInt32(buffer); + + // increment the memory pointer by the size of the int + var currentBuffer = new IntPtr(buffer.ToInt64() + Marshal.SizeOf(typeof(int))); + + // allocate a list of entries + var arpEntries = new List(); + + // cycle through the entries + for (var index = 0; index < entries; index++) + { + arpEntries.Add((MIB_IPNETROW)Marshal.PtrToStructure(new IntPtr(currentBuffer.ToInt64() + (index * Marshal.SizeOf(typeof(MIB_IPNETROW)))), typeof(MIB_IPNETROW))); + } + + // sort the list by interface index + var sortedArpEntries = arpEntries.OrderBy(o => o.dwIndex).ToList(); + var currentIndexAdapter = -1; + + foreach (var arpEntry in sortedArpEntries) + { + var indexAdapter = arpEntry.dwIndex; + + if (currentIndexAdapter != indexAdapter) + { + if (!adapters.ContainsKey(indexAdapter)) + { + adapters[indexAdapter] = new ARPTableDTO(); + adapters[indexAdapter].InterfaceIndex = indexAdapter; + adapters[indexAdapter].InterfaceName = "n/a"; + } + + currentIndexAdapter = indexAdapter; + } + + var ipAddress = new IPAddress(BitConverter.GetBytes(arpEntry.dwAddr)); + var macBytes = new byte[] { arpEntry.mac0, arpEntry.mac1, arpEntry.mac2, arpEntry.mac3, arpEntry.mac4, arpEntry.mac5 }; + var physicalAddress = BitConverter.ToString(macBytes); + var entryType = (ArpEntryType)arpEntry.dwType; + + if (adapters[indexAdapter].Entries == null) + { + adapters[indexAdapter].Entries = new List(); + } + + var entry = new ARPEntry( + ipAddress.ToString(), + physicalAddress, + entryType.ToString() + ); + + adapters[indexAdapter].Entries.Add(entry); + } + + FreeMibTable(buffer); + + foreach (var adapter in adapters) + { + yield return adapter.Value; + } + } + } + + internal class ARPTableDTO : CommandDTOBase + { + public string InterfaceName { get; set; } + + public int InterfaceIndex { get; set; } + + public List InterfaceIPs { get; set; } + + public List DNSServers { get; set; } + + public List Entries { get; set; } + } + + [CommandOutputType(typeof(ARPTableDTO))] + internal class ARPTableTextFormatter : TextFormatterBase + { + public ARPTableTextFormatter(ITextWriter writer) : base(writer) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + var dto = (ARPTableDTO)result; + + if (dto.InterfaceIPs != null) + { + WriteLine("\n\n Interface : {0} ({1}) --- Index {2}", dto.InterfaceName, string.Join(",", (string[])dto.InterfaceIPs.ToArray()), dto.InterfaceIndex); + } + else + { + WriteLine("\n\n Interface : {0} ({1}) --- Index {2}", dto.InterfaceName, "n/a", dto.InterfaceIndex); + } + + if (dto.DNSServers != null) + { + WriteLine(" DNS Servers : {0}\n", string.Join(",", (string[])dto.DNSServers.ToArray())); + } + + WriteLine(" Internet Address Physical Address Type"); + + foreach (var entry in dto.Entries) + { + WriteLine($" {entry.IPAddress,-22}{entry.PhysicalAddress,-22}{entry.EntryType}"); + } + } + } +} +#nullable enable \ No newline at end of file diff --git a/Seatbelt/Commands/Windows/AntiVirusCommand.cs b/Seatbelt/Commands/Windows/AntiVirusCommand.cs new file mode 100644 index 0000000..65c976e --- /dev/null +++ b/Seatbelt/Commands/Windows/AntiVirusCommand.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections.Generic; +using Seatbelt.Interop; + +namespace Seatbelt.Commands.Windows +{ + internal class AntiVirusCommand : CommandBase + { + public override string Command => "AntiVirus"; + public override string Description => "Registered antivirus (via WMI)"; + public override CommandGroup[] Group => new[] { CommandGroup.System, CommandGroup.Remote }; + public override bool SupportRemote => true; + public Runtime ThisRunTime; + + public AntiVirusCommand(Runtime runtime) : base(runtime) + { + ThisRunTime = runtime; + } + + public override IEnumerable Execute(string[] args) + { + if (String.IsNullOrEmpty(ThisRunTime.ComputerName) && Shlwapi.IsWindowsServer()) + { + WriteHost("Cannot enumerate antivirus. root\\SecurityCenter2 WMI namespace is not available on Windows Servers"); + yield break; + } + + // lists installed VA products via WMI (the AntiVirusProduct class) + var wmiData = ThisRunTime.GetManagementObjectSearcher(@"root\SecurityCenter2", "SELECT * FROM AntiVirusProduct"); + var data = wmiData.Get(); + foreach (var virusChecker in data) + { + yield return new AntiVirusDTO( + virusChecker["displayName"], + virusChecker["pathToSignedProductExe"], + virusChecker["pathToSignedReportingExe"] + ); + } + } + } + + internal class AntiVirusDTO : CommandDTOBase + { + public AntiVirusDTO(object engine, object productExe, object reportingExe) + { + Engine = engine; + ProductEXE = productExe; + ReportingEXE = reportingExe; + } + public object Engine { get; } + public object ProductEXE { get; } + public object ReportingEXE { get; } + } +} diff --git a/Seatbelt/Commands/Windows/AppLockerCommand.cs b/Seatbelt/Commands/Windows/AppLockerCommand.cs new file mode 100644 index 0000000..c70aace --- /dev/null +++ b/Seatbelt/Commands/Windows/AppLockerCommand.cs @@ -0,0 +1,146 @@ +#nullable disable +using Microsoft.Win32; +using Seatbelt.Util; +using System.Collections.Generic; +using System.Management; +using Seatbelt.Output.TextWriters; +using Seatbelt.Output.Formatters; + +namespace Seatbelt.Commands.Windows +{ + internal class AppLockerCommand : CommandBase + { + public override string Command => "AppLocker"; + public override string Description => "AppLocker settings, if installed"; + public override CommandGroup[] Group => new[] {CommandGroup.System}; + public override bool SupportRemote => false; // TODO: impement remote + + public AppLockerCommand(Runtime runtime) : base(runtime) + { + } + + public override IEnumerable Execute(string[] args) + { + // ref - @_RastaMouse https://rastamouse.me/2018/09/enumerating-applocker-config/ + var wmiData = new ManagementObjectSearcher(@"root\cimv2", "SELECT Name, State FROM win32_service WHERE Name = 'AppIDSvc'"); + var data = wmiData.Get(); + string appIdSvcState = "Service not found"; + + var rules = new List(); + + foreach (var o in data) + { + var result = (ManagementObject)o; + appIdSvcState = result["State"].ToString(); + } + + var keys = RegistryUtil.GetSubkeyNames(RegistryHive.LocalMachine, "Software\\Policies\\Microsoft\\Windows\\SrpV2"); + + if (keys != null && keys.Length != 0) + { + foreach (var key in keys) + { + var keyName = key; + var enforcementMode = RegistryUtil.GetDwordValue(RegistryHive.LocalMachine, $"Software\\Policies\\Microsoft\\Windows\\SrpV2\\{key}", "EnforcementMode"); + var enforcementModeStr = enforcementMode switch + { + null => "not configured", + 0 => "Audit Mode", + 1 => "Enforce Mode", + _ => $"Unknown value {enforcementMode}" + }; + + var ids = RegistryUtil.GetSubkeyNames(RegistryHive.LocalMachine, "Software\\Policies\\Microsoft\\Windows\\SrpV2\\" + key); + + foreach (var id in ids) + { + var rule = RegistryUtil.GetStringValue(RegistryHive.LocalMachine, $"Software\\Policies\\Microsoft\\Windows\\SrpV2\\{key}\\{id}", "Value"); + rules.Add(rule); + } + + yield return new AppLockerDTO( + configured: true, + appIdSvcState, + keyName, + enforcementModeStr, + rules + ); + } + } + else + { + yield return new AppLockerDTO( + configured: false, + appIdSvcState, + keyName: null, + enforcementMode: null, + rules: null + ); + } + } + + internal class AppLockerDTO : CommandDTOBase + { + public AppLockerDTO(bool configured, string appIdSvcState, string? keyName, string? enforcementMode, List? rules) + { + Configured = configured; + AppIdSvcState = appIdSvcState; + KeyName = keyName; + EnforcementMode = enforcementMode; + Rules = rules; + } + public bool Configured { get; } + + public string AppIdSvcState { get; } + + public string? KeyName { get; } + + public string? EnforcementMode { get; } + + public List? Rules { get; } + } + + [CommandOutputType(typeof(AppLockerDTO))] + internal class AppLockerTextFormatter : TextFormatterBase + { + public AppLockerTextFormatter(ITextWriter writer) : base(writer) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + var dto = (AppLockerDTO)result; + + WriteLine(" [*] AppIDSvc service is {0}\n", dto.AppIdSvcState); + if (dto.AppIdSvcState != "Running") + WriteLine(" [*] Applocker is not running because the AppIDSvc is not running\n"); + + if (!dto.Configured) + { + WriteLine(" [*] AppLocker not configured"); + } + else if (dto.EnforcementMode.Equals("not configured")) + { + WriteLine(" [*] {0} not configured", dto.KeyName); + } + else + { + WriteLine("\n [*] {0} is in {1}", dto.KeyName, dto.EnforcementMode); + + if (dto.Rules.Count == 0) + { + WriteLine(" [*] No rules"); + } + else + { + foreach (var rule in dto.Rules) + { + WriteLine(" [*] {0}", rule); + } + } + } + } + } + } +} +#nullable enable \ No newline at end of file diff --git a/Seatbelt/Commands/Windows/AuditPoliciesCommand.cs b/Seatbelt/Commands/Windows/AuditPoliciesCommand.cs new file mode 100644 index 0000000..c9e0308 --- /dev/null +++ b/Seatbelt/Commands/Windows/AuditPoliciesCommand.cs @@ -0,0 +1,279 @@ +#nullable disable +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text.RegularExpressions; +using Seatbelt.Output.Formatters; +using Seatbelt.Output.TextWriters; +using Seatbelt.Util; + + +namespace Seatbelt.Commands +{ + enum AuditType + { + Success = 1, + Failure = 2, + SuccessAndFailure = 3 + } + + class AuditEntry + { + public AuditEntry(string target, string subcategory, string subcategoryGuid, AuditType auditType) + { + Target = target; + Subcategory = subcategory; + SubcategoryGUID = subcategoryGuid; + AuditType = auditType; + } + public string Target { get; } + public string Subcategory { get; } + public string SubcategoryGUID { get; } + public AuditType AuditType { get; } + } + + internal class AuditPoliciesCommand : CommandBase + { + public override string Command => "AuditPolicies"; + public override string Description => "Enumerates classic and advanced audit policy settings"; + public override CommandGroup[] Group => new[] {CommandGroup.System}; + public override bool SupportRemote => false; + + // reference - https://github.com/trustedsec/HoneyBadger/blob/master/modules/post/windows/gather/ts_get_policyinfo.rb + + public AuditPoliciesCommand(Runtime runtime) : base(runtime) + { + } + + public override IEnumerable Execute(string[] args) + { + var searchPath = $"{Environment.GetEnvironmentVariable("SystemRoot")}\\System32\\GroupPolicy\\DataStore\\0\\sysvol\\"; + var sysnativeSearchPath = $"{Environment.GetEnvironmentVariable("SystemRoot")}\\Sysnative\\GroupPolicy\\DataStore\\0\\sysvol\\"; + var files = FindFiles(searchPath, "audit.csv"); + // var sysnativeFiles = FindFiles(sysnativeSearchPath, "audit.csv"); // TODO: Need to implement parsing of this + var classicFiles = FindFiles(searchPath, "GptTmpl.inf"); + + foreach (var classicFilePath in classicFiles) + { + var result = ParseGPOPath(classicFilePath); + var domain = result[0]; + var gpo = result[1]; + + //ParseClassicPolicy + var sections = IniFileHelper.ReadSections(classicFilePath); + + if (!sections.Contains("Event Audit")) + continue; + + var settings = ParseClassicPolicy(classicFilePath); + + yield return new AuditPolicyGPO( + classicFilePath, + domain, + gpo, + "classic", + settings + ); + } + + foreach (var filePath in files) + { + var result = ParseGPOPath(filePath); + var domain = result[0]; + var gpo = result[1]; + + var settings = ParseAdvancedPolicy(filePath); + + yield return new AuditPolicyGPO( + filePath, + domain, + gpo, + "advanced", + settings + ); + } + } + + public string[] ParseGPOPath(string path) + { + // returns an array of the domain and GPO GUID from an audit.csv (or GptTmpl.inf) path + + var searchPath = $"{Environment.GetEnvironmentVariable("SystemRoot")}\\System32\\GroupPolicy\\DataStore\\0\\sysvol\\"; + var sysnativeSearchPath = $"{Environment.GetEnvironmentVariable("SystemRoot")}\\Sysnative\\GroupPolicy\\DataStore\\0\\sysvol\\"; + + if (Regex.IsMatch(path, "System32")) + { + var rest = path.Substring(searchPath.Length, path.Length - searchPath.Length); + var parts = rest.Split('\\'); + string[] result = { parts[0], parts[2] }; + return result; + } + else + { + var rest = path.Substring(sysnativeSearchPath.Length, path.Length - sysnativeSearchPath.Length); + var parts = rest.Split('\\'); + string[] result = { parts[0], parts[2] }; + return result; + } + } + + public List ParseClassicPolicy(string path) + { + // parses a "classic" auditing policy (GptTmpl.inf), returning a list of AuditEntries + + var results = new List(); + + var settings = IniFileHelper.ReadKeyValuePairs("Event Audit", path); + foreach (var setting in settings) + { + var parts = setting.Split('='); + + var result = new AuditEntry( + "", + parts[0], + "", + (AuditType)Int32.Parse(parts[1]) + ); + + results.Add(result); + } + + return results; + } + + public List ParseAdvancedPolicy(string path) + { + // parses a "advanced" auditing policy (audit.csv), returning a list of AuditEntries + + var results = new List(); + + using (var reader = new StreamReader(path)) + { + while (!reader.EndOfStream) + { + var line = reader.ReadLine(); + var values = line.Split(','); + + if (values[0].Equals("Machine Name")) // skip the header + continue; + + // CSV lines: + // Machine Name,Policy Target,Subcategory,Subcategory GUID,Inclusion Setting,Exclusion Setting,Setting Value + + string + target = values[1], + subcategory = values[2], + subcategoryGuid = values[3]; + var auditType = (AuditType)int.Parse(values[6]); + + results.Add(new AuditEntry( + target, + subcategory, + subcategoryGuid, + auditType + )); + } + } + + return results; + } + + public static List FindFiles(string path, string pattern) + { + // finds files matching one or more patterns under a given path, recursive + // adapted from http://csharphelper.com/blog/2015/06/find-files-that-match-multiple-patterns-in-c/ + // pattern: "*pass*;*.png;" + + var files = new List(); + try + { + var filesUnfiltered = GetFiles(path).ToList(); + + files.AddRange(filesUnfiltered.Where(f => f.Contains(pattern.Trim('*')))); + } + catch (UnauthorizedAccessException) { } + catch (PathTooLongException) { } + + return files; + } + + // FROM: https://stackoverflow.com/a/929418 + private static IEnumerable GetFiles(string path) + { + var queue = new Queue(); + queue.Enqueue(path); + while (queue.Count > 0) + { + path = queue.Dequeue(); + try + { + foreach (var subDir in Directory.GetDirectories(path)) + { + queue.Enqueue(subDir); + } + } + catch (Exception) + { + // Eat it + } + string[] files = null; + try + { + files = Directory.GetFiles(path); + } + catch (Exception) + { + // Eat it + } + + if (files == null) continue; + foreach (var f in files) + { + yield return f; + } + } + } + + internal class AuditPolicyGPO : CommandDTOBase + { + public AuditPolicyGPO(string path, string domain, string gpo, string type, List settings) + { + Path = path; + Domain = domain; + GPO = gpo; + Type = type; + Settings = settings; + } + public string Path { get; } + public string Domain { get; } + public string GPO { get; } + public string Type { get; } + public List Settings { get; } + } + + [CommandOutputType(typeof(AuditPolicyGPO))] + internal class AuditPolicyormatter : TextFormatterBase + { + public AuditPolicyormatter(ITextWriter writer) : base(writer) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + var dto = (AuditPolicyGPO)result; + + //WriteLine(" {0,-40} : {1}", "File", dto.Path); + WriteLine(" {0,-40} : {1}", "Domain", dto.Domain); + WriteLine(" {0,-40} : {1}", "GPO", dto.GPO); + WriteLine(" {0,-40} : {1}", "Type", dto.Type); + foreach (var entry in dto.Settings) + { + WriteLine(" {0,40} : {1}", entry.Subcategory, entry.AuditType); + } + WriteLine(); + } + } + } +} +#nullable enable \ No newline at end of file diff --git a/Seatbelt/Commands/Windows/AuditPolicyRegistryCommand.cs b/Seatbelt/Commands/Windows/AuditPolicyRegistryCommand.cs new file mode 100644 index 0000000..1b13456 --- /dev/null +++ b/Seatbelt/Commands/Windows/AuditPolicyRegistryCommand.cs @@ -0,0 +1,78 @@ +using Microsoft.Win32; +using System.Collections.Generic; +using Seatbelt.Output.TextWriters; +using Seatbelt.Output.Formatters; + + +namespace Seatbelt.Commands.Windows +{ + // TODO: If elevated, pull with Windows Audit Policy/Advanced Audit policy + internal class AuditPolicyRegistryCommand : CommandBase + { + public override string Command => "AuditPolicyRegistry"; + public override string Description => "Audit settings via the registry"; + public override CommandGroup[] Group => new[] {CommandGroup.System}; + public override bool SupportRemote => true; + public Runtime ThisRunTime; + + public AuditPolicyRegistryCommand(Runtime runtime) : base(runtime) + { + ThisRunTime = runtime; + } + + public override IEnumerable Execute(string[] args) + { + // TODO: Expand the audit policy enumeration + var settings = ThisRunTime.GetValues(RegistryHive.LocalMachine, "Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\System\\Audit"); + + if (settings == null) + yield break; + + foreach (var kvp in settings) + { + if (kvp.Value.GetType().IsArray && (kvp.Value.GetType().GetElementType()?.ToString() == "System.String")) + { + var result = string.Join(",", (string[])kvp.Value); + yield return new AuditPolicyDTO( + kvp.Key, + result + ); + } + else + { + yield return new AuditPolicyDTO( + kvp.Key, + $"{kvp.Value}" + ); + } + } + } + + internal class AuditPolicyDTO : CommandDTOBase + { + public AuditPolicyDTO(string key, string value) + { + Key = key; + Value = value; + } + public string Key { get; } + public string Value { get; } + } + + + [CommandOutputType(typeof(AuditPolicyDTO))] + internal class AuditPolicyTextFormatter : TextFormatterBase + { + public AuditPolicyTextFormatter(ITextWriter writer) : base(writer) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + var dto = (AuditPolicyDTO)result; + + WriteLine(" {0,-30} : {1}", dto.Key, dto.Value); + } + } + } +} diff --git a/Seatbelt/Commands/Windows/AutoRunsCommand.cs b/Seatbelt/Commands/Windows/AutoRunsCommand.cs new file mode 100644 index 0000000..2e70ad9 --- /dev/null +++ b/Seatbelt/Commands/Windows/AutoRunsCommand.cs @@ -0,0 +1,85 @@ +#nullable disable +using Microsoft.Win32; +using System.Collections.Generic; +using Seatbelt.Output.TextWriters; +using Seatbelt.Output.Formatters; + + +namespace Seatbelt.Commands.Windows +{ + internal class AutoRunsCommand : CommandBase + { + public override string Command => "AutoRuns"; + public override string Description => "Auto run executables/scripts/programs"; + public override CommandGroup[] Group => new[] {CommandGroup.System}; + public override bool SupportRemote => true; + public Runtime ThisRunTime; + + public AutoRunsCommand(Runtime runtime) : base(runtime) + { + ThisRunTime = runtime; + } + + public override IEnumerable Execute(string[] args) + { + //WriteHost("Registry Autoruns"); + + string[] autorunLocations = new string[] { + "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run", + "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\RunOnce", + "SOFTWARE\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\Run", + "SOFTWARE\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\RunOnce", + "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\RunService", + "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\RunOnceService", + "SOFTWARE\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\RunService", + "SOFTWARE\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\RunOnceService" + }; + + foreach (string autorunLocation in autorunLocations) + { + var settings = ThisRunTime.GetValues(RegistryHive.LocalMachine, autorunLocation); + + if ((settings != null) && (settings.Count != 0)) + { + AutoRunDTO entry = new AutoRunDTO(); + + entry.Key = System.String.Format("HKLM:\\{0}", autorunLocation); + entry.Entries = new List(); + + foreach (KeyValuePair kvp in settings) + { + entry.Entries.Add(kvp.Value.ToString()); + } + + yield return entry; + } + } + } + + internal class AutoRunDTO : CommandDTOBase + { + public string Key { get; set; } + public List Entries { get; set; } + } + + [CommandOutputType(typeof(AutoRunDTO))] + internal class AutoRunTextFormatter : TextFormatterBase + { + public AutoRunTextFormatter(ITextWriter writer) : base(writer) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + var dto = (AutoRunDTO)result; + + WriteLine("\n {0} :", dto.Key); + foreach (string entry in dto.Entries) + { + WriteLine(" {0}", entry); + } + } + } + } +} +#nullable enable \ No newline at end of file diff --git a/Seatbelt/Commands/Windows/CredEnumCommand.cs b/Seatbelt/Commands/Windows/CredEnumCommand.cs new file mode 100644 index 0000000..d059987 --- /dev/null +++ b/Seatbelt/Commands/Windows/CredEnumCommand.cs @@ -0,0 +1,121 @@ +#nullable disable +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using Seatbelt.Output.TextWriters; +using Seatbelt.Output.Formatters; +using Seatbelt.Interop; +using System.ComponentModel; +using System.Text; + + +// this code was adapted by @djhohnstein from @peewpw's work at +// https://github.com/peewpw/Invoke-WCMDump/blob/master/Invoke-WCMDump.ps1 +// which was originally based on https://github.com/spolnik/Simple.CredentialsManager + +namespace Seatbelt.Commands.Windows +{ + internal class CredEnumCommand : CommandBase + { + public override string Command => "CredEnum"; + public override string Description => "Enumerates the current user's saved credentials using CredEnumerate()"; + public override CommandGroup[] Group => new[] { CommandGroup.User }; + public override bool SupportRemote => false; + + public CredEnumCommand(Runtime runtime) : base(runtime) + { + } + + public override IEnumerable Execute(string[] args) + { + // from https://gist.github.com/meziantou/10311113#file-credentialmanager-cs-L83-L105 + var ret = Advapi32.CredEnumerate(null, 0, out var count, out var pCredentials); + if (!ret) + { + var lastError = Marshal.GetLastWin32Error(); + throw new Win32Exception(lastError); + } + + for (var n = 0; n < count; n++) + { + var credentialPtr = Marshal.ReadIntPtr(pCredentials, n * Marshal.SizeOf(typeof(IntPtr))); + var credential = (Advapi32.CREDENTIAL)Marshal.PtrToStructure(credentialPtr, typeof(Advapi32.CREDENTIAL)); + + string password = null; + if (credential.CredentialBlob != IntPtr.Zero) + { + var passwordBytes = new byte[credential.CredentialBlobSize]; + Marshal.Copy(credential.CredentialBlob, passwordBytes, 0, credential.CredentialBlobSize); + var flags = Advapi32.IsTextUnicodeFlags.IS_TEXT_UNICODE_STATISTICS; + + if (Advapi32.IsTextUnicode(passwordBytes, passwordBytes.Length, ref flags)) + { + password = Encoding.Unicode.GetString(passwordBytes); + } + else + { + password = BitConverter.ToString(passwordBytes).Replace("-", " "); + } + } + + yield return new CredEnumDTO( + credential.TargetName, + credential.Comment, + credential.UserName, + password, + credential.Type, + credential.Persist, + DateTime.FromFileTime(credential.LastWritten) + ); + } + + Advapi32.CredFree(pCredentials); + } + + internal class CredEnumDTO : CommandDTOBase + { + public CredEnumDTO(string target, string comment, string username, string password, Advapi32.CredentialType credentialType, Advapi32.PersistenceType persistenceType, DateTime lastWriteTime) + { + Target = target; + Comment = comment; + Username = username; + Password = password; + CredentialType = credentialType; + PersistenceType = persistenceType; + LastWriteTime = lastWriteTime; + } + public string Target { get; } + public string Comment { get; } + public string Username { get; } + public string Password { get; } + public Advapi32.CredentialType CredentialType { get; } + public Advapi32.PersistenceType PersistenceType { get; } + public DateTime LastWriteTime { get; } + } + + [CommandOutputType(typeof(CredEnumDTO))] + internal class WindowsVaultFormatter : TextFormatterBase + { + public WindowsVaultFormatter(ITextWriter writer) : base(writer) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + var dto = (CredEnumDTO)result; + + WriteLine($" Target : {dto.Target}"); + if (!String.IsNullOrEmpty(dto.Comment)) + { + WriteLine($" Comment : {dto.Comment}"); + } + WriteLine($" UserName : {dto.Username}"); + WriteLine($" Password : {dto.Password}"); + WriteLine($" CredentialType : {dto.CredentialType}"); + WriteLine($" PersistenceType : {dto.PersistenceType}"); + WriteLine($" LastWriteTime : {dto.LastWriteTime}\r\n"); + } + } + } +} +#nullable enable \ No newline at end of file diff --git a/Seatbelt/Commands/Windows/CredentialGuardCommand.cs b/Seatbelt/Commands/Windows/CredentialGuardCommand.cs new file mode 100644 index 0000000..5ccc8f2 --- /dev/null +++ b/Seatbelt/Commands/Windows/CredentialGuardCommand.cs @@ -0,0 +1,116 @@ +using System.Collections.Generic; +using System.Linq; +using Seatbelt.Output.Formatters; +using Seatbelt.Output.TextWriters; +using System.Management; +using System; + +namespace Seatbelt.Commands +{ + enum VBS + { + NOT_ENABLED = 0, + ENABLED_NOT_RUNNING = 1, + ENABLED_AND_RUNNING = 2 + } + + internal class CredentialGuardCommand : CommandBase + { + public override string Command => "CredGuard"; + public override string Description => "CredentialGuard configuration"; + public override CommandGroup[] Group => new[] {CommandGroup.System}; + public override bool SupportRemote => false; // TODO remote + + + public CredentialGuardCommand(Runtime runtime) : base(runtime) + { + } + + public override IEnumerable Execute(string[] args) + { + // adapted from @chrismaddalena's PR (https://github.com/GhostPack/Seatbelt/pull/22/files) + + // settings reference - https://www.tenforums.com/tutorials/68926-verify-if-device-guard-enabled-disabled-windows-10-a.html + + ManagementObjectCollection? data = null; + try + { + var wmiData = new ManagementObjectSearcher(@"root\Microsoft\Windows\DeviceGuard", "SELECT * FROM Win32_DeviceGuard"); + data = wmiData.Get(); + } + catch (ManagementException ex) when (ex.ErrorCode == ManagementStatus.InvalidNamespace) + { + WriteError(string.Format(" [X] 'Win32_DeviceGuard' WMI class unavailable", ex.Message)); + } + catch (Exception ex) + { + WriteError(ex.ToString()); + } + + if (data == null) + { + yield break; + } + + foreach (var result in data) + { + // reference: + // https://github.com/GhostPack/Seatbelt/blob/f47c342150e70e96669017bbec258e27227ba1ef/Seatbelt/Program.cs#L1754-L1766 + + var configCheck = (int[])result.GetPropertyValue("SecurityServicesConfigured"); + var serviceCheck = (int[])result.GetPropertyValue("SecurityServicesRunning"); + + var vbsSetting = (VBS)0; + var configured = false; + var running = false; + + uint? vbs = (uint)result.GetPropertyValue("VirtualizationBasedSecurityStatus"); + if(vbs != null) + { + vbsSetting = (VBS)vbs; + } + + if(configCheck.Contains(1)) + { + configured = true; + } + + if (serviceCheck.Contains(1)) + { + running = true; + } + + yield return new CredGuardDTO() + { + VirtualizationBasedSecurityStatus = vbsSetting, + Configured = configured, + Running = running + }; + } + } + + class CredGuardDTO : CommandDTOBase + { + public VBS VirtualizationBasedSecurityStatus { get; set; } + + public bool Configured { get; set; } + + public bool Running { get; set; } + } + + [CommandOutputType(typeof(CredGuardDTO))] + internal class CredentialGuardFormatter : TextFormatterBase + { + public CredentialGuardFormatter(ITextWriter writer) : base(writer) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + var dto = (CredGuardDTO)result; + + + } + } + } +} \ No newline at end of file diff --git a/Seatbelt/Commands/Windows/DNSCacheCommand.cs b/Seatbelt/Commands/Windows/DNSCacheCommand.cs new file mode 100644 index 0000000..77d4fa5 --- /dev/null +++ b/Seatbelt/Commands/Windows/DNSCacheCommand.cs @@ -0,0 +1,68 @@ +#nullable disable +using System; +using System.Collections.Generic; +using System.Management; + + +namespace Seatbelt.Commands.Windows +{ + internal class DNSCacheCommand : CommandBase + { + public override string Command => "DNSCache"; + public override string Description => "DNS cache entries (via WMI)"; + public override CommandGroup[] Group => new[] {CommandGroup.System}; + public override bool SupportRemote => true; + public Runtime ThisRunTime; + + public DNSCacheCommand(Runtime runtime) : base(runtime) + { + ThisRunTime = runtime; + } + + public override IEnumerable Execute(string[] args) + { + ManagementObjectCollection data = null; + + // lists the local DNS cache via WMI (MSFT_DNSClientCache class) + try + { + var wmiData = ThisRunTime.GetManagementObjectSearcher(@"root\standardcimv2", "SELECT * FROM MSFT_DNSClientCache"); + data = wmiData.Get(); + } + catch (ManagementException ex) when (ex.ErrorCode == ManagementStatus.InvalidNamespace) + { + WriteError(string.Format(" [X] 'MSFT_DNSClientCache' WMI class unavailable (minimum supported versions of Windows: 8/2012)", ex.Message)); + } + catch (Exception ex) + { + WriteError(ex.ToString()); + } + + if (data == null) + { + yield break; + } + + foreach (var o in data) + { + var result = (ManagementObject) o; + yield return new DNSCacheDTO() + { + Entry = result["Entry"], + Name = result["Name"], + Data = result["Data"] + }; + } + + data.Dispose(); + } + } + + internal class DNSCacheDTO : CommandDTOBase + { + public object Entry { get; set; } + public object Name { get; set; } + public object Data { get; set; } + } +} +#nullable enable \ No newline at end of file diff --git a/Seatbelt/Commands/Windows/DotNetCommand.cs b/Seatbelt/Commands/Windows/DotNetCommand.cs new file mode 100644 index 0000000..38a438c --- /dev/null +++ b/Seatbelt/Commands/Windows/DotNetCommand.cs @@ -0,0 +1,100 @@ +using Microsoft.Win32; +using Seatbelt.Output.Formatters; +using Seatbelt.Output.TextWriters; +using System; +using System.Collections.Generic; +using System.Linq; + + +namespace Seatbelt.Commands.Windows +{ + internal class DotNetCommand : CommandBase + { + public override string Command => "DotNet"; + public override string Description => "DotNet versions"; + public override CommandGroup[] Group => new[] {CommandGroup.System, CommandGroup.Remote}; + public override bool SupportRemote => true; + public Runtime ThisRunTime; + + public DotNetCommand(Runtime runtime) : base(runtime) + { + ThisRunTime = runtime; + } + + public override IEnumerable Execute(string[] args) + { + var installedVersions = new List(); +#nullable disable + var dotNet35Version = ThisRunTime.GetStringValue(RegistryHive.LocalMachine, @"SOFTWARE\Microsoft\NET Framework Setup\NDP\v3.5", "Version"); + if (!string.IsNullOrEmpty(dotNet35Version)) + { + installedVersions.Add(dotNet35Version); + } + + var dotNet4Version = ThisRunTime.GetStringValue(RegistryHive.LocalMachine, @"SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full", "Version"); + if (!string.IsNullOrEmpty(dotNet4Version)) + { + installedVersions.Add(dotNet4Version); + } +#nullable restore + yield return new DotNetDTO( + installedVersions.ToArray(), + Environment.OSVersion.Version.Major >= 10 + ); + } + } + + class DotNetDTO : CommandDTOBase + { + public DotNetDTO(string[] installedVersions, bool osSupportsAmsi) + { + InstalledVersions = installedVersions; + OsSupportsAmsi = osSupportsAmsi; + } + public string[] InstalledVersions { get; } + public bool OsSupportsAmsi { get; } + } + + [CommandOutputType(typeof(DotNetDTO))] + internal class DotNetTextFormatter : TextFormatterBase + { + public DotNetTextFormatter(ITextWriter writer) : base(writer) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + var dto = (DotNetDTO)result; + var lowestVersion = dto.InstalledVersions.Min(v => (new Version(v))); + var highestVersion = dto.InstalledVersions.Max(v => (new Version(v))); + bool dotNetSupportsAMSI = ((highestVersion.Major >= 4) && (highestVersion.Minor >= 8)); + + WriteLine(" Installed .NET Versions"); + foreach (var v in dto.InstalledVersions) + { + WriteLine(" " + v); + } + + WriteLine("\n Anti-Malware Scan Interface (AMSI)"); + WriteLine($" OS supports AMSI : {dto.OsSupportsAmsi}"); + WriteLine($" .NET version support AMSI : {dotNetSupportsAMSI}"); + + if((highestVersion.Major == 4) && (highestVersion.Minor >= 8)) + { + WriteLine($" [!] The highest .NET version is enrolled in AMSI!"); + } + + if ( + dto.OsSupportsAmsi && + dotNetSupportsAMSI && + (( + (lowestVersion.Major == 3) + ) || + ((lowestVersion.Major == 4) && (lowestVersion.Minor < 8))) + ) + { + WriteLine($" [*] You can invoke .NET version {lowestVersion.Major}.{lowestVersion.Minor} to bypass AMSI."); + } + } + } +} diff --git a/Seatbelt/Commands/Windows/DpapiMasterKeysCommand.cs b/Seatbelt/Commands/Windows/DpapiMasterKeysCommand.cs new file mode 100644 index 0000000..a04d589 --- /dev/null +++ b/Seatbelt/Commands/Windows/DpapiMasterKeysCommand.cs @@ -0,0 +1,116 @@ +#nullable disable +using System; +using System.Collections.Generic; +using System.IO; +using System.Text.RegularExpressions; +using Seatbelt.Output.TextWriters; +using Seatbelt.Output.Formatters; + + +namespace Seatbelt.Commands.Windows +{ + class MasterKey + { + public string FileName { get; set; } + + public DateTime LastAccessed { get; set; } + + public DateTime LastModified { get; set; } + } + + internal class DpapiMasterKeysCommand : CommandBase + { + public override string Command => "DpapiMasterKeys"; + public override string Description => "List DPAPI master keys"; + public override CommandGroup[] Group => new[] { CommandGroup.User }; + public override bool SupportRemote => false; + + + public DpapiMasterKeysCommand(Runtime runtime) : base(runtime) + { + } + + public override IEnumerable Execute(string[] args) + { + var userFolder = $"{Environment.GetEnvironmentVariable("SystemDrive")}\\Users\\"; + var dirs = Directory.GetDirectories(userFolder); + + foreach (var dir in dirs) + { + if (dir.EndsWith("Public") || dir.EndsWith("Default") || dir.EndsWith("Default User") || dir.EndsWith("All Users")) + { + continue; + } + + var userDpapiBasePath = $"{dir}\\AppData\\Roaming\\Microsoft\\Protect\\"; + if (!Directory.Exists(userDpapiBasePath)) + { + continue; + } + + var directories = Directory.GetDirectories(userDpapiBasePath); + foreach (var directory in directories) + { + var files = Directory.GetFiles(directory); + + var MasterKeys = new List(); + + foreach (var file in files) + { + if (!Regex.IsMatch(file, @"[0-9A-Fa-f]{8}[-][0-9A-Fa-f]{4}[-][0-9A-Fa-f]{4}[-][0-9A-Fa-f]{4}[-][0-9A-Fa-f]{12}")) + { + continue; + } + + var masterKey = new MasterKey(); + masterKey.FileName = Path.GetFileName(file); + masterKey.LastAccessed = File.GetLastAccessTime(file); + masterKey.LastModified = File.GetLastAccessTime(file); + MasterKeys.Add(masterKey); + } + + yield return new DpapiMasterKeysDTO() + { + Folder = $"{directory}", + MasterKeys = MasterKeys + }; + } + } + + WriteHost("\n [*] Use the Mimikatz \"dpapi::masterkey\" module with appropriate arguments (/pvk or /rpc) to decrypt"); + WriteHost(" [*] You can also extract many DPAPI masterkeys from memory with the Mimikatz \"sekurlsa::dpapi\" module"); + WriteHost(" [*] You can also use SharpDPAPI for masterkey retrieval."); + } + + internal class DpapiMasterKeysDTO : CommandDTOBase + { + public string Folder { get; set; } + public List MasterKeys { get; set; } + } + + [CommandOutputType(typeof(DpapiMasterKeysDTO))] + internal class DpapiMasterKeysFormatter : TextFormatterBase + { + public DpapiMasterKeysFormatter(ITextWriter writer) : base(writer) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + var dto = (DpapiMasterKeysDTO)result; + + WriteLine(" Folder : {0}\n", dto.Folder); + WriteLine($" LastAccessed LastModified FileName"); + WriteLine($" ------------ ------------ --------"); + + foreach(var masterkey in dto.MasterKeys) + { + WriteLine(" {0,-22} {1,-22} {2}", masterkey.LastAccessed, masterkey.LastModified, masterkey.FileName); + } + + WriteLine(); + } + } + } +} +#nullable enable \ No newline at end of file diff --git a/Seatbelt/Commands/Windows/EnvironmentPathCommand.cs b/Seatbelt/Commands/Windows/EnvironmentPathCommand.cs new file mode 100644 index 0000000..0dc666c --- /dev/null +++ b/Seatbelt/Commands/Windows/EnvironmentPathCommand.cs @@ -0,0 +1,57 @@ +#nullable disable +using System; +using System.Security.AccessControl; +using System.Collections.Generic; +using System.IO; + +namespace Seatbelt.Commands.Windows +{ + internal class EnvironmentPathCommand : CommandBase + { + public override string Command => "EnvironmentPath"; + public override string Description => "Current environment %PATH$ folders and SDDL information"; + public override CommandGroup[] Group => new[] {CommandGroup.System}; + public override bool SupportRemote => false; + + public EnvironmentPathCommand(Runtime runtime) : base(runtime) + { + } + + public override IEnumerable Execute(string[] args) + { + var pathString = Environment.GetEnvironmentVariable("Path"); + var paths = pathString.Split(';'); + + foreach(var path in paths) + { + var SDDL = ""; + + if(!String.IsNullOrEmpty(path.Trim())) + { + try + { + var security = Directory.GetAccessControl(path, AccessControlSections.Owner | AccessControlSections.Access); + SDDL = security.GetSecurityDescriptorSddlForm(AccessControlSections.Owner | AccessControlSections.Access); + } + catch + { + // eat it + } + + yield return new EnvironmentPathDTO() + { + Name = path.Trim(), + SDDL = SDDL + }; + } + } + } + + internal class EnvironmentPathDTO : CommandDTOBase + { + public string Name { get; set; } + public string SDDL { get; set; } + } + } +} +#nullable enable \ No newline at end of file diff --git a/Seatbelt/Commands/Windows/EnvironmentVariableCommand.cs b/Seatbelt/Commands/Windows/EnvironmentVariableCommand.cs new file mode 100644 index 0000000..3756f4b --- /dev/null +++ b/Seatbelt/Commands/Windows/EnvironmentVariableCommand.cs @@ -0,0 +1,129 @@ +#nullable disable +using Microsoft.Win32; +using Seatbelt.Util; +using System; +using System.Collections.Generic; +using Seatbelt.Output.TextWriters; +using Seatbelt.Output.Formatters; + + +namespace Seatbelt.Commands.Windows +{ + internal class EnvironmentVariableCommand : CommandBase + { + public override string Command => "EnvironmentVariables"; + public override string Description => "Current user environment variables"; + public override CommandGroup[] Group => new[] {CommandGroup.System}; + public override bool SupportRemote => false; + + class PathLocation + { + public string Type { get; set; } + public RegistryHive Hive { get; set; } + public string Path { get; set; } + } + + public EnvironmentVariableCommand(Runtime runtime) : base(runtime) + { + } + + public override IEnumerable Execute(string[] args) + { + var currentProcessVars = new SortedDictionary(); + + try + { + // dumps out current user environment variables + foreach (System.Collections.DictionaryEntry env in Environment.GetEnvironmentVariables()) + { + var name = (string)env.Key; + var value = (string)env.Value; + currentProcessVars[name] = value; + } + } + catch (Exception ex) + { + WriteError(ex.Message); + } + + foreach (var variable in currentProcessVars) + { + yield return new EnvironmentVariableDTO() + { + Type = "CurrentProcess", + Name = variable.Key, + Value = variable.Value + }; + } + + PathLocation[] locations = + { + new PathLocation() { + Type = "System", + Hive = RegistryHive.LocalMachine, + Path = "SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment" + }, + new PathLocation() { + Type = "User", + Hive = RegistryHive.CurrentUser, + Path = "Environment" + }, + new PathLocation() { + Type = "Volatile", + Hive = RegistryHive.CurrentUser, + Path = "Volatile Environment" + }, + new PathLocation() { + Type = "Volatile1", + Hive = RegistryHive.CurrentUser, + Path = "Volatile Environment\\1" + } + }; + + foreach (var location in locations) + { + var SystemVariables = new SortedDictionary(); + var settings = RegistryUtil.GetValues(location.Hive, location.Path); + + foreach (var kvp in settings) + { + SystemVariables[kvp.Key] = kvp.Value.ToString(); + } + + foreach (var variable in SystemVariables) + { + yield return new EnvironmentVariableDTO() + { + Type = location.Type, + Name = variable.Key, + Value = variable.Value + }; + } + } + } + + internal class EnvironmentVariableDTO : CommandDTOBase + { + public string Type { get; set; } + public string Name { get; set; } + public string Value { get; set; } + } + + + [CommandOutputType(typeof(EnvironmentVariableDTO))] + internal class EnvironmentVariableFormatter : TextFormatterBase + { + public EnvironmentVariableFormatter(ITextWriter writer) : base(writer) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + var dto = (EnvironmentVariableDTO)result; + + WriteLine(" {0,-15}{1,-35}{2}", dto.Type, dto.Name, dto.Value); + } + } + } +} +#nullable enable \ No newline at end of file diff --git a/Seatbelt/Commands/Windows/EventLogs/ExplicitLogonEvents/ExplicitLogonEventsCommand.cs b/Seatbelt/Commands/Windows/EventLogs/ExplicitLogonEvents/ExplicitLogonEventsCommand.cs new file mode 100644 index 0000000..940caa6 --- /dev/null +++ b/Seatbelt/Commands/Windows/EventLogs/ExplicitLogonEvents/ExplicitLogonEventsCommand.cs @@ -0,0 +1,115 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.Eventing.Reader; +using System.Text.RegularExpressions; +using Seatbelt.Util; + +namespace Seatbelt.Commands.Windows.EventLogs.ExplicitLogonEvents +{ + internal class ExplicitLogonEventsCommand : CommandBase + { + public override string Description => "Explicit Logon events (Event ID 4648) from the security event log. Default of 7 days, argument == last X days."; + public override CommandGroup[] Group => new[] { CommandGroup.Misc }; + public override string Command => "ExplicitLogonEvents"; + public override bool SupportRemote => false; // TODO remote + + public ExplicitLogonEventsCommand(Runtime runtime) : base(runtime) + { + } + + public override IEnumerable Execute(string[] args) + { + const string eventId = "4648"; + string? userFilterRegex = null; + + // grab events from the last X days - 7 for default, 30 for "-full" collection + // Always use the user-supplied value, if specified + var lastDays = 7; + if (args.Length >= 1) + { + if (!int.TryParse(args[0], out lastDays)) + { + WriteError("Argument is not an integer"); + + yield break; + } + } + else + { + if (!Runtime.FilterResults) + { + lastDays = 30; + } + } + + WriteHost("Listing 4648 Explicit Credential Events - A process logged on using plaintext credentials"); + + if (args.Length >= 2) + { + userFilterRegex = args[1]; + WriteHost($"Username Filter: {userFilterRegex}"); + } + WriteHost("Output Format:"); + WriteHost(" --- TargetUser,ProcessResults,SubjectUser,IpAddress ---"); + WriteHost(" \n\n"); + + + var startTime = DateTime.Now.AddDays(-lastDays); + var endTime = DateTime.Now; + + if (!SecurityUtil.IsHighIntegrity()) + { + WriteError("Unable to collect. Must be an administrator."); + yield break; + } + + var query = $@"*[System/EventId={eventId}] and *[System[TimeCreated[@SystemTime >= '{startTime.ToUniversalTime():o}']]] and *[System[TimeCreated[@SystemTime <= '{endTime.ToUniversalTime():o}']]]"; + + var eventsQuery = new EventLogQuery("Security", PathType.LogName, query) + { + ReverseDirection = true + }; + + var logReader = new EventLogReader(eventsQuery); + + for (var eventDetail = logReader.ReadEvent(); eventDetail != null; eventDetail = logReader.ReadEvent()) + { + //string subjectUserSid = eventDetail.Properties[0].Value.ToString(); + var subjectUserName = eventDetail.Properties[1].Value.ToString(); + var subjectDomainName = eventDetail.Properties[2].Value.ToString(); + //var subjectLogonId = eventDetail.Properties[3].Value.ToString(); + //var logonGuid = eventDetail.Properties[4].Value.ToString(); + var targetUserName = eventDetail.Properties[5].Value.ToString(); + var targetDomainName = eventDetail.Properties[6].Value.ToString(); + //var targetLogonGuid = eventDetail.Properties[7].Value.ToString(); + //var targetServerName = eventDetail.Properties[8].Value.ToString(); + //var targetInfo = eventDetail.Properties[9].Value.ToString(); + //var processId = eventDetail.Properties[10].Value.ToString(); + var processName = eventDetail.Properties[11].Value.ToString(); + var ipAddress = eventDetail.Properties[12].Value.ToString(); + //var IpPort = eventDetail.Properties[13].Value.ToString(); + + // Ignore the current machine logging on and + if (Runtime.FilterResults && Regex.IsMatch(targetUserName, Environment.MachineName) || + Regex.IsMatch(targetDomainName, @"^(Font Driver Host|Window Manager)$")) + { + continue; + } + + if (userFilterRegex != null && !Regex.IsMatch(targetUserName, userFilterRegex)) + continue; + + yield return new ExplicitLogonEventsDTO() + { + TimeCreated = eventDetail.TimeCreated, + SubjectUser = subjectUserName, + SubjectDomain = subjectDomainName, + TargetUser = targetUserName, + TargetDomain = targetDomainName, + Process = processName, + IpAddress = ipAddress + }; + } + } + } +} diff --git a/Seatbelt/Commands/Windows/EventLogs/ExplicitLogonEvents/ExplicitLogonEventsCommandDTO.cs b/Seatbelt/Commands/Windows/EventLogs/ExplicitLogonEvents/ExplicitLogonEventsCommandDTO.cs new file mode 100644 index 0000000..202cd0f --- /dev/null +++ b/Seatbelt/Commands/Windows/EventLogs/ExplicitLogonEvents/ExplicitLogonEventsCommandDTO.cs @@ -0,0 +1,17 @@ +#nullable disable +using System; + +namespace Seatbelt.Commands.Windows.EventLogs.ExplicitLogonEvents +{ + internal class ExplicitLogonEventsDTO : CommandDTOBase + { + public string SubjectUser { get; set; } + public string SubjectDomain { get; set; } + public string TargetUser { get; set; } + public string TargetDomain { get; set; } + public string Process { get; set; } + public string IpAddress { get; set; } + public DateTime? TimeCreated { get; set; } + } +} +#nullable enable diff --git a/Seatbelt/Commands/Windows/EventLogs/ExplicitLogonEvents/ExplicitLogonEventsTextFormatter.cs b/Seatbelt/Commands/Windows/EventLogs/ExplicitLogonEvents/ExplicitLogonEventsTextFormatter.cs new file mode 100644 index 0000000..d3af206 --- /dev/null +++ b/Seatbelt/Commands/Windows/EventLogs/ExplicitLogonEvents/ExplicitLogonEventsTextFormatter.cs @@ -0,0 +1,78 @@ +using Seatbelt.Output.Formatters; +using Seatbelt.Output.TextWriters; +using System.Collections.Generic; + +namespace Seatbelt.Commands.Windows.EventLogs.ExplicitLogonEvents +{ + [CommandOutputType(typeof(ExplicitLogonEventsDTO))] + internal class ExplicitLogonEventsTextFormatter : TextFormatterBase + { + private readonly Dictionary> events = new Dictionary>(); + + public ExplicitLogonEventsTextFormatter(ITextWriter writer) : base(writer) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + var dto = (ExplicitLogonEventsDTO)result; + + var targetUser = dto.TargetDomain + "\\" + dto.TargetUser; + var subjectUser = dto.SubjectDomain + "\\" + dto.SubjectUser; + var uniqueCredKey = $"{targetUser},{dto.Process},{subjectUser},{dto.IpAddress}"; + + WriteLine($"{dto.TimeCreated?.ToString("MM/dd/yyyy hh:mm tt")},{uniqueCredKey}"); + + //if (events.TryGetValue(uniqueCredKey, out _) == false) + //{ + // events[uniqueCredKey] = new List + // { + // dto.TimeCreated.ToString() + // }; + //} + //else + //{ + // events[uniqueCredKey].Add(dto.TimeCreated.ToString()); + //} + + + //foreach (string key in events.Keys) + //{ + // WriteLine("\n\n --- " + key + " ---"); + // var dates = events[key].ToArray(); + // for (int i = 0; i < dates.Length; i++) + // { + // if (i % 4 == 0) + // { + // Write("\n "); + // } + + // Write(dates[i].PadRight(24)); + // } + + // Write("\n"); + + + //WriteLine("\n\n --- " + key + " ---"); + //var dates = events[key].ToArray(); + + //for (var i = 0; i < dates.Length; i++) + //{ + // if (i % 4 == 0) + // { + // WriteHost("\n "); + // } + + // WriteHost(dates[i]); + + // if (i != dates.Length - 1) + // { + // WriteHost(", "); + // } + //} + + //WriteLine(); + //} + } + } +} diff --git a/Seatbelt/Commands/Windows/EventLogs/LogonEventsCommand.cs b/Seatbelt/Commands/Windows/EventLogs/LogonEventsCommand.cs new file mode 100644 index 0000000..5ef81aa --- /dev/null +++ b/Seatbelt/Commands/Windows/EventLogs/LogonEventsCommand.cs @@ -0,0 +1,257 @@ +#nullable disable +using System; +using System.Collections.Generic; +using System.Diagnostics.Eventing.Reader; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using Seatbelt.Util; +using Seatbelt.Interop; +using Seatbelt.Output.Formatters; +using Seatbelt.Output.TextWriters; +using static Seatbelt.Interop.Secur32; + +// TODO: Group unique credentials together like we do with explicit logon events +namespace Seatbelt.Commands.Windows.EventLogs +{ + internal class LogonEventsCommand : CommandBase + { + public override string Command => "LogonEvents"; + public override string Description => "Logon events (Event ID 4624) from the security event log. Default of 10 days, argument == last X days."; + public override CommandGroup[] Group => new[] { CommandGroup.Misc }; + public override bool SupportRemote => false; // TODO remote + + + public LogonEventsCommand(Runtime runtime) : base(runtime) + { + } + + public override IEnumerable Execute(string[] args) + { + if (!SecurityUtil.IsHighIntegrity()) + { + WriteError("Unable to collect. Must be an administrator/in a high integrity context."); + yield break; + } + + var NTLMv1Users = new HashSet(); + var NTLMv2Users = new HashSet(); + var KerberosUsers = new HashSet(); + + // grab events from the last X days - 10 for workstations, 30 for "-full" collection + // Always use the user-supplied value, if specified + var lastDays = 10; + if (Shlwapi.IsWindowsServer()) + { + lastDays = 1; + } + + string userRegex = null; + + if (args.Length >= 1) + { + if (!int.TryParse(args[0], out lastDays)) + { + throw new ArgumentException("Argument is not an integer"); + } + } + + if (args.Length >= 2) + { + userRegex = args[1]; + } + + WriteVerbose($"Listing 4624 Account Logon Events for the last {lastDays} days.\n"); + + if (userRegex != null) + { + WriteVerbose($"Username Filter: {userRegex}"); + } + + var startTime = DateTime.Now.AddDays(-lastDays); + var endTime = DateTime.Now; + + var query = $@"*[System/EventId=4624] and *[System[TimeCreated[@SystemTime >= '{startTime.ToUniversalTime():o}']]] and *[System[TimeCreated[@SystemTime <= '{endTime.ToUniversalTime():o}']]]"; + var eventsQuery = new EventLogQuery("Security", PathType.LogName, query) { ReverseDirection = true }; + var logReader = new EventLogReader(eventsQuery); + + WriteHost(" TimeCreated,TargetUser,LogonType,IpAddress,SubjectUsername,AuthenticationPackageName,LmPackageName,TargetOutboundUser"); + + for (var eventDetail = logReader.ReadEvent(); eventDetail != null; eventDetail = logReader.ReadEvent()) + { + //var subjectUserSid = eventDetail.Properties[0].Value.ToString(); + var subjectUserName = eventDetail.Properties[1].Value.ToString(); + var subjectDomainName = eventDetail.Properties[2].Value.ToString(); + //var subjectLogonId = eventDetail.Properties[3].Value.ToString(); + //var targetUserSid = eventDetail.Properties[4].Value.ToString(); + var targetUserName = eventDetail.Properties[5].Value.ToString(); + var targetDomainName = eventDetail.Properties[6].Value.ToString(); + //var targetLogonId = eventDetail.Properties[7].Value.ToString(); + //var logonType = eventDetail.Properties[8].Value.ToString(); + var logonType = $"{(SECURITY_LOGON_TYPE)(int.Parse(eventDetail.Properties[8].Value.ToString()))}"; + //var logonProcessName = eventDetail.Properties[9].Value.ToString(); + var authenticationPackageName = eventDetail.Properties[10].Value.ToString(); + //var workstationName = eventDetail.Properties[11].Value.ToString(); + //var logonGuid = eventDetail.Properties[12].Value.ToString(); + //var transmittedServices = eventDetail.Properties[13].Value.ToString(); + var lmPackageName = eventDetail.Properties[14].Value.ToString(); + lmPackageName = lmPackageName == "-" ? "" : lmPackageName; + //var keyLength = eventDetail.Properties[15].Value.ToString(); + //var processId = eventDetail.Properties[16].Value.ToString(); + //var processName = eventDetail.Properties[17].Value.ToString(); + var ipAddress = eventDetail.Properties[18].Value.ToString(); + //var ipPort = eventDetail.Properties[19].Value.ToString(); + //var impersonationLevel = eventDetail.Properties[20].Value.ToString(); + //var restrictedAdminMode = eventDetail.Properties[21].Value.ToString(); + + var targetOutboundUserName = "-"; + var targetOutboundDomainName = "-"; + if (eventDetail.Properties.Count > 22) // Not available on older versions of Windows + { + targetOutboundUserName = eventDetail.Properties[22].Value.ToString(); + targetOutboundDomainName = eventDetail.Properties[23].Value.ToString(); + } + //var VirtualAccount = eventDetail.Properties[24].Value.ToString(); + //var TargetLinkedLogonId = eventDetail.Properties[25].Value.ToString(); + //var ElevatedToken = eventDetail.Properties[26].Value.ToString(); + + // filter out SYSTEM, computer accounts, local service accounts, UMFD-X accounts, and DWM-X accounts (for now) + var ignoreRegex = "^(SYSTEM|LOCAL SERVICE|NETWORK SERVICE|UMFD-[0-9]+|DWM-[0-9]+|ANONYMOUS LOGON|" + Environment.MachineName + "\\$)$"; + if (userRegex == null && Regex.IsMatch(targetUserName, ignoreRegex, RegexOptions.IgnoreCase)) + continue; + + + // Handle the user filter + if (userRegex != null && !Regex.IsMatch(targetUserName, userRegex, RegexOptions.IgnoreCase)) + continue; + + // Analyze the output + if (logonType == "Network") + { + var accountName = $"{targetDomainName}\\{targetUserName}"; + if (authenticationPackageName == "NTLM") + { + switch (lmPackageName) + { + case "NTLM V1": + NTLMv1Users.Add(accountName); + break; + case "NTLM V2": + NTLMv2Users.Add(accountName); + break; + } + } + else if (authenticationPackageName == "Kerberos") + { + KerberosUsers.Add(accountName); + } + } + + yield return new LogonEventsDTO() + { + TimeCreated = eventDetail.TimeCreated, + TargetUserName = targetUserName, + TargetDomainName = targetDomainName, + LogonType = logonType, + IpAddress = ipAddress, + SubjectUserName = subjectUserName, + SubjectDomainName = subjectDomainName, + AuthenticationPackage = authenticationPackageName, + LmPackage = lmPackageName, + TargetOutboundUserName = targetOutboundUserName, + TargetOutboundDomainName = targetOutboundDomainName + }; + } + + + // TODO: Move all of this into a Foramtter class + if (NTLMv1Users.Count > 0 || NTLMv2Users.Count > 0) + { + WriteHost("\n Other accounts authenticate to this machine using NTLM! NTLM-relay may be possible"); + } + + if (NTLMv1Users.Count > 0) + { + WriteHost("\n Accounts authenticate to this machine using NTLM v1!"); + WriteHost(" You can obtain these accounts' **NTLM** hashes by sniffing NTLM challenge/responses and then cracking them!"); + WriteHost(" NTLM v1 authentication is 100% broken!\n"); + + PrintUserSet(NTLMv1Users); + } + + if (NTLMv2Users.Count > 0) + { + WriteHost("\n Accounts authenticate to this machine using NTLM v2!"); + WriteHost(" You can obtain NetNTLMv2 for these accounts by sniffing NTLM challenge/responses."); + WriteHost(" You can then try and crack their passwords.\n"); + + PrintUserSet(NTLMv2Users); + } + + if (KerberosUsers.Count > 0) + { + WriteHost("\n The following users have authenticated to this machine using Kerberos.\n"); + PrintUserSet(KerberosUsers); + } + } + + private void PrintUserSet(HashSet users) + { + var set = users.OrderBy(u => u).ToArray(); + + var line = new StringBuilder(); + for (var i = 0; i < set.Length; i++) + { + if (i % 3 == 0) + { + WriteHost(line.ToString()); + line.Length = 0; + line.Append(" "); + } + + line.Append(set.ElementAt(i).PadRight(30)); + } + + WriteHost(line.ToString()); + WriteHost(); + } + } + + internal class LogonEventsDTO : CommandDTOBase + { + public DateTime? TimeCreated { get; set; } + public string TargetUserName { get; set; } + public string TargetDomainName { get; set; } + public string LogonType { get; set; } + public string IpAddress { get; set; } + public string SubjectUserName { get; set; } + public string SubjectDomainName { get; set; } + public string AuthenticationPackage { get; set; } + public string LmPackage { get; set; } + public string TargetOutboundUserName { get; set; } + public string TargetOutboundDomainName { get; set; } + } + + [CommandOutputType(typeof(LogonEventsDTO))] + internal class LogonEventsTextFormatter : TextFormatterBase + { + public LogonEventsTextFormatter(ITextWriter writer) : base(writer) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + var dto = (LogonEventsDTO)result; + var targetUser = dto.TargetDomainName + "\\" + dto.TargetUserName; + var subjectUser = dto.SubjectDomainName + "\\" + dto.SubjectUserName; + string targetOutboundUser = null; + if (dto.TargetOutboundUserName != "-") + { + targetOutboundUser = dto.TargetOutboundDomainName + "\\" + dto.TargetOutboundUserName; + } + + WriteLine($" {dto.TimeCreated},{targetUser},{dto.LogonType},{dto.IpAddress},{subjectUser},{dto.AuthenticationPackage},{dto.LmPackage},{targetOutboundUser}"); + } + } +} +#nullable enable \ No newline at end of file diff --git a/Seatbelt/Commands/Windows/EventLogs/PowerShellEventsCommand.cs b/Seatbelt/Commands/Windows/EventLogs/PowerShellEventsCommand.cs new file mode 100644 index 0000000..c4ea8ca --- /dev/null +++ b/Seatbelt/Commands/Windows/EventLogs/PowerShellEventsCommand.cs @@ -0,0 +1,111 @@ +using Seatbelt.Util; +using System; +using System.Collections.Generic; +using System.Diagnostics.Eventing.Reader; + + +namespace Seatbelt.Commands.Windows.EventLogs +{ + internal class PowerShellEventsCommand : CommandBase + { + public override string Command => "PowerShellEvents"; + public override string Description => "PowerShell script block logs (4104) with sensitive data."; + public override CommandGroup[] Group => new[] { CommandGroup.Misc }; + public override bool SupportRemote => false; // TODO remote + + public PowerShellEventsCommand(Runtime runtime) : base(runtime) + { + } + + public override IEnumerable Execute(string[] args) + { + // adapted from @djhohnstein's EventLogParser project + // https://github.com/djhohnstein/EventLogParser/blob/master/EventLogParser/EventLogHelpers.cs + // combined with scraping from https://docs.microsoft.com/en-us/windows-server/administration/windows-commands/windows-commands + + WriteVerbose($"Searching script block logs (EID 4104) for sensitive data.\n"); + + var context = 3; // number of lines around the match to display + + string[] powershellLogs = { "Microsoft-Windows-PowerShell/Operational", "Windows PowerShell" }; + + // Get our "sensitive" cmdline regexes from a common helper function. + var powershellRegex = MiscUtil.GetProcessCmdLineRegex(); + + foreach (var logName in powershellLogs) + { + var query = "*[System/EventId=4104]"; + var eventLogQuery = new EventLogQuery(logName, PathType.LogName, query) { ReverseDirection = true }; + var logReader = new EventLogReader(eventLogQuery); + + for (var eventDetail = logReader.ReadEvent(); eventDetail != null; eventDetail = logReader.ReadEvent()) + { + var scriptBlock = eventDetail.Properties[2].Value.ToString(); + foreach (var reg in powershellRegex) + { + var m = reg.Match(scriptBlock); + if (!m.Success) + continue; + + var contextLines = new List(); + + var scriptBlockParts = scriptBlock.Split('\n'); + for (var i = 0; i < scriptBlockParts.Length; i++) + { + if (!scriptBlockParts[i].Contains(m.Value)) + continue; + + var printed = 0; + for (var j = 1; i - j > 0 && printed < context; j++) + { + if (scriptBlockParts[i - j].Trim() == "") + continue; + + contextLines.Add(scriptBlockParts[i - j].Trim()); + printed++; + } + printed = 0; + contextLines.Add(m.Value.Trim()); + for (var j = 1; printed < context && i + j < scriptBlockParts.Length; j++) + { + if (scriptBlockParts[i + j].Trim() == "") + continue; + + contextLines.Add(scriptBlockParts[i + j].Trim()); + printed++; + } + break; + } + + var contextJoined = string.Join("\n", contextLines.ToArray()); + + yield return new PowerShellEventsDTO( + eventDetail.TimeCreated, + eventDetail.Id, + $"{eventDetail.UserId}", + m.Value, + contextJoined + ); + } + } + } + } + } + + internal class PowerShellEventsDTO : CommandDTOBase + { + public PowerShellEventsDTO(DateTime? timeCreated, int eventId, string userId, string match, string context) + { + TimeCreated = timeCreated; + EventId = eventId; + UserId = userId; + Match = match; + Context = context; + } + public DateTime? TimeCreated { get; } + public int EventId { get; } + public string UserId { get; } + public string Match { get; } + public string Context { get; } + } +} diff --git a/Seatbelt/Commands/Windows/EventLogs/PoweredOnEventsCommand.cs b/Seatbelt/Commands/Windows/EventLogs/PoweredOnEventsCommand.cs new file mode 100644 index 0000000..f8008bf --- /dev/null +++ b/Seatbelt/Commands/Windows/EventLogs/PoweredOnEventsCommand.cs @@ -0,0 +1,119 @@ +#nullable disable +using System; +using System.Collections.Generic; +using System.Diagnostics.Eventing.Reader; +using Seatbelt.Output.Formatters; +using Seatbelt.Output.TextWriters; + +namespace Seatbelt.Commands.Windows.EventLogs +{ + internal class PoweredOnEventsCommand : CommandBase + { + public override string Command => "PoweredOnEvents"; + public override string Description => "Reboot and sleep schedule based on the System event log EIDs 1, 12, 13, 42, and 6008. Default of 7 days, argument == last X days."; + public override CommandGroup[] Group => new[] {CommandGroup.System}; + public override bool SupportRemote => false; // TODO remote + + public PoweredOnEventsCommand(Runtime runtime) : base(runtime) + { + } + + public override IEnumerable Execute(string[] args) + { + // queries event IDs 12 (kernel boot) and 13 (kernel shutdown), sorts, and gives reboot schedule + // grab events from the last X days - 15 for default + var lastDays = 7; + + if (!Runtime.FilterResults) + { + lastDays = 30; + } + + if (args.Length == 1) + { + if (!int.TryParse(args[0], out lastDays)) + { + WriteError("Could not parse number"); + yield break; + } + } + + WriteHost($"Collecting kernel boot (EID 12) and shutdown (EID 13) events from the last {lastDays} days\n"); + + var startTime = DateTime.Now.AddDays(-lastDays); + var endTime = DateTime.Now; + + WriteHost("Powered On Events"); + + // eventID 1 == sleep + var query = $@" +((*[System[(EventId=12 or EventId=13) and Provider[@Name='Microsoft-Windows-Kernel-General']]] or *[System/EventId=42]) or (*[System/EventId=6008]) or (*[System/EventId=1] and *[System[Provider[@Name='Microsoft-Windows-Power-Troubleshooter']]])) and *[System[TimeCreated[@SystemTime >= '{startTime.ToUniversalTime():o}']]] and *[System[TimeCreated[@SystemTime <= '{endTime.ToUniversalTime():o}']]] +"; + + var eventsQuery = new EventLogQuery("System", PathType.LogName, query); + var logReader = new EventLogReader(eventsQuery); + + for (var eventDetail = logReader.ReadEvent(); eventDetail != null; eventDetail = logReader.ReadEvent()) + { + string action = null; + switch (eventDetail.Id) + { + case 1: + action = "awake"; + break; + case 12: + action = "startup"; + break; + case 13: + action = "shutdown"; + break; + case 42: + action = "sleep"; + break; + case 6008: + action = "shutdown(UnexpectedShutdown)"; + break; + } + + yield return new PoweredOnEventsDTO() + { + Date = (DateTime)eventDetail.TimeCreated, + Description = action + }; + } + } + + + + } + + internal class PoweredOnEventsDTO : CommandDTOBase + { + public DateTime Date { get; set; } + public string Description { get; set; } + } + + [CommandOutputType(typeof(PoweredOnEventsDTO))] + internal class PoweredOnEventsTextFormatter : TextFormatterBase + { + private string _currentDay = ""; + + public PoweredOnEventsTextFormatter(ITextWriter writer) : base(writer) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + + var dto = (PoweredOnEventsDTO)result; + if (_currentDay != dto.Date.ToShortDateString()) + { + _currentDay = dto.Date.ToShortDateString(); + WriteLine(); + } + + WriteLine($" {dto.Date,-23} : {dto.Description}"); + } + } +} +#nullable enable \ No newline at end of file diff --git a/Seatbelt/Commands/Windows/EventLogs/ProcessCreationEventsCommand.cs b/Seatbelt/Commands/Windows/EventLogs/ProcessCreationEventsCommand.cs new file mode 100644 index 0000000..6fc12be --- /dev/null +++ b/Seatbelt/Commands/Windows/EventLogs/ProcessCreationEventsCommand.cs @@ -0,0 +1,73 @@ +#nullable disable +using System; +using System.Collections.Generic; +using System.Diagnostics.Eventing.Reader; +using System.Text.RegularExpressions; +using Seatbelt.Util; + + +namespace Seatbelt.Commands.Windows.EventLogs +{ + internal class ProcessCreationEventsCommand : CommandBase + { + public override string Command => "ProcessCreationEvents"; + public override string Description => "Process creation logs (4688) with sensitive data."; + public override CommandGroup[] Group => new[] { CommandGroup.Misc }; + public override bool SupportRemote => false; // TODO remote + + public ProcessCreationEventsCommand(Runtime runtime) : base(runtime) + { + } + + public override IEnumerable Execute(string[] args) + { + if (!SecurityUtil.IsHighIntegrity()) + { + WriteError("Unable to collect. Must be an administrator."); + yield break; + } + + WriteVerbose($"Searching process creation logs (EID 4688) for sensitive data.\n"); + + // Get our "sensitive" cmdline regexes from a common helper function. + Regex[] processCmdLineRegex = MiscUtil.GetProcessCmdLineRegex(); + + var eventId = 4688; + var query = String.Format("*[System/EventId={0}]", eventId); + var eventLogQuery = new EventLogQuery("Security", PathType.LogName, query); + eventLogQuery.ReverseDirection = true; + var logReader = new EventLogReader(eventLogQuery); + + for (var eventDetail = logReader.ReadEvent(); eventDetail != null; eventDetail = logReader.ReadEvent()) + { + var commandLine = eventDetail.Properties[8].Value.ToString().Trim(); + if (commandLine != "") + { + foreach (var reg in processCmdLineRegex) + { + var m = reg.Match(commandLine); + if (m.Success) + { + yield return new ProcessCreationDTO() + { + TimeCreated = eventDetail.TimeCreated, + EventID = eventDetail.Id, + UserID = $"{eventDetail.UserId}", + Match = m.Value + }; + } + } + } + } + } + } + + internal class ProcessCreationDTO : CommandDTOBase + { + public DateTime? TimeCreated { get; set; } + public int EventID { get; set; } + public string UserID { get; set; } + public string Match { get; set; } + } +} +#nullable enable \ No newline at end of file diff --git a/Seatbelt/Commands/Windows/EventLogs/SysmonEventsCommand.cs b/Seatbelt/Commands/Windows/EventLogs/SysmonEventsCommand.cs new file mode 100644 index 0000000..efc5a6c --- /dev/null +++ b/Seatbelt/Commands/Windows/EventLogs/SysmonEventsCommand.cs @@ -0,0 +1,87 @@ +#nullable disable +using System; +using System.Collections.Generic; +using System.Diagnostics.Eventing.Reader; +using System.Text.RegularExpressions; +using Seatbelt.Util; + + +namespace Seatbelt.Commands.Windows.EventLogs +{ + internal class SysmonEventCommand : CommandBase + { + public override string Command => "SysmonEvents"; + public override string Description => "Sysmon process creation logs (1) with sensitive data."; + public override CommandGroup[] Group => new[] { CommandGroup.Misc }; + public override bool SupportRemote => false; // TODO remote + + public SysmonEventCommand(Runtime runtime) : base(runtime) + { + } + + public override IEnumerable Execute(string[] args) + { + if (!SecurityUtil.IsHighIntegrity()) + { + WriteError("Unable to collect. Must be an administrator."); + yield break; + } + + WriteVerbose($"Searching Sysmon process creation logs (Sysmon ID 1) for sensitive data.\n"); + + // Get our "sensitive" cmdline regexes from a common helper function. + Regex[] processCmdLineRegex = MiscUtil.GetProcessCmdLineRegex(); + + var eventId = 1; + var query = String.Format("*[System/EventId={0}]", eventId); + var eventLogQuery = new EventLogQuery("Microsoft-Windows-Sysmon/Operational", PathType.LogName, query); + eventLogQuery.ReverseDirection = true; + + EventLogReader logReader = null; + try + { + logReader = new EventLogReader(eventLogQuery); + } + catch + { + WriteError("Unable to query Sysmon event logs, Sysmon likely not installed."); + yield break; + } + + var i = 0; + + for (var eventDetail = logReader.ReadEvent(); eventDetail != null; eventDetail = logReader.ReadEvent()) + { + ++i; + var commandLine = eventDetail.Properties[10].Value.ToString().Trim(); + if (commandLine != "") + { + foreach (var reg in processCmdLineRegex) + { + var m = reg.Match(commandLine); + if (m.Success) + { + var userName = eventDetail.Properties[12].Value.ToString().Trim(); + yield return new SysmonEventDTO() + { + TimeCreated = eventDetail.TimeCreated, + EventID = eventDetail.Id, + UserName = userName, + Match = m.Value + }; + } + } + } + } + } + } + + internal class SysmonEventDTO : CommandDTOBase + { + public DateTime? TimeCreated { get; set; } + public int EventID { get; set; } + public string UserName { get; set; } + public string Match { get; set; } + } +} +#nullable enable \ No newline at end of file diff --git a/Seatbelt/Commands/Windows/ExplorerMRUsCommand.cs b/Seatbelt/Commands/Windows/ExplorerMRUsCommand.cs new file mode 100644 index 0000000..41248f6 --- /dev/null +++ b/Seatbelt/Commands/Windows/ExplorerMRUsCommand.cs @@ -0,0 +1,158 @@ +#nullable disable +using Seatbelt.Output.Formatters; +using Seatbelt.Output.TextWriters; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Security.Principal; +using Seatbelt.Interop; + + +namespace Seatbelt.Commands.Windows +{ + internal class ExplorerMRUsCommand : CommandBase + { + public override string Command => "ExplorerMRUs"; + public override string Description => "Explorer most recently used files (last 7 days, argument == last X days)"; + public override CommandGroup[] Group => new[] { CommandGroup.User }; + public override bool SupportRemote => false; + + + public ExplorerMRUsCommand(Runtime runtime) : base(runtime) + { + } + + public override IEnumerable Execute(string[] args) + { + var lastDays = 7; + + // parses recent file shortcuts via COM + if (args.Length == 1) + { + lastDays = int.Parse(args[0]); + } + else if (!Runtime.FilterResults) + { + lastDays = 30; + } + + foreach (var file in EnumRecentExplorerFiles(lastDays).OrderByDescending(e => ((ExplorerRecentFilesDTO)e).LastAccessDate)) + { + yield return file; + } + } + + private IEnumerable EnumRecentExplorerFiles(int lastDays) + { + var startTime = DateTime.Now.AddDays(-lastDays); + object shellObj = null; + + try + { + // WshShell COM object GUID + var shell = Type.GetTypeFromCLSID(new Guid("F935DC22-1CF0-11d0-ADB9-00C04FD58A0B")); + shellObj = Activator.CreateInstance(shell); + + var userFolder = $"{Environment.GetEnvironmentVariable("SystemDrive")}\\Users\\"; + var dirs = Directory.GetDirectories(userFolder); + foreach (var userDir in dirs) + { + if (userDir.EndsWith("Public") || userDir.EndsWith("Default") || userDir.EndsWith("Default User") || + userDir.EndsWith("All Users")) + { + continue; + } + + string userName = null; + try + { + userName = File.GetAccessControl($"{userDir}\\NTUSER.DAT") + .GetOwner(typeof(SecurityIdentifier)) + .ToString(); + userName = Advapi32.TranslateSid(userName); + } + catch + { + var parts = userDir.Split('\\'); + userName = parts[parts.Length - 1]; + } + + var recentPath = $"{userDir}\\AppData\\Roaming\\Microsoft\\Windows\\Recent\\"; + + string[] recentFiles = null; + + try + { + recentFiles = Directory.GetFiles(recentPath, "*.lnk", SearchOption.AllDirectories); + } + catch + { + continue; + } + + if (recentFiles.Length == 0) continue; + foreach (var recentFile in recentFiles) + { + var lastAccessed = File.GetLastAccessTime(recentFile); + + if (lastAccessed <= startTime) + { + continue; + } + + // invoke the WshShell com object, creating a shortcut to then extract the TargetPath from + var shortcut = shellObj.GetType().InvokeMember("CreateShortcut", BindingFlags.InvokeMethod, null, shellObj, new object[] { recentFile }); + var targetPath = (string)shortcut.GetType().InvokeMember("TargetPath", BindingFlags.GetProperty, null, shortcut, new object[] { }); + + if (targetPath.Trim() == "") + { + continue; + } + + yield return new ExplorerRecentFilesDTO() + { + Application = "Explorer", + User = userName, + Target = targetPath, + LastAccessDate = lastAccessed + }; + } + } + } + finally + { + if (shellObj != null) + { + Marshal.ReleaseComObject(shellObj); + } + } + } + } + + internal class ExplorerRecentFilesDTO : CommandDTOBase + { + public string Application { get; set; } + public string Target { get; set; } + public DateTime LastAccessDate { get; set; } + public string User { get; set; } + } + + [CommandOutputType(typeof(ExplorerRecentFilesDTO))] + internal class ExplorerRecentFileTextFormatter : TextFormatterBase + { + public ExplorerRecentFileTextFormatter(ITextWriter sink) : base(sink) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + var dto = (ExplorerRecentFilesDTO)result; + + WriteLine(" {0} {1} {2} {3}", dto.Application, dto.User, dto.LastAccessDate.ToString("yyyy-MM-dd"), dto.Target); + } + } +} +#nullable enable \ No newline at end of file diff --git a/Seatbelt/Commands/Windows/ExplorerRunCommandCommand.cs b/Seatbelt/Commands/Windows/ExplorerRunCommandCommand.cs new file mode 100644 index 0000000..ec5fdbe --- /dev/null +++ b/Seatbelt/Commands/Windows/ExplorerRunCommandCommand.cs @@ -0,0 +1,96 @@ +#nullable disable +using Microsoft.Win32; +using System.Collections.Generic; +using Seatbelt.Output.TextWriters; +using Seatbelt.Output.Formatters; + +namespace Seatbelt.Commands.Windows +{ + class ExplorerRunCommand + { + public string Key { get; set; } + public string Value { get; set; } + } + + internal class ExplorerRunCommandCommand : CommandBase + { + public override string Command => "ExplorerRunCommands"; + public override string Description => "Recent Explorer \"run\" commands"; + public override CommandGroup[] Group => new[] { CommandGroup.User, CommandGroup.Remote }; + public override bool SupportRemote => true; + public Runtime ThisRunTime; + + public ExplorerRunCommandCommand(Runtime runtime) : base(runtime) + { + ThisRunTime = runtime; + } + + public override IEnumerable Execute(string[] args) + { + // lists recently run commands via the RunMRU registry key + + var SIDs = ThisRunTime.GetUserSIDs(); + + foreach (var sid in SIDs) + { + if (!sid.StartsWith("S-1-5") || sid.EndsWith("_Classes")) + { + continue; + } + + var recentCommands = ThisRunTime.GetValues(RegistryHive.Users, $"{sid}\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\RunMRU"); + if ((recentCommands == null) || (recentCommands.Count == 0)) + { + continue; + } + + var commands = new List(); + + foreach (var kvp in recentCommands) + { + var command = new ExplorerRunCommand(); + command.Key = kvp.Key; + command.Value = $"{kvp.Value}"; + commands.Add(command); + } + + yield return new ExplorerRunCommandDTO( + sid, + commands + ); + } + } + + internal class ExplorerRunCommandDTO : CommandDTOBase + { + public ExplorerRunCommandDTO(string sid, List commands) + { + Sid = sid; + Commands = commands; + } + public string Sid { get; set; } + public List Commands { get; set; } + } + + [CommandOutputType(typeof(ExplorerRunCommandDTO))] + internal class ExplorerRunCommandFormatter : TextFormatterBase + { + public ExplorerRunCommandFormatter(ITextWriter writer) : base(writer) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + var dto = (ExplorerRunCommandDTO)result; + + WriteLine("\n {0} :", dto.Sid); + + foreach (var runCommand in dto.Commands) + { + WriteLine(" {0,-10} : {1}", runCommand.Key, runCommand.Value); + } + } + } + } +} +#nullable enable \ No newline at end of file diff --git a/Seatbelt/Commands/Windows/IdleTimeCommand.cs b/Seatbelt/Commands/Windows/IdleTimeCommand.cs new file mode 100644 index 0000000..fae04ae --- /dev/null +++ b/Seatbelt/Commands/Windows/IdleTimeCommand.cs @@ -0,0 +1,78 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Runtime.InteropServices; +using static Seatbelt.Interop.User32; +using Seatbelt.Output.Formatters; +using Seatbelt.Output.TextWriters; + + +namespace Seatbelt.Commands.Windows +{ + internal class IdleTimeCommand : CommandBase + { + public override string Command => "IdleTime"; + public override string Description => "Returns the number of seconds since the current user's last input."; + public override CommandGroup[] Group => new[] { CommandGroup.User }; + public override bool SupportRemote => false; + + + public IdleTimeCommand(Runtime runtime) : base(runtime) + { + } + + public override IEnumerable Execute(string[] args) + { + var lastInputInfo = new LastInputInfo(); + lastInputInfo.Size = (uint)Marshal.SizeOf(lastInputInfo); + + if (GetLastInputInfo(ref lastInputInfo)) + { + var currentUser = System.Security.Principal.WindowsIdentity.GetCurrent().Name; + yield return new IdleTimeDTO( + currentUser, + ((uint) Environment.TickCount - lastInputInfo.Time) + ); + } + else + { + throw new Win32Exception(Marshal.GetLastWin32Error()); + } + } + } + + internal class IdleTimeDTO : CommandDTOBase + { + public IdleTimeDTO(string currentUser, uint milliseconds) + { + CurrentUser = currentUser; + Milliseconds = milliseconds; + } + public string CurrentUser { get; } + + public uint Milliseconds { get; } + } + + [CommandOutputType(typeof(IdleTimeDTO))] + internal class IdleTimeFormatter : TextFormatterBase + { + public IdleTimeFormatter(ITextWriter writer) : base(writer) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + var dto = (IdleTimeDTO)result; + + var t = TimeSpan.FromMilliseconds(dto.Milliseconds); + var idleTime = string.Format("{0:D2}h:{1:D2}m:{2:D2}s:{3:D3}ms", + t.Hours, + t.Minutes, + t.Seconds, + t.Milliseconds); + + WriteLine($" CurrentUser : {dto.CurrentUser}"); + WriteLine($" Idletime : {idleTime} ({dto.Milliseconds} milliseconds)\n"); + } + } +} diff --git a/Seatbelt/Commands/Windows/InterestingProcessesCommand.cs b/Seatbelt/Commands/Windows/InterestingProcessesCommand.cs new file mode 100644 index 0000000..e94fe25 --- /dev/null +++ b/Seatbelt/Commands/Windows/InterestingProcessesCommand.cs @@ -0,0 +1,835 @@ +using System.Collections.Generic; +using System.Management; +using Seatbelt.Output.TextWriters; +using Seatbelt.Output.Formatters; +using System.Linq; +using Seatbelt.Util; + + +namespace Seatbelt.Commands.Windows +{ + internal class InterestingProcessesCommand : CommandBase + { + public override string Command => "InterestingProcesses"; + public override string Description => "\"Interesting\" processes - defensive products and admin tools"; + public override CommandGroup[] Group => new[] { CommandGroup.System, CommandGroup.Remote }; + public override bool SupportRemote => true; + public Runtime ThisRunTime; + + public InterestingProcessesCommand(Runtime runtime) : base(runtime) + { + ThisRunTime = runtime; + } + + public override IEnumerable Execute(string[] args) + { + // TODO: check out https://github.com/harleyQu1nn/AggressorScripts/blob/master/ProcessColor.cna#L10 + + // from https://github.com/threatexpress/red-team-scripts/blob/master/HostEnum.ps1#L985-L1033 + var defensiveProcesses = new Dictionary() + { + { "mcshield", "McAfee"}, + { "windefend", "Windows Defender AV"}, + { "msascui", "Windows Defender AV"}, + { "msascuil", "Windows Defender AV"}, + { "msmpeng", "Windows Defender AV"}, + { "msmpsvc", "Windows Defender AV"}, + { "wrsa", "WebRoot AV"}, + { "savservice", "Sophos AV"}, + { "tmccsf", "Trend Micro AV"}, + { "symantec antivirus", "Symantec"}, + { "aexnsagent", "Symantec"}, + { "ccsvchst", "Symantec"}, + { "sisidsservice", "Symantec IDS"}, + { "sisipsservice", "Symantec IPS"}, + { "sisipsutil", "Symantec IPS"}, + { "kvoop", "Symantec DLP"}, + { "brkrprcs64", "Symantec DLP"}, + { "edpa", "Symantec DLP"}, + { "mbae", "MalwareBytes Anti-Exploit"}, + { "parity", "Bit9 application whitelisting"}, + { "cb", "Carbon Black behavioral analysis"}, + { "repux", "Carbon Black Defense"}, + { "repmgr", "Carbon Black Defense"}, + { "reputils", "Carbon Black Defense"}, + { "repwsc", "Carbon Black Defense"}, + { "cylancesvc", "Cylance Protect"}, + { "cylanceui", "Cylance Protect"}, + { "bds-vision", "BDS Vision behavioral analysis"}, + { "triumfant", "Triumfant behavioral analysis"}, + { "csfalcon", "CrowdStrike Falcon EDR"}, + { "csfalconcontainer", "CrowdStrike Falcon EDR"}, + { "csfalconservice", "CrowdStrike Falcon EDR"}, + { "ossec", "OSSEC intrusion detection"}, + { "tmpfw", "Trend Micro firewall"}, + { "dgagent", "Verdasys Digital Guardian DLP"}, + { "aawtray", "UNKNOWN"}, + { "ackwin32", "UNKNOWN"}, + { "ad-aware", "UNKNOWN"}, + { "adaware", "UNKNOWN"}, + { "advxdwin", "UNKNOWN"}, + { "agentsvr", "UNKNOWN"}, + { "agentw", "UNKNOWN"}, + { "alertsvc", "UNKNOWN"}, + { "alevir", "UNKNOWN"}, + { "alogserv", "UNKNOWN"}, + { "amon9x", "UNKNOWN"}, + { "anti-trojan", "UNKNOWN"}, + { "antivirus", "UNKNOWN"}, + { "ants", "UNKNOWN"}, + { "apimonitor", "UNKNOWN"}, + { "aplica32", "UNKNOWN"}, + { "apvxdwin", "UNKNOWN"}, + { "arr", "UNKNOWN"}, + { "atcon", "UNKNOWN"}, + { "atguard", "UNKNOWN"}, + { "atro55en", "UNKNOWN"}, + { "atupdater", "UNKNOWN"}, + { "atwatch", "UNKNOWN"}, + { "au", "UNKNOWN"}, + { "aupdate", "UNKNOWN"}, + { "auto-protect.nav80try", "UNKNOWN"}, + { "autodown", "UNKNOWN"}, + { "autoruns", "UNKNOWN"}, + { "autorunsc", "UNKNOWN"}, + { "autotrace", "UNKNOWN"}, + { "autoupdate", "UNKNOWN"}, + { "avconsol", "UNKNOWN"}, + { "ave32", "UNKNOWN"}, + { "avgcc32", "UNKNOWN"}, + { "avgctrl", "UNKNOWN"}, + { "avgemc", "UNKNOWN"}, + { "avgnt", "UNKNOWN"}, + { "avgrsx", "UNKNOWN"}, + { "avgserv", "UNKNOWN"}, + { "avgserv9", "UNKNOWN"}, + { "avguard", "UNKNOWN"}, + { "avgwdsvc", "UNKNOWN"}, + { "avgui", "UNKNOWN"}, + { "avgw", "UNKNOWN"}, + { "avkpop", "UNKNOWN"}, + { "avkserv", "UNKNOWN"}, + { "avkservice", "UNKNOWN"}, + { "avkwctl9", "UNKNOWN"}, + { "avltmain", "UNKNOWN"}, + { "avnt", "UNKNOWN"}, + { "avp", "UNKNOWN"}, + { "avp32", "UNKNOWN"}, + { "avpcc", "UNKNOWN"}, + { "avpdos32", "UNKNOWN"}, + { "avpm", "UNKNOWN"}, + { "avptc32", "UNKNOWN"}, + { "avpupd", "UNKNOWN"}, + { "avsched32", "UNKNOWN"}, + { "avsynmgr", "UNKNOWN"}, + { "avwin", "UNKNOWN"}, + { "avwin95", "UNKNOWN"}, + { "avwinnt", "UNKNOWN"}, + { "avwupd", "UNKNOWN"}, + { "avwupd32", "UNKNOWN"}, + { "avwupsrv", "UNKNOWN"}, + { "avxmonitor9x", "UNKNOWN"}, + { "avxmonitornt", "UNKNOWN"}, + { "avxquar", "UNKNOWN"}, + { "backweb", "UNKNOWN"}, + { "bargains", "UNKNOWN"}, + { "bd_professional", "UNKNOWN"}, + { "beagle", "UNKNOWN"}, + { "belt", "UNKNOWN"}, + { "besclient", "IBM BigFix"}, + { "bidef", "UNKNOWN"}, + { "bidserver", "UNKNOWN"}, + { "bipcp", "UNKNOWN"}, + { "bipcpevalsetup", "UNKNOWN"}, + { "bisp", "UNKNOWN"}, + { "blackd", "UNKNOWN"}, + { "blackice", "UNKNOWN"}, + { "blink", "UNKNOWN"}, + { "blss", "UNKNOWN"}, + { "bootconf", "UNKNOWN"}, + { "bootwarn", "UNKNOWN"}, + { "borg2", "UNKNOWN"}, + { "bpc", "UNKNOWN"}, + { "brasil", "UNKNOWN"}, + { "bs120", "UNKNOWN"}, + { "bundle", "UNKNOWN"}, + { "bvt", "UNKNOWN"}, + { "ccapp", "UNKNOWN"}, + { "ccevtmgr", "UNKNOWN"}, + { "ccpxysvc", "UNKNOWN"}, + { "cdp", "UNKNOWN"}, + { "cfd", "UNKNOWN"}, + { "cfgwiz", "UNKNOWN"}, + { "cfiadmin", "UNKNOWN"}, + { "cfiaudit", "UNKNOWN"}, + { "cfinet", "UNKNOWN"}, + { "cfinet32", "UNKNOWN"}, + { "claw95", "UNKNOWN"}, + { "claw95cf", "UNKNOWN"}, + { "clean", "UNKNOWN"}, + { "cleaner", "UNKNOWN"}, + { "cleaner3", "UNKNOWN"}, + { "cleanpc", "UNKNOWN"}, + { "cleanup", "UNKNOWN"}, + { "click", "UNKNOWN"}, + { "cmdagent", "UNKNOWN"}, + { "cmesys", "UNKNOWN"}, + { "cmgrdian", "UNKNOWN"}, + { "cmon016", "UNKNOWN"}, + { "connectionmonitor", "UNKNOWN"}, + { "cpd", "UNKNOWN"}, + { "cpf9x206", "UNKNOWN"}, + { "cpfnt206", "UNKNOWN"}, + { "ctrl", "UNKNOWN"}, + { "cv", "UNKNOWN"}, + { "cwnb181", "UNKNOWN"}, + { "cwntdwmo", "UNKNOWN"}, + { "cyprotect", "UNKNOWN"}, + { "cyupdate", "UNKNOWN"}, + { "cyserver", "UNKNOWN"}, + { "cytray", "UNKNOWN"}, + { "cyveraservice", "UNKNOWN"}, + { "datemanager", "UNKNOWN"}, + { "dcomx", "UNKNOWN"}, + { "defalert", "UNKNOWN"}, + { "defscangui", "UNKNOWN"}, + { "defwatch", "UNKNOWN"}, + { "deputy", "UNKNOWN"}, + { "divx", "UNKNOWN"}, + { "dgprompt", "UNKNOWN"}, + { "dgservice", "UNKNOWN"}, + { "dllcache", "UNKNOWN"}, + { "dllreg", "UNKNOWN"}, + { "doors", "UNKNOWN"}, + { "dpf", "UNKNOWN"}, + { "dpfsetup", "UNKNOWN"}, + { "dpps2", "UNKNOWN"}, + { "drwatson", "UNKNOWN"}, + { "drweb32", "UNKNOWN"}, + { "drwebupw", "UNKNOWN"}, + { "dssagent", "UNKNOWN"}, + { "dumpcap", "UNKNOWN"}, + { "dvp95", "UNKNOWN"}, + { "dvp95_0", "UNKNOWN"}, + { "ecengine", "UNKNOWN"}, + { "efpeadm", "UNKNOWN"}, + { "egui", "UNKNOWN"}, + { "ekrn", "UNKNOWN"}, + { "enstart64", "Encase (Forensics tool)"}, + { "emet_agent", "UNKNOWN"}, + { "emet_service", "UNKNOWN"}, + { "emsw", "UNKNOWN"}, + { "engineserver", "UNKNOWN"}, + { "ent", "UNKNOWN"}, + { "esafe", "UNKNOWN"}, + { "escanhnt", "UNKNOWN"}, + { "escanv95", "UNKNOWN"}, + { "espwatch", "UNKNOWN"}, + { "ethereal", "UNKNOWN"}, + { "etrustcipe", "UNKNOWN"}, + { "evpn", "UNKNOWN"}, + { "exantivirus-cnet", "UNKNOWN"}, + { "exe.avxw", "UNKNOWN"}, + { "expert", "UNKNOWN"}, + { "explore", "UNKNOWN"}, + { "f-agnt95", "UNKNOWN"}, + { "f-prot", "UNKNOWN"}, + { "f-prot95", "UNKNOWN"}, + { "f-stopw", "UNKNOWN"}, + { "fameh32", "UNKNOWN"}, + { "fast", "UNKNOWN"}, + { "fch32", "UNKNOWN"}, + { "fcagswd", "McAfee DLP Agent"}, + { "fcags", "McAfee DLP Agent"}, + { "fih32", "UNKNOWN"}, + { "findviru", "UNKNOWN"}, + { "firesvc", "McAfee Host Intrusion Prevention"}, + { "firetray", "UNKNOWN"}, + { "firewall", "UNKNOWN"}, + { "fnrb32", "UNKNOWN"}, + { "fp-win", "UNKNOWN"}, + { "fp-win_trial", "UNKNOWN"}, + { "fprot", "UNKNOWN"}, + { "frameworkservice", "UNKNOWN"}, + { "frminst", "UNKNOWN"}, + { "frw", "UNKNOWN"}, + { "fsaa", "UNKNOWN"}, + { "fsav", "UNKNOWN"}, + { "fsav32", "UNKNOWN"}, + { "fsav530stbyb", "UNKNOWN"}, + { "fsav530wtbyb", "UNKNOWN"}, + { "fsav95", "UNKNOWN"}, + { "fsgk32", "UNKNOWN"}, + { "fsm32", "UNKNOWN"}, + { "fsma32", "UNKNOWN"}, + { "fsmb32", "UNKNOWN"}, + { "gator", "UNKNOWN"}, + { "gbmenu", "UNKNOWN"}, + { "gbpoll", "UNKNOWN"}, + { "generics", "UNKNOWN"}, + { "gmt", "UNKNOWN"}, + { "guard", "UNKNOWN"}, + { "guarddog", "UNKNOWN"}, + { "hacktracersetup", "UNKNOWN"}, + { "hbinst", "UNKNOWN"}, + { "hbsrv", "UNKNOWN"}, + { "hijackthis", "UNKNOWN"}, + { "hipsvc", "UNKNOWN"}, + { "hipmgmt", "McAfee Host Intrusion Protection"}, + { "hotactio", "UNKNOWN"}, + { "hotpatch", "UNKNOWN"}, + { "htlog", "UNKNOWN"}, + { "htpatch", "UNKNOWN"}, + { "hwpe", "UNKNOWN"}, + { "hxdl", "UNKNOWN"}, + { "hxiul", "UNKNOWN"}, + { "iamapp", "UNKNOWN"}, + { "iamserv", "UNKNOWN"}, + { "iamstats", "UNKNOWN"}, + { "ibmasn", "UNKNOWN"}, + { "ibmavsp", "UNKNOWN"}, + { "icload95", "UNKNOWN"}, + { "icloadnt", "UNKNOWN"}, + { "icmon", "UNKNOWN"}, + { "icsupp95", "UNKNOWN"}, + { "icsuppnt", "UNKNOWN"}, + { "idle", "UNKNOWN"}, + { "iedll", "UNKNOWN"}, + { "iedriver", "UNKNOWN"}, + { "iface", "UNKNOWN"}, + { "ifw2000", "UNKNOWN"}, + { "inetlnfo", "UNKNOWN"}, + { "infus", "UNKNOWN"}, + { "infwin", "UNKNOWN"}, + { "init", "UNKNOWN"}, + { "intdel", "UNKNOWN"}, + { "intren", "UNKNOWN"}, + { "iomon98", "UNKNOWN"}, + { "istsvc", "UNKNOWN"}, + { "jammer", "UNKNOWN"}, + { "jdbgmrg", "UNKNOWN"}, + { "jedi", "UNKNOWN"}, + { "kavlite40eng", "UNKNOWN"}, + { "kavpers40eng", "UNKNOWN"}, + { "kavpf", "UNKNOWN"}, + { "kazza", "UNKNOWN"}, + { "keenvalue", "UNKNOWN"}, + { "kerio-pf-213-en-win", "UNKNOWN"}, + { "kerio-wrl-421-en-win", "UNKNOWN"}, + { "kerio-wrp-421-en-win", "UNKNOWN"}, + { "kernel32", "UNKNOWN"}, + { "keypass", "UNKNOWN"}, + { "killprocesssetup161", "UNKNOWN"}, + { "launcher", "UNKNOWN"}, + { "ldnetmon", "UNKNOWN"}, + { "ldpro", "UNKNOWN"}, + { "ldpromenu", "UNKNOWN"}, + { "ldscan", "UNKNOWN"}, + { "lnetinfo", "UNKNOWN"}, + { "loader", "UNKNOWN"}, + { "localnet", "UNKNOWN"}, + { "lockdown", "UNKNOWN"}, + { "lockdown2000", "UNKNOWN"}, + { "lookout", "UNKNOWN"}, + { "lordpe", "UNKNOWN"}, + { "lsetup", "UNKNOWN"}, + { "luall", "UNKNOWN"}, + { "luau", "UNKNOWN"}, + { "lucomserver", "UNKNOWN"}, + { "luinit", "UNKNOWN"}, + { "luspt", "UNKNOWN"}, + { "mapisvc32", "UNKNOWN"}, + { "macmnsvc", "McAfee"}, + { "macompatsvc", "McAfee"}, + { "masvc", "McAfee"}, + { "mfeesp", "McAfee"}, + { "mfemactl", "McAfee"}, + { "mfetp", "McAfee"}, + { "mfecanary", "McAfee"}, + { "mfemms", "McAfee"}, + { "mbamservice", "UNKNOWN"}, + { "mcafeefire", "UNKNOWN"}, + { "mcagent", "UNKNOWN"}, + { "mcmnhdlr", "UNKNOWN"}, + { "mcscript", "UNKNOWN"}, + { "mcscript_inuse", "UNKNOWN"}, + { "mctool", "UNKNOWN"}, + { "mctray", "UNKNOWN"}, + { "mcupdate", "UNKNOWN"}, + { "mcvsrte", "UNKNOWN"}, + { "mcvsshld", "UNKNOWN"}, + { "md", "UNKNOWN"}, + { "mfeann", "McAfee VirusScan Enterprise"}, + { "mfevtps", "UNKNOWN"}, + { "mfin32", "UNKNOWN"}, + { "mfw2en", "UNKNOWN"}, + { "mfweng3.02d30", "UNKNOWN"}, + { "mgavrtcl", "UNKNOWN"}, + { "mgavrte", "UNKNOWN"}, + { "mghtml", "UNKNOWN"}, + { "mgui", "UNKNOWN"}, + { "minilog", "UNKNOWN"}, + { "minionhost", "Cyberreason"}, + { "mmod", "UNKNOWN"}, + { "monitor", "UNKNOWN"}, + { "moolive", "UNKNOWN"}, + { "mostat", "UNKNOWN"}, + { "mpfagent", "UNKNOWN"}, + { "mpfservice", "UNKNOWN"}, + { "mpftray", "UNKNOWN"}, + { "mrflux", "UNKNOWN"}, + { "msapp", "UNKNOWN"}, + { "msbb", "UNKNOWN"}, + { "msblast", "UNKNOWN"}, + { "mscache", "UNKNOWN"}, + { "msccn32", "UNKNOWN"}, + { "mscman", "UNKNOWN"}, + { "msconfig", "UNKNOWN"}, + { "msdm", "UNKNOWN"}, + { "msdos", "UNKNOWN"}, + { "msiexec16", "UNKNOWN"}, + { "msinfo32", "UNKNOWN"}, + { "mslaugh", "UNKNOWN"}, + { "msmgt", "UNKNOWN"}, + { "msmsgri32", "UNKNOWN"}, + { "mssense", "Microsoft Defender ATP"}, + { "sensecncproxy", "Microsoft Defender ATP"}, + { "mssmmc32", "UNKNOWN"}, + { "mssys", "UNKNOWN"}, + { "msvxd", "UNKNOWN"}, + { "mu0311ad", "UNKNOWN"}, + { "mwatch", "UNKNOWN"}, + { "n32scanw", "UNKNOWN"}, + { "naprdmgr", "UNKNOWN"}, + { "nav", "UNKNOWN"}, + { "navap.navapsvc", "UNKNOWN"}, + { "navapsvc", "UNKNOWN"}, + { "navapw32", "UNKNOWN"}, + { "navdx", "UNKNOWN"}, + { "navlu32", "UNKNOWN"}, + { "navnt", "UNKNOWN"}, + { "navstub", "UNKNOWN"}, + { "navw32", "UNKNOWN"}, + { "navwnt", "UNKNOWN"}, + { "nc2000", "UNKNOWN"}, + { "ncinst4", "UNKNOWN"}, + { "ndd32", "UNKNOWN"}, + { "neomonitor", "UNKNOWN"}, + { "neowatchlog", "UNKNOWN"}, + { "netarmor", "UNKNOWN"}, + { "netd32", "UNKNOWN"}, + { "netinfo", "UNKNOWN"}, + { "netmon", "UNKNOWN"}, + { "netscanpro", "UNKNOWN"}, + { "netspyhunter-1.2", "UNKNOWN"}, + { "netstat", "UNKNOWN"}, + { "netutils", "UNKNOWN"}, + { "nisserv", "UNKNOWN"}, + { "nisum", "UNKNOWN"}, + { "nmain", "UNKNOWN"}, + { "nod32", "UNKNOWN"}, + { "normist", "UNKNOWN"}, + { "norton_internet_secu_3.0_407", "UNKNOWN"}, + { "notstart", "UNKNOWN"}, + { "npf40_tw_98_nt_me_2k", "UNKNOWN"}, + { "npfmessenger", "UNKNOWN"}, + { "nprotect", "UNKNOWN"}, + { "npscheck", "UNKNOWN"}, + { "npssvc", "UNKNOWN"}, + { "nsched32", "UNKNOWN"}, + { "nssys32", "UNKNOWN"}, + { "nstask32", "UNKNOWN"}, + { "nsupdate", "UNKNOWN"}, + { "nt", "UNKNOWN"}, + { "ntrtscan", "UNKNOWN"}, + { "ntvdm", "UNKNOWN"}, + { "ntxconfig", "UNKNOWN"}, + { "nui", "UNKNOWN"}, + { "nupgrade", "UNKNOWN"}, + { "nvarch16", "UNKNOWN"}, + { "nvc95", "UNKNOWN"}, + { "nvsvc32", "UNKNOWN"}, + { "nwinst4", "UNKNOWN"}, + { "nwservice", "UNKNOWN"}, + { "nwtool16", "UNKNOWN"}, + { "nxlog", "UNKNOWN"}, + { "ollydbg", "UNKNOWN"}, + { "onsrvr", "UNKNOWN"}, + { "optimize", "UNKNOWN"}, + { "ostronet", "UNKNOWN"}, + { "osqueryd", "UNKNOWN"}, + { "otfix", "UNKNOWN"}, + { "outpost", "UNKNOWN"}, + { "outpostinstall", "UNKNOWN"}, + { "outpostproinstall", "UNKNOWN"}, + { "padmin", "UNKNOWN"}, + { "panixk", "UNKNOWN"}, + { "patch", "UNKNOWN"}, + { "pavcl", "UNKNOWN"}, + { "pavproxy", "UNKNOWN"}, + { "pavsched", "UNKNOWN"}, + { "pavw", "UNKNOWN"}, + { "pccwin98", "UNKNOWN"}, + { "pcfwallicon", "UNKNOWN"}, + { "pcip10117_0", "UNKNOWN"}, + { "pcscan", "UNKNOWN"}, + { "pdsetup", "UNKNOWN"}, + { "periscope", "UNKNOWN"}, + { "persfw", "UNKNOWN"}, + { "perswf", "UNKNOWN"}, + { "pf2", "UNKNOWN"}, + { "pfwadmin", "UNKNOWN"}, + { "pgmonitr", "UNKNOWN"}, + { "pingscan", "UNKNOWN"}, + { "platin", "UNKNOWN"}, + { "pop3trap", "UNKNOWN"}, + { "poproxy", "UNKNOWN"}, + { "popscan", "UNKNOWN"}, + { "portdetective", "UNKNOWN"}, + { "portmonitor", "UNKNOWN"}, + { "powerscan", "UNKNOWN"}, + { "ppinupdt", "UNKNOWN"}, + { "pptbc", "UNKNOWN"}, + { "ppvstop", "UNKNOWN"}, + { "prizesurfer", "UNKNOWN"}, + { "prmt", "UNKNOWN"}, + { "prmvr", "UNKNOWN"}, + { "procdump", "UNKNOWN"}, + { "processmonitor", "UNKNOWN"}, + { "procexp", "UNKNOWN"}, + { "procexp64", "UNKNOWN"}, + { "procexplorerv1.0", "UNKNOWN"}, + { "procmon", "UNKNOWN"}, + { "programauditor", "UNKNOWN"}, + { "proport", "UNKNOWN"}, + { "protectx", "UNKNOWN"}, + { "pspf", "UNKNOWN"}, + { "purge", "UNKNOWN"}, + { "qconsole", "UNKNOWN"}, + { "qserver", "UNKNOWN"}, + { "rapapp", "UNKNOWN"}, + { "rav7", "UNKNOWN"}, + { "rav7win", "UNKNOWN"}, + { "rav8win32eng", "UNKNOWN"}, + { "ray", "UNKNOWN"}, + { "rb32", "UNKNOWN"}, + { "rcsync", "UNKNOWN"}, + { "realmon", "UNKNOWN"}, + { "reged", "UNKNOWN"}, + { "regedit", "UNKNOWN"}, + { "regedt32", "UNKNOWN"}, + { "rescue", "UNKNOWN"}, + { "rescue32", "UNKNOWN"}, + { "rrguard", "UNKNOWN"}, + { "rtvscan", "UNKNOWN"}, + { "rtvscn95", "UNKNOWN"}, + { "rulaunch", "UNKNOWN"}, + { "run32dll", "UNKNOWN"}, + { "rundll", "UNKNOWN"}, + { "rundll16", "UNKNOWN"}, + { "ruxdll32", "UNKNOWN"}, + { "safeweb", "UNKNOWN"}, + { "sahagent.exescan32", "UNKNOWN"}, + { "save", "UNKNOWN"}, + { "savenow", "UNKNOWN"}, + { "sbserv", "UNKNOWN"}, + { "scam32", "UNKNOWN"}, + { "scan32", "UNKNOWN"}, + { "scan95", "UNKNOWN"}, + { "scanpm", "UNKNOWN"}, + { "scrscan", "UNKNOWN"}, + { "sentinelone", "SentinelOne Endpoint Security Software"}, + { "serv95", "UNKNOWN"}, + { "setupvameeval", "UNKNOWN"}, + { "setup_flowprotector_us", "UNKNOWN"}, + { "sfc", "UNKNOWN"}, + { "sgssfw32", "UNKNOWN"}, + { "sh", "UNKNOWN"}, + { "shellspyinstall", "UNKNOWN"}, + { "shn", "UNKNOWN"}, + { "showbehind", "UNKNOWN"}, + { "shstat", "McAfee VirusScan Enterprise"}, + { "smc", "UNKNOWN"}, + { "sms", "UNKNOWN"}, + { "smcgui", "Symantec Endpoint Detection"}, + { "smss32", "UNKNOWN"}, + { "snac64", "Symantec Endpoint Detection"}, + { "wdp", "Symantec Endpoint Detection"}, + { "sofi", "UNKNOWN"}, + { "sperm", "UNKNOWN"}, + { "splunk", "Splunk"}, + { "splunkd", "Splunk"}, + { "splunk-admon", "Splunk"}, + { "splunk-powershell", "Splunk"}, + { "splunk-winevtlog", "Splunk"}, + { "spf", "UNKNOWN"}, + { "sphinx", "UNKNOWN"}, + { "spoler", "UNKNOWN"}, + { "spoolcv", "UNKNOWN"}, + { "spoolsv32", "UNKNOWN"}, + { "spyxx", "UNKNOWN"}, + { "srexe", "UNKNOWN"}, + { "srng", "UNKNOWN"}, + { "ss3edit", "UNKNOWN"}, + { "ssgrate", "UNKNOWN"}, + { "ssg_4104", "UNKNOWN"}, + { "st2", "UNKNOWN"}, + { "start", "UNKNOWN"}, + { "stcloader", "UNKNOWN"}, + { "supftrl", "UNKNOWN"}, + { "support", "UNKNOWN"}, + { "supporter5", "UNKNOWN"}, + { "svchostc", "UNKNOWN"}, + { "svchosts", "UNKNOWN"}, + { "sweep95", "UNKNOWN"}, + { "sweepnet.sweepsrv.sys.swnetsup", "UNKNOWN"}, + { "symproxysvc", "UNKNOWN"}, + { "symtray", "UNKNOWN"}, + { "sysedit", "UNKNOWN"}, + { "sysmon", "Sysinternals Sysmon"}, + { "sysupd", "UNKNOWN"}, + { "taniumclient", "Tanium"}, + { "taskmg", "UNKNOWN"}, + { "taskmo", "UNKNOWN"}, + { "taumon", "UNKNOWN"}, + { "tbmon", "UNKNOWN"}, + { "tbscan", "UNKNOWN"}, + { "tc", "UNKNOWN"}, + { "tca", "UNKNOWN"}, + { "tcm", "UNKNOWN"}, + { "tcpview", "UNKNOWN"}, + { "tds-3", "UNKNOWN"}, + { "tds2-98", "UNKNOWN"}, + { "tds2-nt", "UNKNOWN"}, + { "teekids", "UNKNOWN"}, + { "tfak", "UNKNOWN"}, + { "tfak5", "UNKNOWN"}, + { "tgbob", "UNKNOWN"}, + { "titanin", "UNKNOWN"}, + { "titaninxp", "UNKNOWN"}, + { "tlaservice", "UNKNOWN"}, + { "tlaworker", "UNKNOWN"}, + { "tracert", "UNKNOWN"}, + { "trickler", "UNKNOWN"}, + { "trjscan", "UNKNOWN"}, + { "trjsetup", "UNKNOWN"}, + { "trojantrap3", "UNKNOWN"}, + { "tsadbot", "UNKNOWN"}, + { "tshark", "UNKNOWN"}, + { "tvmd", "UNKNOWN"}, + { "tvtmd", "UNKNOWN"}, + { "udaterui", "UNKNOWN"}, + { "undoboot", "UNKNOWN"}, + { "updat", "UNKNOWN"}, + { "update", "UNKNOWN"}, + { "updaterui", "UNKNOWN"}, + { "upgrad", "UNKNOWN"}, + { "utpost", "UNKNOWN"}, + { "vbcmserv", "UNKNOWN"}, + { "vbcons", "UNKNOWN"}, + { "vbust", "UNKNOWN"}, + { "vbwin9x", "UNKNOWN"}, + { "vbwinntw", "UNKNOWN"}, + { "vcsetup", "UNKNOWN"}, + { "vet32", "UNKNOWN"}, + { "vet95", "UNKNOWN"}, + { "vettray", "UNKNOWN"}, + { "vfsetup", "UNKNOWN"}, + { "vir-help", "UNKNOWN"}, + { "virusmdpersonalfirewall", "UNKNOWN"}, + { "vnlan300", "UNKNOWN"}, + { "vnpc3000", "UNKNOWN"}, + { "vpc32", "UNKNOWN"}, + { "vpc42", "UNKNOWN"}, + { "vpfw30s", "UNKNOWN"}, + { "vptray", "UNKNOWN"}, + { "vscan40", "UNKNOWN"}, + { "vscenu6.02d30", "UNKNOWN"}, + { "vsched", "UNKNOWN"}, + { "vsecomr", "UNKNOWN"}, + { "vshwin32", "UNKNOWN"}, + { "vsisetup", "UNKNOWN"}, + { "vsmain", "UNKNOWN"}, + { "vsmon", "UNKNOWN"}, + { "vsstat", "UNKNOWN"}, + { "vstskmgr", "McAfee VirusScan Enterprise"}, + { "vswin9xe", "UNKNOWN"}, + { "vswinntse", "UNKNOWN"}, + { "vswinperse", "UNKNOWN"}, + { "w32dsm89", "UNKNOWN"}, + { "w9x", "UNKNOWN"}, + { "watchdog", "UNKNOWN"}, + { "webdav", "UNKNOWN"}, + { "webscanx", "UNKNOWN"}, + { "webtrap", "UNKNOWN"}, + { "wfindv32", "UNKNOWN"}, + { "whoswatchingme", "UNKNOWN"}, + { "wimmun32", "UNKNOWN"}, + { "win-bugsfix", "UNKNOWN"}, + { "win32", "UNKNOWN"}, + { "win32us", "UNKNOWN"}, + { "winactive", "UNKNOWN"}, + { "window", "UNKNOWN"}, + { "windows", "UNKNOWN"}, + { "wininetd", "UNKNOWN"}, + { "wininitx", "UNKNOWN"}, + { "winlogin", "UNKNOWN"}, + { "winmain", "UNKNOWN"}, + { "winnet", "UNKNOWN"}, + { "winppr32", "UNKNOWN"}, + { "winrecon", "UNKNOWN"}, + { "winservn", "UNKNOWN"}, + { "winssk32", "UNKNOWN"}, + { "winstart", "UNKNOWN"}, + { "winstart001", "UNKNOWN"}, + { "wintsk32", "UNKNOWN"}, + { "winupdate", "UNKNOWN"}, + { "wireshark", "UNKNOWN"}, + { "wkufind", "UNKNOWN"}, + { "wnad", "UNKNOWN"}, + { "wnt", "UNKNOWN"}, + { "wradmin", "UNKNOWN"}, + { "wrctrl", "UNKNOWN"}, + { "wsbgate", "UNKNOWN"}, + { "wupdater", "UNKNOWN"}, + { "wupdt", "UNKNOWN"}, + { "wyvernworksfirewall", "UNKNOWN"}, + { "xagt", "FireEye Endpoint Security (HX)"}, + { "xpf202en", "UNKNOWN"}, + { "zapro", "UNKNOWN"}, + { "zapsetup3001", "UNKNOWN"}, + { "zatutor", "UNKNOWN"}, + { "zonalm2601", "UNKNOWN"}, + { "zonealarm", "UNKNOWN"}, + { "_avp32", "UNKNOWN"}, + { "_avpcc", "UNKNOWN"}, + { "rshell", "UNKNOWN"}, + { "_avpm", "UNKNOWN"} + }; + + // TODO: additional processes? + var interestingProcesses = new Dictionary() + { + {"cmrcservice" , "Configuration Manager Remote Control Service"}, + {"ftp" , "Misc. FTP client"}, + {"lmiguardian" , "LogMeIn Reporter"}, + {"logmeinsystray" , "LogMeIn System Tray"}, + {"ramaint" , "LogMeIn maintenance sevice"}, + {"mmc" , "Microsoft Management Console"}, + {"putty" , "Putty SSH client"}, + {"pscp" , "Putty SCP client"}, + {"psftp" , "Putty SFTP client"}, + {"puttytel" , "Putty Telnet client"}, + {"plink" , "Putty CLI client"}, + {"pageant" , "Putty SSH auth agent"}, + {"kitty" , "Kitty SSH client"}, + {"telnet" , "Misc. Telnet client"}, + {"securecrt" , "SecureCRT SSH/Telnet client"}, + {"teamviewer" , "TeamViewer"}, + {"tv_x64" , "TeamViewer x64 remote control"}, + {"tv_w32" , "TeamViewer x86 remote control"}, + {"keepass" , "KeePass password vault"}, + {"mstsc" , "Microsoft RDP client"}, + {"vnc" , "Possible VNC client"}, + {"powershell" , "PowerShell host process"}, + {"cmd" , "Command Prompt"}, + {"kaseya.agentendpoint.exe", "Command Prompt"}, + }; + + var browserProcesses = new Dictionary() + { + {"chrome" , "Google Chrome"}, + {"iexplore" , "Microsoft Internet Explorer"}, + {"microsoftedge" , "Microsoft Edge"}, + {"firefox" , "Mozilla Firefox"} + }; + + var wmiData = ThisRunTime.GetManagementObjectSearcher(@"Root\CIMV2", "SELECT * FROM Win32_Process"); + var retObjectCollection = wmiData.Get(); + + foreach (ManagementObject Process in retObjectCollection) + { + var display = false; + var category = ""; + var product = ""; + var processName = ExtensionMethods.TrimEnd(Process["Name"].ToString().ToLower(), ".exe"); + + if (defensiveProcesses.Keys.OfType().ToList().Contains(processName)) + { + display = true; + category = "defensive"; + product = defensiveProcesses[processName]; + } + else if (browserProcesses.Keys.OfType().ToList().Contains(processName, System.StringComparer.OrdinalIgnoreCase)) + { + display = true; + category = "browser"; + product = browserProcesses[processName]; + } + else if (interestingProcesses.Keys.OfType().ToList().Contains(processName, System.StringComparer.OrdinalIgnoreCase)) + { + display = true; + category = "interesting"; + product = interestingProcesses[processName]; + } + + if (display) + { + var OwnerInfo = new string[2]; + Process.InvokeMethod("GetOwner", (object[])OwnerInfo); + var owner = ""; + + if (OwnerInfo[0] != null) + { + owner = System.String.Format("{0}\\{1}", OwnerInfo[1], OwnerInfo[0]); + } + + yield return new InterestingProcessesDTO() + { + Category = category, + Name = Process["Name"], + Product = product, + ProcessID = Process["ProcessID"], + Owner = owner, + CommandLine = Process["CommandLine"] + }; + } + } + } + + internal class InterestingProcessesDTO : CommandDTOBase + { + public object Category { get; set; } = string.Empty; // "defensive", "browser", or "interesting + + public object Name { get; set; } = string.Empty; + + public object Product { get; set; } = string.Empty; + + public object ProcessID { get; set; } = string.Empty; + + public object Owner { get; set; } = string.Empty; + + public object CommandLine { get; set; } = string.Empty; + } + + [CommandOutputType(typeof(InterestingProcessesDTO))] + internal class InterestingProcessesFormatter : TextFormatterBase + { + public InterestingProcessesFormatter(ITextWriter writer) : base(writer) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + var dto = (InterestingProcessesDTO)result; + + WriteLine(" Category : {0}", dto.Category); + WriteLine(" Name : {0}", dto.Name); + WriteLine(" Product : {0}", dto.Product); + WriteLine(" ProcessID : {0}", dto.ProcessID); + WriteLine(" Owner : {0}", dto.Owner); + WriteLine(" CommandLine : {0}\n", dto.CommandLine); + } + } + } +} diff --git a/Seatbelt/Commands/Windows/InternetSettingsCommand.cs b/Seatbelt/Commands/Windows/InternetSettingsCommand.cs new file mode 100644 index 0000000..e8b88aa --- /dev/null +++ b/Seatbelt/Commands/Windows/InternetSettingsCommand.cs @@ -0,0 +1,84 @@ +#nullable disable +using Microsoft.Win32; +using Seatbelt.Util; +using System.Collections.Generic; +using Seatbelt.Output.TextWriters; +using Seatbelt.Output.Formatters; + + +namespace Seatbelt.Commands.Windows +{ + internal class InternetSettingsCommand : CommandBase + { + public override string Command => "InternetSettings"; + public override string Description => "Internet settings including proxy configs"; + public override CommandGroup[] Group => new[] {CommandGroup.System}; + public override bool SupportRemote => false; // TODO remote + + public InternetSettingsCommand(Runtime runtime) : base(runtime) + { + } + + public override IEnumerable Execute(string[] args) + { + WriteHost(" Hive Key : Value\n"); + // lists user/system internet settings, including default proxy info + var proxySettings = RegistryUtil.GetValues(RegistryHive.CurrentUser, "Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings"); + + if ((proxySettings != null) && (proxySettings.Count != 0)) + { + foreach (var kvp in proxySettings) + { + yield return new InternetSettingsDTO() + { + Hive = "HKCU", + Key = kvp.Key, + Value = kvp.Value.ToString() + }; + } + } + + WriteHost(); + + var proxySettings2 = RegistryUtil.GetValues(RegistryHive.LocalMachine, "Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings"); + + if ((proxySettings2 != null) && (proxySettings2.Count != 0)) + { + foreach (var kvp in proxySettings2) + { + yield return new InternetSettingsDTO() + { + Hive = "HKLM", + Key = kvp.Key, + Value = kvp.Value.ToString() + }; + } + } + } + + internal class InternetSettingsDTO : CommandDTOBase + { + public string Hive { get; set; } + + public string Key { get; set; } + + public string Value { get; set; } + } + + [CommandOutputType(typeof(InternetSettingsDTO))] + internal class InternetSettingsFormatter : TextFormatterBase + { + public InternetSettingsFormatter(ITextWriter writer) : base(writer) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + var dto = (InternetSettingsDTO)result; + + WriteLine(" {0} {1,30} : {2}", dto.Hive, dto.Key, dto.Value); + } + } + } +} +#nullable enable \ No newline at end of file diff --git a/Seatbelt/Commands/Windows/LastShutdownCommand.cs b/Seatbelt/Commands/Windows/LastShutdownCommand.cs new file mode 100644 index 0000000..d13468f --- /dev/null +++ b/Seatbelt/Commands/Windows/LastShutdownCommand.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using Microsoft.Win32; + + +namespace Seatbelt.Commands.Windows +{ + internal class LastShutdownCommand : CommandBase + { + public override string Command => "LastShutdown"; + public override string Description => "Returns the DateTime of the last system shutdown (via the registry)."; + public override CommandGroup[] Group => new[] { CommandGroup.System, CommandGroup.Remote }; + public override bool SupportRemote => true; + public Runtime ThisRunTime; + + public LastShutdownCommand(Runtime runtime) : base(runtime) + { + ThisRunTime = runtime; + } + + public override IEnumerable Execute(string[] args) + { + var shutdownBytes = ThisRunTime.GetBinaryValue(RegistryHive.LocalMachine, "SYSTEM\\ControlSet001\\Control\\Windows", "ShutdownTime"); + if (shutdownBytes != null) + { + var shutdownInt = BitConverter.ToInt64(shutdownBytes, 0); + var shutdownTime = DateTime.FromFileTime(shutdownInt); + + yield return new LastShutdownDTO() + { + LastShutdown = shutdownTime + }; + } + } + } + + internal class LastShutdownDTO : CommandDTOBase + { + public DateTime LastShutdown { get; set; } + } +} diff --git a/Seatbelt/Commands/Windows/LocalGPOCommand.cs b/Seatbelt/Commands/Windows/LocalGPOCommand.cs new file mode 100644 index 0000000..1b8abd1 --- /dev/null +++ b/Seatbelt/Commands/Windows/LocalGPOCommand.cs @@ -0,0 +1,135 @@ +using Microsoft.Win32; +using Seatbelt.Util; +using System.Collections.Generic; + + +namespace Seatbelt.Commands.Windows +{ + enum GPOLink + { + NO_LINK_INFORMATION = 0, + LOCAL_MACHINE = 1, + SITE = 2, + DOMAIN = 3, + ORGANIZATIONAL_UNIT = 4 + } + + // ref for "flags" - https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-gpol/b0e5c9e8-e858-4a7a-a94a-4a3d0a9d87a2 + enum GPOOptions + { + ALL_SECTIONS_ENABLED = 0, + USER_SECTION_DISABLED = 1, + COMPUTER_SECTION_DISABLE = 2, + ALL_SECTIONS_DISABLED = 3 + } + + internal class LocalGPOCommand : CommandBase + { + public override string Command => "LocalGPOs"; + public override string Description => "Local Group Policy settings applied to the machine/local users"; + public override CommandGroup[] Group => new[] {CommandGroup.System}; + public override bool SupportRemote => false; // TODO remote + + + public LocalGPOCommand(Runtime runtime) : base(runtime) + { + } + + public override IEnumerable Execute(string[] args) + { + // reference - https://specopssoft.com/blog/things-work-group-policy-caching/ + + // local machine GPOs + var basePath = @"SOFTWARE\Microsoft\Windows\CurrentVersion\Group Policy\DataStore\Machine\0"; + var machineIDs = RegistryUtil.GetSubkeyNames(RegistryHive.LocalMachine, basePath) ?? new string[] {}; + foreach(var ID in machineIDs) + { + var settings = RegistryUtil.GetValues(RegistryHive.LocalMachine, $"{basePath}\\{ID}"); + + yield return new LocalGPODTO( + settings["GPOName"], + "machine", + settings["DisplayName"], + settings["Link"], + settings["FileSysPath"], + (GPOOptions)settings["Options"], + (GPOLink)settings["GPOLink"], + settings["Extensions"] + ); + } + + // local user GPOs + var userGpOs = new Dictionary>(); + + var sids = Registry.Users.GetSubKeyNames(); + foreach (var sid in sids) + { + if (!sid.StartsWith("S-1-5") || sid.EndsWith("_Classes")) + { + continue; + } + + var extensions = RegistryUtil.GetSubkeyNames(RegistryHive.Users, $"{sid}\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Group Policy\\History"); + if ((extensions == null) || (extensions.Length == 0)) + { + continue; + } + + foreach (var extension in extensions) + { + var path = + $"{sid}\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Group Policy\\History\\{extension}"; + var UserIDs = RegistryUtil.GetSubkeyNames(RegistryHive.Users, path) ?? new string[] { }; + foreach (var ID in UserIDs) + { + var settings = RegistryUtil.GetValues(RegistryHive.Users, $"{path}\\{ID}"); + + if (userGpOs.ContainsKey($"{settings["GPOName"]}")) + { + continue; + } + + userGpOs.Add($"{settings["GPOName"]}", settings); + } + } + } + + foreach (var UserGPO in userGpOs) + { + yield return new LocalGPODTO( + UserGPO.Value["GPOName"], + "user", + UserGPO.Value["DisplayName"], + UserGPO.Value["Link"], + UserGPO.Value["FileSysPath"], + (GPOOptions)UserGPO.Value["Options"], + (GPOLink)UserGPO.Value["GPOLink"], + UserGPO.Value["Extensions"] + ); + } + } + + internal class LocalGPODTO : CommandDTOBase + { + public LocalGPODTO(object gpoName, object gpoType, object displayName, object link, object fileSysPath, GPOOptions options, GPOLink gpoLink, object extensions) + { + GPOName = gpoName; + GPOType = gpoType; + DisplayName = displayName; + Link = link; + FileSysPath = fileSysPath; + Options = options; + GPOLink = gpoLink; + Extensions = extensions; + } + public object GPOName { get; } + public object GPOType { get; } + public object DisplayName { get; } + public object Link { get; set; } + public object FileSysPath { get; } + public GPOOptions Options { get; } + public GPOLink GPOLink { get; } + public object Extensions { get; } + } + } +} diff --git a/Seatbelt/Commands/Windows/LocalGroupMembershipCommand.cs b/Seatbelt/Commands/Windows/LocalGroupMembershipCommand.cs new file mode 100644 index 0000000..65a7d08 --- /dev/null +++ b/Seatbelt/Commands/Windows/LocalGroupMembershipCommand.cs @@ -0,0 +1,103 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Seatbelt.Output.Formatters; +using Seatbelt.Output.TextWriters; +using static Seatbelt.Interop.Netapi32; + + +namespace Seatbelt.Commands.Windows +{ + internal class LocalGroupMembershipCommand : CommandBase + { + public override string Command => "LocalGroups"; + public override string Description => "Non-empty local groups, \"-full\" displays all groups (argument == computername to enumerate)"; + public override CommandGroup[] Group => new[] {CommandGroup.System}; + public override bool SupportRemote => true; + public Runtime ThisRunTime; + + public LocalGroupMembershipCommand(Runtime runtime) : base(runtime) + { + ThisRunTime = runtime; + } + + public override IEnumerable Execute(string[] args) + { + // takes an optional remote computername as an argument, otherwise defaults to the localhost + + string? computerName = null; + if(!String.IsNullOrEmpty(ThisRunTime.ComputerName)) + { + computerName = ThisRunTime.ComputerName; + } + else if (args.Length >= 1) + { + computerName = args[0]; + } + + // adapted from https://stackoverflow.com/questions/33935825/pinvoke-netlocalgroupgetmembers-runs-into-fatalexecutionengineerror/33939889#33939889 + + WriteHost(Runtime.FilterResults ? "Non-empty Local Groups (and memberships)\n\n" : "All Local Groups (and memberships)\n\n"); + + foreach(var group in GetLocalGroups(computerName)) + { + var members = GetLocalGroupMembers(computerName, group.name)?.ToList(); + if (members != null && members.Any()) + { + yield return new LocalGroupMembershipDTO( + computerName ?? Environment.MachineName, + group.name, + group.comment, + members + ); + } + } + } + } + + internal class LocalGroupMembershipDTO : CommandDTOBase + { + public LocalGroupMembershipDTO(string computerName, string groupName, string groupComment, IEnumerable members) + { + ComputerName = computerName; + GroupName = groupName; + GroupComment = groupComment; + Members = members; + } + + public string ComputerName { get; } + public string GroupName { get; } + public string GroupComment { get; } + public IEnumerable Members { get; } + } + + [CommandOutputType(typeof(LocalGroupMembershipDTO))] + internal class LocalGroupMembershipTextFormatter : TextFormatterBase + { + public LocalGroupMembershipTextFormatter(ITextWriter writer) : base(writer) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + + if (result == null) + { + return; + } + + var dto = (LocalGroupMembershipDTO)result; + WriteLine(" ** {0}\\{1} ** ({2})\n", dto.ComputerName, dto.GroupName, dto.GroupComment); + + if (dto.Members != null) + { + foreach (var member in dto.Members) + { + WriteLine($" {member.Class,-15} {member.Domain + "\\" + member.User,-40} {member.Sid}"); + } + } + + WriteLine(); + } + } +} diff --git a/Seatbelt/Commands/Windows/LocalSecurityAuthorityCommand.cs b/Seatbelt/Commands/Windows/LocalSecurityAuthorityCommand.cs new file mode 100644 index 0000000..e7543b1 --- /dev/null +++ b/Seatbelt/Commands/Windows/LocalSecurityAuthorityCommand.cs @@ -0,0 +1,86 @@ +using Microsoft.Win32; +using System.Collections.Generic; +using System.Text.RegularExpressions; +using Seatbelt.Output.TextWriters; +using Seatbelt.Output.Formatters; + + +namespace Seatbelt.Commands.Windows +{ + internal class LocalSecurityAuthorityCommand : CommandBase + { + public override string Command => "LSASettings"; + public override string Description => "LSA settings (including auth packages)"; + public override CommandGroup[] Group => new[] { CommandGroup.System, CommandGroup.Remote }; + public override bool SupportRemote => true; + public Runtime ThisRunTime; + + public LocalSecurityAuthorityCommand(Runtime runtime) : base(runtime) + { + ThisRunTime = runtime; + } + + public override IEnumerable Execute(string[] args) + { + var settings = ThisRunTime.GetValues(RegistryHive.LocalMachine, "SYSTEM\\CurrentControlSet\\Control\\Lsa"); + + if ((settings != null) && (settings.Count != 0)) + { + foreach (var kvp in settings) + { + if (kvp.Value.GetType().IsArray && (kvp.Value.GetType().GetElementType().ToString() == "System.String")) + { + var result = string.Join(",", (string[])kvp.Value); + + yield return new LocalSecurityAuthorityDTO(kvp.Key, result); + } + else if (kvp.Value.GetType().IsArray && (kvp.Value.GetType().GetElementType().ToString() == "System.Byte")) + { + var result = System.BitConverter.ToString((byte[])kvp.Value); + yield return new LocalSecurityAuthorityDTO(kvp.Key, result); + } + else + { + yield return new LocalSecurityAuthorityDTO(kvp.Key, kvp.Value.ToString()); + } + } + } + } + + internal class LocalSecurityAuthorityDTO : CommandDTOBase + { + public LocalSecurityAuthorityDTO(string key, string value) + { + Key = key; + Value = value; + } + public string Key { get; } + public string Value { get; } + } + + [CommandOutputType(typeof(LocalSecurityAuthorityDTO))] + internal class LocalSecurityAuthorityFormatter : TextFormatterBase + { + public LocalSecurityAuthorityFormatter(ITextWriter writer) : base(writer) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + var dto = (LocalSecurityAuthorityDTO)result; + + WriteLine(" {0,-30} : {1}", dto.Key, dto.Value); + + if (Regex.IsMatch(dto.Key, "Security Packages") && Regex.IsMatch(dto.Value, @".*wdigest.*")) + { + WriteLine(" [*] WDigest is enabled - plaintext password extraction is possible!"); + } + + if (dto.Key.Equals("RunAsPPL", System.StringComparison.InvariantCultureIgnoreCase) && dto.Value == "1") + { + WriteLine(" [*] LSASS Protected Mode is enabled! You will not be able to access lsass.exe's memory easily."); + } + } + } + } +} diff --git a/Seatbelt/Commands/Windows/LocalUserCommand.cs b/Seatbelt/Commands/Windows/LocalUserCommand.cs new file mode 100644 index 0000000..199ccdf --- /dev/null +++ b/Seatbelt/Commands/Windows/LocalUserCommand.cs @@ -0,0 +1,94 @@ +#nullable disable +using System; +using System.Collections.Generic; +using static Seatbelt.Interop.Netapi32; + + +namespace Seatbelt.Commands.Windows +{ + internal class LocalUserCommand : CommandBase + { + public override string Command => "LocalUsers"; + public override string Description => "Local users, whether they're active/disabled, and pwd last set (argument == computername to enumerate)"; + public override CommandGroup[] Group => new[] {CommandGroup.System}; + public override bool SupportRemote => true; + public Runtime ThisRunTime; + + public LocalUserCommand(Runtime runtime) : base(runtime) + { + ThisRunTime = runtime; + } + + public override IEnumerable Execute(string[] args) + { + // takes an optional remote computername as an argument, otherwise defaults to the localhost + + string computerName = null; + + if (!String.IsNullOrEmpty(ThisRunTime.ComputerName)) + { + computerName = ThisRunTime.ComputerName; + } + else if (args.Length == 1) + { + computerName = args[0]; + } + + // adapted from https://stackoverflow.com/questions/33935825/pinvoke-netlocalgroupgetmembers-runs-into-fatalexecutionengineerror/33939889#33939889 + foreach(var localUser in GetLocalUsers(computerName)) + { + var enabled = ((localUser.flags >> 1) & 1) == 0; + var pwdLastSet = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); + var lastLogon = new DateTime(1970, 1, 1, 0, 0, 0); + + if (localUser.passwordAge != 0) + { + pwdLastSet = DateTime.Now.AddSeconds(-localUser.passwordAge); + } + + if(localUser.last_logon != 0) + { + lastLogon = lastLogon.AddSeconds(localUser.last_logon).ToLocalTime(); + } + + yield return new LocalUserDTO( + computerName ?? "localhost", + localUser.name, + enabled, + localUser.user_id, + (Priv)localUser.priv, + localUser.comment, + pwdLastSet, + lastLogon, + localUser.num_logons + ); + } + } + } + + internal class LocalUserDTO : CommandDTOBase + { + public LocalUserDTO(string computerName, string userName, bool enabled, uint rid, Priv userType, string comment, DateTime pwdLastSet, DateTime lastLogon, uint numLogins) + { + ComputerName = computerName; + UserName = userName; + Enabled = enabled; + Rid = rid; + UserType = userType; + Comment = comment; + PwdLastSet = pwdLastSet; + LastLogon = lastLogon; + NumLogins = numLogins; + } + public string ComputerName { get; } + public string UserName { get; } + public bool Enabled { get; } + public uint Rid { get; } + public Priv UserType { get; } + public string Comment { get; } + public DateTime PwdLastSet { get; } + public DateTime LastLogon { get; } + public uint NumLogins { get; } + } +} +#nullable enable \ No newline at end of file diff --git a/Seatbelt/Commands/Windows/LogonSessionsCommand.cs b/Seatbelt/Commands/Windows/LogonSessionsCommand.cs new file mode 100644 index 0000000..df5ed77 --- /dev/null +++ b/Seatbelt/Commands/Windows/LogonSessionsCommand.cs @@ -0,0 +1,255 @@ +using Seatbelt.Util; +using System; +using System.Collections.Generic; +using System.Management; +using System.Runtime.InteropServices; +using System.Text.RegularExpressions; +using static Seatbelt.Interop.Secur32; +using Seatbelt.Output.TextWriters; +using Seatbelt.Output.Formatters; + + +namespace Seatbelt.Commands.Windows +{ + internal class LogonSessionsCommand : CommandBase + { + public override string Command => "LogonSessions"; + public override string Description => "Windows logon sessions"; + public override CommandGroup[] Group => new[] { CommandGroup.System, CommandGroup.Remote }; + public override bool SupportRemote => true; + public Runtime ThisRunTime; + + public LogonSessionsCommand(Runtime runtime) : base(runtime) + { + ThisRunTime = runtime; + } + + public override IEnumerable Execute(string[] args) + { + if (!SecurityUtil.IsHighIntegrity() || ThisRunTime.ISRemote()) + { + // https://www.pinvoke.net/default.aspx/secur32.lsalogonuser + + // list user logons combined with logon session data via WMI + var userDomainRegex = new Regex(@"Domain=""(.*)"",Name=""(.*)"""); + var logonIdRegex = new Regex(@"LogonId=""(\d+)"""); + + WriteHost("Logon Sessions (via WMI)\r\n"); + + var logonMap = new Dictionary(); + + var wmiData = ThisRunTime.GetManagementObjectSearcher(@"root\cimv2", "SELECT * FROM Win32_LoggedOnUser"); + var data = wmiData.Get(); + + foreach (ManagementObject result in data) + { + var m = logonIdRegex.Match(result["Dependent"].ToString()); + if (!m.Success) + { + continue; + } + + var logonId = m.Groups[1].ToString(); + var m2 = userDomainRegex.Match(result["Antecedent"].ToString()); + if (!m2.Success) + { + continue; + } + + var domain = m2.Groups[1].ToString(); + var user = m2.Groups[2].ToString(); + logonMap.Add(logonId, new[] { domain, user }); + } + + var wmiData2 = ThisRunTime.GetManagementObjectSearcher(@"root\cimv2", "SELECT * FROM Win32_LogonSession"); + var data2 = wmiData2.Get(); + + foreach (var o in data2) + { + var result2 = (ManagementObject)o; + var userDomain = logonMap[result2["LogonId"].ToString()]; + var domain = userDomain[0]; + var userName = userDomain[1]; + var startTime = ManagementDateTimeConverter.ToDateTime(result2["StartTime"].ToString()); + + var logonType = $"{((SECURITY_LOGON_TYPE)(int.Parse(result2["LogonType"].ToString())))}"; + + yield return new LogonSessionsDTO( + "WMI", + userName, + domain, + result2["LogonId"].ToString(), + logonType, + result2["AuthenticationPackage"].ToString(), + startTime, + null, + null, + null, + null, + null + ); + } + } + else + { + // heavily adapted from from Jared Hill: + // https://www.codeproject.com/Articles/18179/Using-the-Local-Security-Authority-to-Enumerate-Us + + WriteHost("Logon Sessions (via LSA)\n\n"); + + var logonSessions = new List(); + + var systime = new DateTime(1601, 1, 1, 0, 0, 0, 0); //win32 systemdate + + var ret = LsaEnumerateLogonSessions(out var count, out var luidPtr); // get an array of pointers to LUIDs + + for (ulong i = 0; i < count; i++) + { + // TODO: Check return value + ret = LsaGetLogonSessionData(luidPtr, out var sessionData); + var data = (SECURITY_LOGON_SESSION_DATA)Marshal.PtrToStructure(sessionData, typeof(SECURITY_LOGON_SESSION_DATA)); + + // if we have a valid logon + if (data.PSiD != IntPtr.Zero) + { + // get the account username + var username = Marshal.PtrToStringUni(data.Username.Buffer).Trim(); + + // convert the security identifier of the user + var sid = new System.Security.Principal.SecurityIdentifier(data.PSiD); + + // domain for this account + var domain = Marshal.PtrToStringUni(data.LoginDomain.Buffer).Trim(); + + // authentication package + var authpackage = Marshal.PtrToStringUni(data.AuthenticationPackage.Buffer).Trim(); + + // logon type + var logonType = (SECURITY_LOGON_TYPE)data.LogonType; + + // datetime the session was logged in + var logonTime = systime.AddTicks((long)data.LoginTime); + + // user's logon server + var logonServer = Marshal.PtrToStringUni(data.LogonServer.Buffer).Trim(); + + // logon server's DNS domain + var dnsDomainName = Marshal.PtrToStringUni(data.DnsDomainName.Buffer).Trim(); + + // user principalname + var upn = Marshal.PtrToStringUni(data.Upn.Buffer).Trim(); + + var logonID = ""; + try { logonID = data.LoginID.LowPart.ToString(); } + catch { } + + var userSID = ""; + try { userSID = sid.Value; } + catch { } + + yield return new LogonSessionsDTO( + "LSA", + username, + domain, + logonID, + logonType.ToString(), + authpackage, + null, + logonTime, + logonServer, + dnsDomainName, + upn, + userSID + ); + } + + // move the pointer forward + luidPtr = (IntPtr)((long)luidPtr.ToInt64() + Marshal.SizeOf(typeof(LUID))); + LsaFreeReturnBuffer(sessionData); + } + LsaFreeReturnBuffer(luidPtr); + } + } + + internal class LogonSessionsDTO : CommandDTOBase + { + public LogonSessionsDTO(string enumerationMethod, string userName, string domain, string logonId, string logonType, string authenticationPackage, DateTime? startTime, DateTime? logonTime, string? logonServer, string? logonServerDnsDomain, string? userPrincipalName, string? userSid) + { + EnumerationMethod = enumerationMethod; + UserName = userName; + Domain = domain; + LogonId = logonId; + LogonType = logonType; + AuthenticationPackage = authenticationPackage; + StartTime = startTime; + LogonTime = logonTime; + LogonServer = logonServer; + LogonServerDnsDomain = logonServerDnsDomain; + UserPrincipalName = userPrincipalName; + UserSID = userSid; + } + public string EnumerationMethod { get; } + + public string UserName { get; } + + public string Domain { get; } + + public string LogonId { get; } + + public string LogonType { get; } + + public string AuthenticationPackage { get; } + + public DateTime? StartTime { get; } + + public DateTime? LogonTime { get; } + + public string? LogonServer { get; } + + public string? LogonServerDnsDomain { get; } + + public string? UserPrincipalName { get; } + public string? UserSID { get; } + + } + + [CommandOutputType(typeof(LogonSessionsDTO))] + internal class LogonSessionsTextFormatter : TextFormatterBase + { + public LogonSessionsTextFormatter(ITextWriter writer) : base(writer) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + var dto = (LogonSessionsDTO)result; + + if (dto.EnumerationMethod.Equals("WMI")) + { + WriteLine("\n UserName : {0}", dto.UserName); + WriteLine(" Domain : {0}", dto.Domain); + WriteLine(" LogonId : {0}", dto.LogonId); + WriteLine(" LogonType : {0}", dto.LogonType); + WriteLine(" AuthenticationPackage : {0}", dto.AuthenticationPackage); + WriteLine($" StartTime : {dto.StartTime}"); + WriteLine(" UserPrincipalName : {0}", dto.UserPrincipalName); + + } + else + { + // LSA enumeration + WriteLine("\n UserName : {0}", dto.UserName); + WriteLine(" Domain : {0}", dto.Domain); + WriteLine(" LogonId : {0}", dto.LogonId); + WriteLine(" UserSID : {0}", dto.UserSID); + WriteLine(" AuthenticationPackage : {0}", dto.AuthenticationPackage); + WriteLine(" LogonType : {0}", dto.LogonType); + WriteLine(" LogonType : {0}", dto.LogonTime); + WriteLine(" LogonServer : {0}", dto.LogonServer); + WriteLine(" LogonServerDNSDomain : {0}", dto.LogonServerDnsDomain); + WriteLine(" UserPrincipalName : {0}", dto.UserPrincipalName); + } + } + } + } +} diff --git a/Seatbelt/Commands/Windows/MappedDrivesCommand.cs b/Seatbelt/Commands/Windows/MappedDrivesCommand.cs new file mode 100644 index 0000000..556ec27 --- /dev/null +++ b/Seatbelt/Commands/Windows/MappedDrivesCommand.cs @@ -0,0 +1,57 @@ +#nullable disable +using System.Collections.Generic; +using System.Management; + + +namespace Seatbelt.Commands.Windows +{ + internal class MappedDrivesCommand : CommandBase + { + public override string Command => "MappedDrives"; + public override string Description => "Users' mapped drives (via WMI)"; + public override CommandGroup[] Group => new[] { CommandGroup.User, CommandGroup.Remote }; + public override bool SupportRemote => true; + public Runtime ThisRunTime; + + public MappedDrivesCommand(Runtime runtime) : base(runtime) + { + ThisRunTime = runtime; + } + + public override IEnumerable Execute(string[] args) + { + var wmiData = ThisRunTime.GetManagementObjectSearcher(@"root\cimv2", "SELECT * FROM win32_networkconnection"); + var data = wmiData.Get(); + + WriteHost("Mapped Drives (via WMI)\n"); + + foreach (ManagementObject result in data) + { + yield return new MappedDrivesDTO() + { + LocalName = result["LocalName"].ToString(), + RemoteName = result["RemoteName"].ToString(), + RemotePath = result["RemotePath"].ToString(), + Status = result["Status"].ToString(), + ConnectionState = result["ConnectionState"].ToString(), + Persistent = result["Persistent"].ToString(), + UserName = result["UserName"].ToString(), + Description = result["Description"].ToString() + }; + } + } + } + + internal class MappedDrivesDTO : CommandDTOBase + { + public string LocalName { get; set; } + public string RemoteName { get; set; } + public string RemotePath { get; set; } + public string Status { get; set; } + public string ConnectionState { get; set; } + public string Persistent { get; set; } + public string UserName { get; set; } + public string Description { get; set; } + } +} +#nullable enable \ No newline at end of file diff --git a/Seatbelt/Commands/Windows/NamedPipesCommand.cs b/Seatbelt/Commands/Windows/NamedPipesCommand.cs new file mode 100644 index 0000000..8b3863c --- /dev/null +++ b/Seatbelt/Commands/Windows/NamedPipesCommand.cs @@ -0,0 +1,111 @@ +#nullable disable +using System.Collections.Generic; +using Seatbelt.Output.TextWriters; +using Seatbelt.Output.Formatters; +using System.Security.AccessControl; +using static Seatbelt.Interop.Kernel32; +using System.IO; + + +namespace Seatbelt.Commands.Windows +{ + internal class NamedPipesCommand : CommandBase + { + public override string Command => "NamedPipes"; + public override string Description => "Named pipe names and any readable ACL information."; + public override CommandGroup[] Group => new[] {CommandGroup.System}; + + public override bool SupportRemote => false; // might be true? unsure... + + public NamedPipesCommand(Runtime runtime) : base(runtime) + { + } + + public override IEnumerable Execute(string[] args) + { + // lists named pipes + // reference - https://stackoverflow.com/questions/25109491/how-can-i-get-a-list-of-all-open-named-pipes-in-windows-and-avoiding-possible-ex/25126943#25126943 + var namedPipes = new List(); + WIN32_FIND_DATA lpFindFileData; + + var ptr = FindFirstFile(@"\\.\pipe\*", out lpFindFileData); + namedPipes.Add(lpFindFileData.cFileName); + while (FindNextFile(ptr, out lpFindFileData)) + { + namedPipes.Add(lpFindFileData.cFileName); + } + FindClose(ptr); + + namedPipes.Sort(); + + foreach (var namedPipe in namedPipes) + { + FileSecurity security; + var sddl = ""; + try + { + security = File.GetAccessControl(System.String.Format("\\\\.\\pipe\\{0}", namedPipe)); + sddl = security.GetSecurityDescriptorSddlForm(AccessControlSections.All); + } + catch + { + sddl = "ERROR"; + } + + if (!System.String.IsNullOrEmpty(sddl) && !sddl.Equals("ERROR")) + { + yield return new NamedPipesDTO() + { + Name = namedPipe, + Sddl = sddl + //SecurityDescriptor = new RawSecurityDescriptor(sddl) + }; + } + else + { + yield return new NamedPipesDTO() + { + Name = namedPipe, + Sddl = sddl + //SecurityDescriptor = null + }; + } + } + } + } + + internal class NamedPipesDTO : CommandDTOBase + { + public string Name { get; set; } + + public string Sddl { get; set; } + + // public RawSecurityDescriptor SecurityDescriptor { get; set; } + } + + [CommandOutputType(typeof(NamedPipesDTO))] + internal class NamedPipesFormatter : TextFormatterBase + { + public NamedPipesFormatter(ITextWriter writer) : base(writer) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + var dto = (NamedPipesDTO)result; + + WriteLine(" \\\\.\\pipe\\{0}", dto.Name); + if (!dto.Sddl.Equals("ERROR")) + { + //WriteLine(" Owner : {0}", dto.SecurityDescriptor.Owner); + //foreach (CommonAce rule in dto.SecurityDescriptor.DiscretionaryAcl) + //{ + // WriteLine(" {0} :", rule.SecurityIdentifier); + // WriteLine(" {0} : {1}", rule.AceType, (GenericAceMask)rule.AccessMask); + //} + WriteLine(" SDDL : {0}", dto.Sddl); + } + } + } +} +#nullable enable \ No newline at end of file diff --git a/Seatbelt/Commands/Windows/NetworkProfilesCommand.cs b/Seatbelt/Commands/Windows/NetworkProfilesCommand.cs new file mode 100644 index 0000000..910d8fb --- /dev/null +++ b/Seatbelt/Commands/Windows/NetworkProfilesCommand.cs @@ -0,0 +1,113 @@ +#nullable disable +using System; +using System.Collections.Generic; +using Seatbelt.Util; +using Microsoft.Win32; + +namespace Seatbelt.Commands.Windows +{ + enum NetworkCategory + { + PUBLIC = 0, + HOME = 1, + WORK = 2 + } + + // ref - https://social.technet.microsoft.com/Forums/windows/en-US/b0e13a16-51a6-4aca-8d44-c85e097f882b/nametype-in-nla-information-for-a-network-profile + enum NetworkType + { + WIRED = 6, + VPN = 23, + WIRELESS = 25, + MOBILE_BROADBAND = 243 + } + + internal class NetworkProfilesCommand : CommandBase + { + public override string Command => "NetworkProfiles"; + public override string Description => "Windows network profiles"; + public override CommandGroup[] Group => new[] { CommandGroup.System, CommandGroup.Remote }; + public override bool SupportRemote => true; + public Runtime ThisRunTime; + + public NetworkProfilesCommand(Runtime runtime) : base(runtime) + { + ThisRunTime = runtime; + } + + public override IEnumerable Execute(string[] args) + { + if (!SecurityUtil.IsHighIntegrity() && !ThisRunTime.ISRemote()) + { + WriteError("Unable to collect. Must be an administrator."); + yield break; + } + + var profileGUIDs = ThisRunTime.GetSubkeyNames(RegistryHive.LocalMachine, @"SOFTWARE\Microsoft\Windows NT\CurrentVersion\NetworkList\Profiles\"); + foreach (var profileGUID in profileGUIDs) + { + var ProfileName = ThisRunTime.GetStringValue(RegistryHive.LocalMachine, $"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\NetworkList\\Profiles\\{profileGUID}", "ProfileName"); + + var Description = ThisRunTime.GetStringValue(RegistryHive.LocalMachine, $"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\NetworkList\\Profiles\\{profileGUID}", "Description"); + + var NetworkCategory = (NetworkCategory)ThisRunTime.GetDwordValue(RegistryHive.LocalMachine, $"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\NetworkList\\Profiles\\{profileGUID}", "Category"); + + var NetworkType = (NetworkType)ThisRunTime.GetDwordValue(RegistryHive.LocalMachine, $"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\NetworkList\\Profiles\\{profileGUID}", "NameType"); + + var Managed = ThisRunTime.GetDwordValue(RegistryHive.LocalMachine, $"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\NetworkList\\Profiles\\{profileGUID}", "Managed"); + + var dateCreatedBytes = ThisRunTime.GetBinaryValue(RegistryHive.LocalMachine, $"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\NetworkList\\Profiles\\{profileGUID}", "DateCreated"); + var DateCreated = ConvertBinaryDateTime(dateCreatedBytes); + + var dateLastConnectedBytes = ThisRunTime.GetBinaryValue(RegistryHive.LocalMachine, $"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\NetworkList\\Profiles\\{profileGUID}", "DateCreated"); + var DateLastConnected = ConvertBinaryDateTime(dateLastConnectedBytes); + + yield return new NetworkProfilesDTO() + { + ProfileName = ProfileName, + Description = Description, + NetworkCategory = NetworkCategory, + NetworkType = NetworkType, + Managed = Managed, + DateCreated = DateCreated, + DateLastConnected = DateLastConnected, + }; + } + } + + public DateTime ConvertBinaryDateTime(byte[] bytes) + { + // helper that does some stupid Microsoft format conversion + + if (bytes == null || bytes.Length == 0) + { + return new DateTime(); + } + + // ...yes, I know this conversion is stupid + // little endian, bytes transformed to text hex and then converted to an int + // ref- http://cfed-ttf.blogspot.com/2009/08/decoding-datecreated-and.html + var year = Convert.ToInt32($"{bytes[1]:X2}{bytes[0]:X2}", 16); + var month = Convert.ToInt32($"{bytes[3]:X2}{bytes[2]:X2}", 16); + var weekday = Convert.ToInt32($"{bytes[5]:X2}{bytes[4]:X2}", 16); + var day = Convert.ToInt32($"{bytes[7]:X2}{bytes[6]:X2}", 16); + var hour = Convert.ToInt32($"{bytes[9]:X2}{bytes[8]:X2}", 16); + var min = Convert.ToInt32($"{bytes[11]:X2}{bytes[10]:X2}", 16); + var sec = Convert.ToInt32($"{bytes[13]:X2}{bytes[12]:X2}", 16); + + return new DateTime(year, month, day, hour, min, sec); + } + + internal class NetworkProfilesDTO : CommandDTOBase + { + public string ProfileName { get; set; } + public string Description { get; set; } + public NetworkCategory NetworkCategory { get; set; } + public NetworkType NetworkType { get; set; } + public object Managed { get; set; } + public DateTime DateCreated { get; set; } + public DateTime DateLastConnected { get; set; } + } + } +} +#nullable enable \ No newline at end of file diff --git a/Seatbelt/Commands/Windows/NetworkSharesCommand.cs b/Seatbelt/Commands/Windows/NetworkSharesCommand.cs new file mode 100644 index 0000000..3f8df4c --- /dev/null +++ b/Seatbelt/Commands/Windows/NetworkSharesCommand.cs @@ -0,0 +1,49 @@ +using System.Collections.Generic; +using System.Management; + + +namespace Seatbelt.Commands.Windows +{ + internal class NetworkSharesCommand : CommandBase + { + public override string Command => "NetworkShares"; + public override string Description => "Network shares exposed by the machine (via WMI)"; + public override CommandGroup[] Group => new[] { CommandGroup.System, CommandGroup.Remote }; + public override bool SupportRemote => true; + public Runtime ThisRunTime; + + public NetworkSharesCommand(Runtime runtime) : base(runtime) + { + ThisRunTime = runtime; + } + + public override IEnumerable Execute(string[] args) + { + // lists current network shares for this system via WMI + var wmiData = ThisRunTime.GetManagementObjectSearcher(@"root\cimv2", "SELECT * FROM Win32_Share"); + using var data = wmiData.Get(); + + foreach (ManagementObject result in data) + { + yield return new NetworkShareDTO( + result["Name"], + result["Path"], + result["Description"] + ); + } + } + } + + internal class NetworkShareDTO : CommandDTOBase + { + public NetworkShareDTO(object name, object path, object description) + { + Name = name; + Path = path; + Description = description; + } + public object Name { get; } + public object Path { get; } + public object Description { get; } + } +} diff --git a/Seatbelt/Commands/Windows/NtlmSettingsCommand.cs b/Seatbelt/Commands/Windows/NtlmSettingsCommand.cs new file mode 100644 index 0000000..ce2846c --- /dev/null +++ b/Seatbelt/Commands/Windows/NtlmSettingsCommand.cs @@ -0,0 +1,201 @@ +#nullable disable +using Microsoft.Win32; +using Seatbelt.Output.Formatters; +using Seatbelt.Output.TextWriters; +using Seatbelt.Util; +using System; +using System.Collections.Generic; + + +namespace Seatbelt.Commands.Windows +{ + internal class NtlmSettingsCommand : CommandBase + { + public override string Command => "NTLMSettings"; + public override string Description => "NTLM authentication settings"; + public override CommandGroup[] Group => new[] { CommandGroup.System, CommandGroup.Remote }; + public override bool SupportRemote => true; + public Runtime ThisRunTime; + + public NtlmSettingsCommand(Runtime runtime) : base(runtime) + { + ThisRunTime = runtime; + } + + public override IEnumerable Execute(string[] args) + { + yield return new NtlmSettingsDTO() + { + LanmanCompatibilityLevel = ThisRunTime.GetDwordValue(RegistryHive.LocalMachine, @"System\CurrentControlSet\Control\Lsa", "LmCompatibilityLevel"), + + ClientRequireSigning = ThisRunTime.GetDwordValue(RegistryHive.LocalMachine, @"System\CurrentControlSet\Services\LanmanWorkstation\Parameters", "RequireSecuritySignature") == 1, + ClientNegotiateSigning = ThisRunTime.GetDwordValue(RegistryHive.LocalMachine, @"System\CurrentControlSet\Services\LanmanWorkstation\Parameters", "EnableSecuritySignature") == 1, + ServerRequireSigning = ThisRunTime.GetDwordValue(RegistryHive.LocalMachine, @"System\CurrentControlSet\Services\LanManServer\Parameters", "RequireSecuritySignature") == 1, + ServerNegotiateSigning = ThisRunTime.GetDwordValue(RegistryHive.LocalMachine, @"System\CurrentControlSet\Services\LanManServer\Parameters", "EnableSecuritySignature") == 1, + + //ExtendedProtectionForAuthentication = RegistryUtil.GetValue(RegistryHive.LocalMachine, @"System\CurrentControlSet\Control\LSA", "SuppressExtendedProtection"), + + LdapSigning = ThisRunTime.GetDwordValue(RegistryHive.LocalMachine, @"System\CurrentControlSet\Services\LDAP", "LDAPClientIntegrity"), + //DCLdapSigning = RegistryUtil.GetValue(RegistryHive.LocalMachine, @"System\CurrentControlSet\Services\NTDS\Parameters", "LDAPServerIntegrity"), + //LdapChannelBinding = RegistryUtil.GetValue(RegistryHive.LocalMachine, @"System\CurrentControlSet\Services\NTDS\Parameters", "LdapEnforceChannelBinding"), + + NTLMMinClientSec = ThisRunTime.GetDwordValue(RegistryHive.LocalMachine, @"SYSTEM\CurrentControlSet\Control\Lsa\MSV1_0", "NtlmMinClientSec"), + NTLMMinServerSec = ThisRunTime.GetDwordValue(RegistryHive.LocalMachine, @"SYSTEM\CurrentControlSet\Control\Lsa\MSV1_0", "NtlmMinServerSec"), + + //DCRestrictions = RegistryUtil.GetValue(RegistryHive.LocalMachine, @"System\CurrentControlSet\Services\Netlogon\Parameters", "RestrictNTLMInDomain"), // Network security: Restrict NTLM: NTLM authentication in this domain + //DCExceptions = RegistryUtil.GetValue(RegistryHive.LocalMachine, @"System\CurrentControlSet\Services\Netlogon\Parameters", "DCAllowedNTLMServers"), // Network security: Restrict NTLM: Add server exceptions in this domain + //DCAuditing = RegistryUtil.GetValue(RegistryHive.LocalMachine, @"System\CurrentControlSet\Services\Netlogon\Parameters", "AuditNTLMInDomain"), // Network security: Restrict NTLM: Audit NTLM authentication in this domain + + InboundRestrictions = ThisRunTime.GetDwordValue(RegistryHive.LocalMachine, @"System\CurrentControlSet\Control\Lsa\MSV1_0", "RestrictReceivingNTLMTraffic"), // Network security: Restrict NTLM: Incoming NTLM traffic + OutboundRestrictions = ThisRunTime.GetDwordValue(RegistryHive.LocalMachine, @"System\CurrentControlSet\Control\Lsa\MSV1_0", "RestrictSendingNTLMTraffic"), // Network security: Restrict NTLM: Outgoing NTLM traffic to remote servers + InboundAuditing = ThisRunTime.GetDwordValue(RegistryHive.LocalMachine, @"System\CurrentControlSet\Control\Lsa\MSV1_0", "AuditReceivingNTLMTraffic"), // Network security: Restrict NTLM: Audit Incoming NTLM Traffic + OutboundExceptions = ThisRunTime.GetStringValue(RegistryHive.LocalMachine, @"System\CurrentControlSet\Control\Lsa\MSV1_0", "ClientAllowedNTLMServers"), // Network security: Restrict NTLM: Add remote server exceptions for NTLM authentication + + }; + } + } + + [Flags] + enum SessionSecurity : uint + { + None = 0x00000000, + Integrity = 0x00000010, // Message integrity + Confidentiality = 0x00000020, // Message confidentiality + NTLMv2 = 0x00080000, + Require128BitKey = 0x20000000, + Require56BitKey = 0x80000000 + } + + internal class NtlmSettingsDTO : CommandDTOBase + { + public uint? LanmanCompatibilityLevel { get; set; } + + public bool ClientRequireSigning { get; set; } + public bool ClientNegotiateSigning { get; set; } + public bool ServerRequireSigning { get; set; } + public bool ServerNegotiateSigning { get; set; } + + //public string ExtendedProtectionForAuthentication { get; set; } + + public uint? LdapSigning { get; set; } + //public string LdapChannelBinding { get; set; } + + public uint? NTLMMinClientSec { get; set; } + public uint? NTLMMinServerSec { get; set; } + + //public string DCRestrictions { get; internal set; } + //public string DCExceptions { get; internal set; } + //public string DCAuditing { get; internal set; } + + public uint? InboundRestrictions { get; internal set; } + public uint? OutboundRestrictions { get; internal set; } + public uint? InboundAuditing { get; internal set; } + public string OutboundExceptions { get; internal set; } + } + + [CommandOutputType(typeof(NtlmSettingsDTO))] + internal class NtlmSettingsTextFormatter : TextFormatterBase + { + public NtlmSettingsTextFormatter(ITextWriter writer) : base(writer) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + var dto = (NtlmSettingsDTO)result; + + string lmStr = null; + switch (dto.LanmanCompatibilityLevel) + { + case 0: lmStr = "Send LM & NTLM responses"; break; + case 1: lmStr = "Send LM & NTLM - Use NTLMv2 session security if negotiated"; break; + case 2: lmStr = "Send NTLM response only"; break; + case null: + case 3: + lmStr = "Send NTLMv2 response only - Win7+ default"; break; + case 4: lmStr = "Send NTLMv2 response only. DC: Refuse LM"; break; + case 5: lmStr = "Send NTLMv2 response only. DC: Refuse LM & NTLM"; break; + default: lmStr = "Unknown"; break; + } + WriteLine(" LanmanCompatibilityLevel : {0}({1})", dto.LanmanCompatibilityLevel, lmStr); + + + + WriteLine("\n NTLM Signing Settings"); + WriteLine(" ClientRequireSigning : {0}", dto.ClientRequireSigning); + WriteLine(" ClientNegotiateSigning : {0}", dto.ClientNegotiateSigning); + WriteLine(" ServerRequireSigning : {0}", dto.ServerRequireSigning); + WriteLine(" ServerNegotiateSigning : {0}", dto.ServerNegotiateSigning); + + + string ldapSigningStr; + switch (dto.LdapSigning) + { + case 0: ldapSigningStr = "No signing"; break; + case 1: case null: ldapSigningStr = "Negotiate signing"; break; + case 2: ldapSigningStr = "Require Signing"; break; + default: ldapSigningStr = "Unknown"; break; + } + WriteLine(" LdapSigning : {0} ({1})", dto.LdapSigning, ldapSigningStr); + + + WriteLine("\n Session Security"); + + if (dto.NTLMMinClientSec != null) + { + var clientSessionSecurity = (SessionSecurity)dto.NTLMMinClientSec; + WriteLine(" NTLMMinClientSec : {0} ({1})", dto.NTLMMinClientSec, clientSessionSecurity); + + if (dto.LanmanCompatibilityLevel < 3 && !clientSessionSecurity.HasFlag(SessionSecurity.NTLMv2)) + { + WriteLine(" [!] NTLM clients support NTLMv1!"); + } + } + + if (dto.NTLMMinServerSec != null) + { + var serverSessionSecurity = (SessionSecurity)dto.NTLMMinServerSec; + WriteLine(" NTLMMinServerSec : {0} ({1})\n", dto.NTLMMinServerSec, serverSessionSecurity); + + if (dto.LanmanCompatibilityLevel < 3 && !serverSessionSecurity.HasFlag(SessionSecurity.NTLMv2)) + { + WriteLine(" [!] NTLM services on this machine support NTLMv1!"); + } + } + + string inboundRestrictStr; + switch (dto.InboundRestrictions) + { + case 0: inboundRestrictStr = "Allow all"; break; + case 1: inboundRestrictStr = "Deny all domain accounts"; break; + case 2: inboundRestrictStr = "Deny all accounts"; break; + default: inboundRestrictStr = "Not defined"; break; + } + + string outboundRestrictStr; + switch (dto.OutboundRestrictions) + { + case 0: outboundRestrictStr = "Allow all"; break; + case 1: outboundRestrictStr = "Audit all"; break; + case 2: outboundRestrictStr = "Deny all"; break; + default: outboundRestrictStr = "Not defined"; break; + } + + string inboundAuditStr; + switch (dto.InboundAuditing) + { + case 0: inboundAuditStr = "Disable"; break; + case 1: inboundAuditStr = "Enable auditing for domain accounts"; break; + case 2: inboundAuditStr = "Enable auditing for all accounts"; break; + default: inboundAuditStr = "Not defined"; break; + } + + WriteLine("\n NTLM Auditing and Restrictions"); + WriteLine(" InboundRestrictions : {0}({1})", dto.InboundRestrictions, inboundRestrictStr); + WriteLine(" OutboundRestrictions : {0}({1})", dto.OutboundRestrictions, outboundRestrictStr); + WriteLine(" InboundAuditing : {0}({1})", dto.InboundAuditing, inboundAuditStr); + WriteLine(" OutboundExceptions : {0}", dto.OutboundExceptions); + } + } +} +#nullable enable \ No newline at end of file diff --git a/Seatbelt/Commands/Windows/OSInfoCommand.cs b/Seatbelt/Commands/Windows/OSInfoCommand.cs new file mode 100644 index 0000000..022b3d8 --- /dev/null +++ b/Seatbelt/Commands/Windows/OSInfoCommand.cs @@ -0,0 +1,182 @@ +#nullable disable +using Seatbelt.Output.Formatters; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Net; +using System.Net.NetworkInformation; +using System.Security.Principal; +using Microsoft.Win32; +using Seatbelt.Util; +using Seatbelt.Output.TextWriters; + + +namespace Seatbelt.Commands.Windows +{ + internal class OSInfoCommand : CommandBase + { + public override string Command => "OSInfo"; + public override string Description => "Basic OS info (i.e. architecture, OS version, etc.)"; + public override CommandGroup[] Group => new[] {CommandGroup.System}; + public override bool SupportRemote => false; + + public OSInfoCommand(Runtime runtime) : base(runtime) + { + } + + public override IEnumerable Execute(string[] args) + { + var ProductName = RegistryUtil.GetStringValue(RegistryHive.LocalMachine, "Software\\Microsoft\\Windows NT\\CurrentVersion", "ProductName"); + var EditionID = RegistryUtil.GetStringValue(RegistryHive.LocalMachine, "Software\\Microsoft\\Windows NT\\CurrentVersion", "EditionID"); + var ReleaseId = RegistryUtil.GetStringValue(RegistryHive.LocalMachine, "Software\\Microsoft\\Windows NT\\CurrentVersion", "ReleaseId"); + var BuildBranch = RegistryUtil.GetStringValue(RegistryHive.LocalMachine, "Software\\Microsoft\\Windows NT\\CurrentVersion", "BuildBranch"); + var CurrentMajorVersionNumber = RegistryUtil.GetStringValue(RegistryHive.LocalMachine, "Software\\Microsoft\\Windows NT\\CurrentVersion", "CurrentMajorVersionNumber"); + var CurrentVersion = RegistryUtil.GetStringValue(RegistryHive.LocalMachine, "Software\\Microsoft\\Windows NT\\CurrentVersion", "CurrentVersion"); + + var BuildNumber = RegistryUtil.GetStringValue(RegistryHive.LocalMachine, "Software\\Microsoft\\Windows NT\\CurrentVersion", "CurrentBuildNumber"); + var UBR = RegistryUtil.GetStringValue(RegistryHive.LocalMachine, "Software\\Microsoft\\Windows NT\\CurrentVersion", "UBR"); + if (!string.IsNullOrEmpty(UBR)) // UBR is not on Win < 10 + { + BuildNumber += ("." + UBR); + } + + var isHighIntegrity = SecurityUtil.IsHighIntegrity(); + var isLocalAdmin = SecurityUtil.IsLocalAdmin(); + + var arch = Environment.GetEnvironmentVariable("PROCESSOR_ARCHITECTURE"); + var ProcessorCount = Environment.ProcessorCount.ToString(); + var isVM = IsVirtualMachine(); + + var now = DateTime.UtcNow; + var boot = now - TimeSpan.FromMilliseconds(Environment.TickCount); + var BootTime = boot + TimeSpan.FromMilliseconds(Environment.TickCount); + + var strHostName = Dns.GetHostName(); + var properties = IPGlobalProperties.GetIPGlobalProperties(); + var dnsDomain = properties.DomainName; + + var timeZone = TimeZone.CurrentTimeZone; + var cultureInfo = CultureInfo.InstalledUICulture; + + var machineGuid = RegistryUtil.GetStringValue(RegistryHive.LocalMachine, "SOFTWARE\\Microsoft\\Cryptography", "MachineGuid"); + + yield return new OSInfoDTO() + { + Hostname = strHostName, + Domain = dnsDomain, + Username = WindowsIdentity.GetCurrent().Name, + ProductName = ProductName, + EditionId = EditionID, + ReleaseId = ReleaseId, + Build = BuildNumber, + BuildBranch = BuildBranch, + CurrentMajorVersionNumber = CurrentMajorVersionNumber, + CurrentVersion = CurrentVersion, + Architecture = arch, + ProcessorCount = ProcessorCount, + IsVirtualMachine = isVM, + BootTime = BootTime, + IsHighIntegrity = isHighIntegrity, + IsLocalAdmin = isLocalAdmin, + Time = DateTime.Now, + TimeZone = timeZone.StandardName, + TimeZoneUtcOffset = timeZone.GetUtcOffset(DateTime.Now).ToString(), + Locale = cultureInfo.ToString(), + MachineGuid = machineGuid + }; + } + + private bool IsVirtualMachine() + { + // returns true if the system is likely a virtual machine + // Adapted from RobSiklos' code from https://stackoverflow.com/questions/498371/how-to-detect-if-my-application-is-running-in-a-virtual-machine/11145280#11145280 + + using (var searcher = new System.Management.ManagementObjectSearcher("Select * from Win32_ComputerSystem")) + { + using (var items = searcher.Get()) + { + foreach (var item in items) + { + var manufacturer = item["Manufacturer"].ToString().ToLower(); + if ((manufacturer == "microsoft corporation" && item["Model"].ToString().ToUpperInvariant().Contains("VIRTUAL")) + || manufacturer.Contains("vmware") + || manufacturer.Contains("xen") + || item["Model"].ToString() == "VirtualBox") + { + return true; + } + } + } + } + return false; + } + } + + internal class OSInfoDTO : CommandDTOBase + { + public string Hostname { get; set; } + public string Domain { get; set; } + public string Username { get; set; } + public string? ProductName { get; set; } + public string? EditionId { get; set; } + public string? ReleaseId { get; set; } + public string Build { get; set; } + public string? BuildBranch { get; set; } + public string? CurrentMajorVersionNumber { get; set; } + public string? CurrentVersion { get; set; } + public string Architecture { get; set; } + public string ProcessorCount { get; set; } + public bool IsVirtualMachine { get; set; } + public DateTime BootTime { get; set; } + public bool IsHighIntegrity { get; set; } + public bool IsLocalAdmin { get; set; } + public DateTime Time { get; set; } + public string TimeZone { get; set; } + public string TimeZoneUtcOffset { get; set; } + public string Locale { get; set; } + public string MachineGuid { get; set; } + } + + [CommandOutputType(typeof(OSInfoDTO))] + internal class OsInfoTextFormatter : TextFormatterBase + { + public OsInfoTextFormatter(ITextWriter writer) : base(writer) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + var dto = (OSInfoDTO)result; + WriteLine(" {0,-30}: {1}", "Hostname", dto.Hostname); + WriteLine(" {0,-30}: {1}", "Domain Name", dto.Domain); + WriteLine(" {0,-30}: {1}", "Username", dto.Username); + WriteLine(" {0,-30}: {1}", "ProductName", dto.ProductName); + WriteLine(" {0,-30}: {1}", "EditionID", dto.EditionId); + WriteLine(" {0,-30}: {1}", "ReleaseId", dto.ReleaseId); + WriteLine(" {0,-30}: {1}", "Build", dto.Build); + WriteLine(" {0,-30}: {1}", "BuildBranch", dto.BuildBranch); + WriteLine(" {0,-30}: {1}", "CurrentMajorVersionNumber", dto.CurrentMajorVersionNumber); + WriteLine(" {0,-30}: {1}", "CurrentVersion", dto.CurrentVersion); + WriteLine(" {0,-30}: {1}", "Architecture", dto.Architecture); + WriteLine(" {0,-30}: {1}", "ProcessorCount", dto.ProcessorCount); + WriteLine(" {0,-30}: {1}", "IsVirtualMachine", dto.IsVirtualMachine); + var uptime = TimeSpan.FromTicks(dto.Time.Ticks - dto.BootTime.Ticks); + var bootTimeStr = $"{uptime.Days:00}:{uptime.Hours:00}:{uptime.Minutes:00}:{uptime.Seconds:00}"; + WriteLine(" {0,-30}: {1} ({2})", "BootTime (approx)", dto.BootTime, bootTimeStr); + WriteLine(" {0,-30}: {1}", "HighIntegrity", dto.IsHighIntegrity); + WriteLine(" {0,-30}: {1}", "IsLocalAdmin", dto.IsLocalAdmin); + + if (!dto.IsHighIntegrity && dto.IsLocalAdmin) + { + WriteLine(" [*] In medium integrity but user is a local administrator - UAC can be bypassed."); + } + + WriteLine(" {0,-30}: {1}", "Time", dto.Time); + WriteLine(" {0,-30}: {1}", "TimeZone", dto.TimeZone); + WriteLine(" {0,-30}: {1}", "TimeZoneOffset", dto.TimeZoneUtcOffset); + WriteLine(" {0,-30}: {1}", "Locale", dto.Locale); + WriteLine(" {0,-30}: {1}", "MachineGuid", dto.MachineGuid); + } + } +} +#nullable enable \ No newline at end of file diff --git a/Seatbelt/Commands/Windows/PSSessionSettingsCommand.cs b/Seatbelt/Commands/Windows/PSSessionSettingsCommand.cs new file mode 100644 index 0000000..afcdb0b --- /dev/null +++ b/Seatbelt/Commands/Windows/PSSessionSettingsCommand.cs @@ -0,0 +1,119 @@ +using Microsoft.Win32; +using Seatbelt.Output.Formatters; +using Seatbelt.Output.TextWriters; +using Seatbelt.Util; +using System.Xml; +using System.Collections.Generic; +using System.Security.AccessControl; + +namespace Seatbelt.Commands.Windows +{ + class PluginAccess + { + public PluginAccess(string principal, string sid, string permission) + { + Principal = principal; + Sid = sid; + Permission = permission; + } + public string Principal { get; } + public string Sid { get; } + public string Permission { get; } + } + + internal class PSSessionSettingsCommand : CommandBase + { + public override string Command => "PSSessionSettings"; + public override string Description => "Enumerates PS Session Settings from the registry"; + public override CommandGroup[] Group => new[] {CommandGroup.System}; + public override bool SupportRemote => true; + public Runtime ThisRunTime; + + public PSSessionSettingsCommand(Runtime runtime) : base(runtime) + { + ThisRunTime = runtime; + } + + public override IEnumerable Execute(string[] args) + { + if (!SecurityUtil.IsHighIntegrity() && !ThisRunTime.ISRemote()) + { + WriteError("Unable to collect. Must be an administrator."); + yield break; + } + + var plugins = new[] { "Microsoft.PowerShell", "Microsoft.PowerShell.Workflow", "Microsoft.PowerShell32" }; + foreach (var plugin in plugins) + { + var Config = ThisRunTime.GetStringValue(RegistryHive.LocalMachine, + $"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\WSMAN\\Plugin\\{plugin}", "ConfigXML"); + + var Access = new List(); + + var xmlDoc = new XmlDocument(); + xmlDoc.LoadXml(Config); + var security = xmlDoc.GetElementsByTagName("Security"); + + if (security.Count <= 0) + continue; + + foreach (XmlAttribute attr in security[0].Attributes) + { + if (attr.Name != "Sddl") + continue; + + var desc = new RawSecurityDescriptor(attr.Value); + foreach (QualifiedAce ace in desc.DiscretionaryAcl) + { + var principal = ace.SecurityIdentifier.Translate(typeof(System.Security.Principal.NTAccount)).ToString(); + var access = ace.AceQualifier.ToString(); + + Access.Add(new PluginAccess( + principal, + ace.SecurityIdentifier.ToString(), + access + )); + } + } + + yield return new PSSessionSettingsRTO( + plugin, + Access + ); + } + } + } + + internal class PSSessionSettingsRTO : CommandDTOBase + { + public PSSessionSettingsRTO(string plugin, List permission) + { + Plugin = plugin; + Permission = permission; + } + public string Plugin { get; } + public List Permission { get; } + } + + [CommandOutputType(typeof(PSSessionSettingsRTO))] + internal class PSSessionSettingsFormatter : TextFormatterBase + { + public PSSessionSettingsFormatter(ITextWriter writer) : base(writer) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + var dto = (PSSessionSettingsRTO)result; + + WriteLine(" Name : {0}", dto.Plugin); + + foreach (var access in dto.Permission) + { + WriteLine(" {0,-35} {1,-22}", access.Principal, access.Permission); + } + + WriteLine(); + } + } +} diff --git a/Seatbelt/Commands/Windows/PowerShellCommand.cs b/Seatbelt/Commands/Windows/PowerShellCommand.cs new file mode 100644 index 0000000..3e4fbb8 --- /dev/null +++ b/Seatbelt/Commands/Windows/PowerShellCommand.cs @@ -0,0 +1,197 @@ +using Microsoft.Win32; +using Seatbelt.Output.Formatters; +using Seatbelt.Output.TextWriters; +using System; +using System.Collections.Generic; +using System.Linq; + + +namespace Seatbelt.Commands.Windows +{ + // TODO: Check for the presence of the PSReadline log file + internal class PowerShellCommand : CommandBase + { + public override string Command => "PowerShell"; + public override string Description => "PowerShell versions and security settings"; + public override CommandGroup[] Group => new[] { CommandGroup.System, CommandGroup.Remote }; + public override bool SupportRemote => true; + public Runtime ThisRunTime; + + public PowerShellCommand(Runtime runtime) : base(runtime) + { + ThisRunTime = runtime; + } + + private IEnumerable GetWindowsPowerShellVersions() + { + var versions = new List(); + var PowerShellVersion2 = ThisRunTime.GetStringValue(RegistryHive.LocalMachine, "SOFTWARE\\Microsoft\\PowerShell\\1\\PowerShellEngine", "PowerShellVersion"); + + if (PowerShellVersion2 != null) + { + versions.Add(PowerShellVersion2); + } + + var PowerShellVersion4Plus = ThisRunTime.GetStringValue(RegistryHive.LocalMachine, "SOFTWARE\\Microsoft\\PowerShell\\3\\PowerShellEngine", "PowerShellVersion"); + if (PowerShellVersion4Plus != null) + { + versions.Add(PowerShellVersion4Plus); + } + + return versions; + } + + private IEnumerable GetPowerShellCoreVersions() + { + var versions = new List(); + + var keys = ThisRunTime.GetSubkeyNames(RegistryHive.LocalMachine, + @"SOFTWARE\Microsoft\PowerShellCore\InstalledVersions\") ?? new string[] { }; + + + foreach (var key in keys) + { + var version = ThisRunTime.GetStringValue(RegistryHive.LocalMachine, @"SOFTWARE\Microsoft\PowerShellCore\InstalledVersions\" + key, "SemanticVersion"); + if (version != null) + { + versions.Add(version); + } + } + + return versions; + } + + + public override IEnumerable Execute(string[] args) + { + + var installedVersions = new List(); + installedVersions.AddRange(GetWindowsPowerShellVersions()); + installedVersions.AddRange(GetPowerShellCoreVersions()); + + + var transcriptionLogging = ThisRunTime.GetStringValue(RegistryHive.LocalMachine, "SOFTWARE\\Policies\\Microsoft\\Windows\\PowerShell\\Transcription", "EnableTranscripting") == "1"; + var transcriptionInvocationLogging = ThisRunTime.GetStringValue(RegistryHive.LocalMachine, "SOFTWARE\\Policies\\Microsoft\\Windows\\PowerShell\\Transcription", "EnableInvocationHeader") == "1"; + var transcriptionDirectory = ThisRunTime.GetStringValue(RegistryHive.LocalMachine, "SOFTWARE\\Policies\\Microsoft\\Windows\\PowerShell\\Transcription", "OutputDirectory"); + var moduleLogging = ThisRunTime.GetStringValue(RegistryHive.LocalMachine, "SOFTWARE\\Policies\\Microsoft\\Windows\\PowerShell\\ModuleLogging", "EnableModuleLogging") == "1"; + var moduleNames = ThisRunTime.GetValues(RegistryHive.LocalMachine, @"SOFTWARE\Policies\Microsoft\Windows\PowerShell\ModuleLogging\ModuleNames")?.Keys.ToArray(); + var scriptBlockLogging = ThisRunTime.GetStringValue(RegistryHive.LocalMachine, "SOFTWARE\\Policies\\Microsoft\\Windows\\PowerShell\\ScriptBlockLogging", "EnableScriptBlockLogging") == "1"; + var scriptBlockInvocationLogging = ThisRunTime.GetStringValue(RegistryHive.LocalMachine, + "SOFTWARE\\Policies\\Microsoft\\Windows\\PowerShell\\ScriptBlockLogging", + "EnableScriptBlockInvocationLogging") == "1"; + var osSupportsAmsi = Environment.OSVersion.Version.Major >= 10; + + yield return new PowerShellDTO( + installedVersions.ToArray(), + transcriptionLogging, + transcriptionInvocationLogging, + transcriptionDirectory, + moduleLogging, + moduleNames, + scriptBlockLogging, + scriptBlockInvocationLogging, + osSupportsAmsi + ); + } + } + + class PowerShellDTO : CommandDTOBase + { + public PowerShellDTO(string[] installedVersions, bool transcriptionLogging, bool transcriptionInvocationLogging, string? transcriptionDirectory, bool moduleLogging, string[]? moduleNames, bool scriptBlockLogging, bool scriptBlockInvocationLogging, bool osSupportsAmsi) + { + InstalledVersions = installedVersions; + TranscriptionLogging = transcriptionLogging; + TranscriptionInvocationLogging = transcriptionInvocationLogging; + TranscriptionDirectory = transcriptionDirectory; + ModuleLogging = moduleLogging; + ModuleNames = moduleNames; + ScriptBlockLogging = scriptBlockLogging; + ScriptBlockInvocationLogging = scriptBlockInvocationLogging; + OsSupportsAmsi = osSupportsAmsi; + } + public string[] InstalledVersions { get; } + public bool? TranscriptionLogging { get; } + public bool? TranscriptionInvocationLogging { get; } + public string? TranscriptionDirectory { get; } + public bool ModuleLogging { get; } + public string[]? ModuleNames { get; } + public bool ScriptBlockLogging { get; } + public bool? ScriptBlockInvocationLogging { get; } + public bool OsSupportsAmsi { get; } + } + + [CommandOutputType(typeof(PowerShellDTO))] + internal class PowerShellTextFormatter : TextFormatterBase + { + public PowerShellTextFormatter(ITextWriter writer) : base(writer) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + var dto = (PowerShellDTO)result; + var lowestVersion = dto.InstalledVersions.Min(v => (new Version(v))); + var highestVersion = dto.InstalledVersions.Max(v => (new Version(v))); + + WriteLine(" Installed PowerShell Versions"); + foreach (var v in dto.InstalledVersions) + { + WriteLine(" " + v); + } + + WriteLine("\n Transcription Logging Settings"); + WriteLine(" Enabled : " + dto.TranscriptionLogging); + WriteLine(" Invocation Logging : " + dto.TranscriptionInvocationLogging); + WriteLine(" Log Directory : " + dto.TranscriptionDirectory); + + WriteLine("\n Module Logging Settings"); + WriteLine(" Enabled : " + dto.ModuleLogging); + WriteLine(" Logged Module Names :"); + + if (dto.ModuleNames != null) + { + foreach (var m in dto.ModuleNames) + { + WriteLine(" " + m); + } + } + + if (dto.ModuleLogging) + { + if (lowestVersion.Major < 3) + { + WriteLine(" [!] You can do a PowerShell version downgrade to bypass the logging."); + } + + if (highestVersion.Major < 3) + { + WriteLine(" [!] Module logging is configured. Logging will not occur, however, because it requires PSv3."); + } + } + + + WriteLine("\n Script Block Logging Settings"); + WriteLine(" Enabled : " + dto.ScriptBlockLogging); + WriteLine(" Invocation Logging : " + dto.ScriptBlockInvocationLogging); + if (dto.ScriptBlockLogging) + { + if (highestVersion.Major < 5) + { + WriteLine(" [!] Script block logging is configured. Logging will not occur, however, because it requires PSv5."); + } + + if (lowestVersion.Major < 5) + { + WriteLine(" [!] You can do a PowerShell version downgrade to bypass the logging."); + } + } + + WriteLine("\n Anti-Malware Scan Interface (AMSI)"); + WriteLine(" OS Supports AMSI: " + dto.OsSupportsAmsi); + if (dto.OsSupportsAmsi && lowestVersion.Major < 3) + { + WriteLine(" [!] You can do a PowerShell version downgrade to bypass AMSI."); + } + } + } +} diff --git a/Seatbelt/Commands/Windows/PrintersCommand.cs b/Seatbelt/Commands/Windows/PrintersCommand.cs new file mode 100644 index 0000000..9f736cc --- /dev/null +++ b/Seatbelt/Commands/Windows/PrintersCommand.cs @@ -0,0 +1,69 @@ +#nullable disable +using System.Collections.Generic; +using System.Management; +using Seatbelt.Util; + + +namespace Seatbelt.Commands.Windows +{ + internal class PrintersCommand : CommandBase + { + public override string Command => "Printers"; + public override string Description => "Installed Printers (via WMI)"; + public override CommandGroup[] Group => new[] { CommandGroup.System }; + public override bool SupportRemote => false; // could if it wasn't for the SDDL + + public PrintersCommand(Runtime runtime) : base(runtime) + { + } + + public override IEnumerable Execute(string[] args) + { + // lists installed printers via WMI (the Win32_Printer class) + var printerQuery = new ManagementObjectSearcher("SELECT * from Win32_Printer"); + foreach (var printer in printerQuery.Get()) + { + var isDefault = (bool)printer.GetPropertyValue("Default"); + var isNetworkPrinter = (bool)printer.GetPropertyValue("Network"); + string printerSDDL = null; + var printerName = $"{printer.GetPropertyValue("Name")}"; + + try + { + var info = SecurityUtil.GetSecurityInfos(printerName, Interop.Advapi32.SE_OBJECT_TYPE.SE_PRINTER); + printerSDDL = info.SDDL; + } + catch + { + // eat it + } + + yield return new InstalledPrintersDTO( + printerName, + $"{printer.GetPropertyValue("Status")}", + printerSDDL, + isDefault, + isNetworkPrinter + ); + } + } + } + + internal class InstalledPrintersDTO : CommandDTOBase + { + public InstalledPrintersDTO(string name, string status, string sddl, bool isDefault, bool isNetworkPrinter) + { + Name = name; + Status = status; + Sddl = sddl; + IsDefault = isDefault; + IsNetworkPrinter = isNetworkPrinter; + } + public string Name { get; } + public string Status { get; } + public string Sddl { get; } + public bool IsDefault { get; } + public bool IsNetworkPrinter { get; } + } +} +#nullable enable \ No newline at end of file diff --git a/Seatbelt/Commands/Windows/ProcessOwnersCommand.cs b/Seatbelt/Commands/Windows/ProcessOwnersCommand.cs new file mode 100644 index 0000000..b309ae3 --- /dev/null +++ b/Seatbelt/Commands/Windows/ProcessOwnersCommand.cs @@ -0,0 +1,80 @@ +#nullable disable +using System; +using System.Collections.Generic; +using System.Management; +using Seatbelt.Output.TextWriters; +using Seatbelt.Output.Formatters; + + +namespace Seatbelt.Commands.Windows +{ + internal class ProcessesOwnerCommand : CommandBase + { + public override string Command => "ProcessOwners"; + public override string Description => "Running non-session 0 process list with owners. For remote use."; + public override CommandGroup[] Group => new[] { CommandGroup.Misc, CommandGroup.Remote }; + public override bool SupportRemote => true; + public Runtime ThisRunTime; + + public ProcessesOwnerCommand(Runtime runtime) : base(runtime) + { + ThisRunTime = runtime; + } + + public override IEnumerable Execute(string[] args) + { + var wmiData = ThisRunTime.GetManagementObjectSearcher(@"Root\CIMV2", "SELECT * FROM Win32_Process WHERE SessionID != 0"); + var retObjectCollection = wmiData.Get(); + + foreach (ManagementObject Process in retObjectCollection) + { + var OwnerInfo = new string[2]; + + try + { + Process.InvokeMethod("GetOwner", (object[])OwnerInfo); + } + catch { } + var owner = ""; + + if (OwnerInfo[0] != null) + { + owner = String.Format("{0}\\{1}", OwnerInfo[1], OwnerInfo[0]); + + yield return new ProcessesOwnerDTO() + { + ProcessName = Process["Name"], + ProcessID = Process["ProcessId"], + Owner = owner + }; + } + } + } + + } + + internal class ProcessesOwnerDTO : CommandDTOBase + { + public object ProcessName { get; set; } + + public object ProcessID { get; set; } + + public object Owner { get; set; } + + } + + [CommandOutputType(typeof(ProcessesOwnerDTO))] + internal class ProcessOwnerFormatter : TextFormatterBase + { + public ProcessOwnerFormatter(ITextWriter writer) : base(writer) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + var dto = (ProcessesOwnerDTO)result; + WriteLine(" {0,-50} {1,-10} {2}", dto.ProcessName, dto.ProcessID, dto.Owner); + } + } +} +#nullable enable \ No newline at end of file diff --git a/Seatbelt/Commands/Windows/ProcessesCommand.cs b/Seatbelt/Commands/Windows/ProcessesCommand.cs new file mode 100644 index 0000000..e497963 --- /dev/null +++ b/Seatbelt/Commands/Windows/ProcessesCommand.cs @@ -0,0 +1,204 @@ +#nullable disable // Temporary - Need to fix nullable type issues +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Management; +using System.Reflection; +using System.Text.RegularExpressions; +using Seatbelt.Output.TextWriters; +using Seatbelt.Output.Formatters; + + +namespace Seatbelt.Commands.Windows +{ + class Module + { + public string ModuleName { get; set; } + public string ModuleFileName { get; set; } + public string ModuleFileDescription { get; set; } + public string ModuleOriginalFilename { get; set; } + public string ModuleCompanyName { get; set; } + } + + internal class ProcessesCommand : CommandBase + { + public override string Command => "Processes"; + public override string Description => "Running processes with file info company names that don't contain 'Microsoft', \"-full\" enumerates all processes"; + public override CommandGroup[] Group => new[] {CommandGroup.System}; + public override bool SupportRemote => false; // other local Process" stuff prevents this + + public ProcessesCommand(Runtime runtime) : base(runtime) + { + } + + public override IEnumerable Execute(string[] args) + { + // lists currently running processes that don't have "Microsoft Corporation" as the company name in their file info + // or all processes if "-full" is passed + + var enumerateModules = false; + if (args.Length == 1 && args[0].ToLower().Equals("modules")) + { + enumerateModules = true; + } + + WriteHost(Runtime.FilterResults + ? "Collecting Non Microsoft Processes (via WMI)\n" + : "Collecting All Processes (via WMI)\n"); + + var wmiQueryString = "SELECT ProcessId, ExecutablePath, CommandLine FROM Win32_Process"; + using (var searcher = new ManagementObjectSearcher(wmiQueryString)) + { + using (var results = searcher.Get()) + { + var query = from p in Process.GetProcesses() + join mo in results.Cast() + on p.Id equals (int)(uint)mo["ProcessId"] + select new + { + Process = p, + Path = (string)mo["ExecutablePath"], + CommandLine = (string)mo["CommandLine"], + }; + foreach (var item in query) + { + var isDotNet = false; + string companyName = null; + + if (item.Path != null) + { + try + { + var myAssemblyName = AssemblyName.GetAssemblyName(item.Path); + isDotNet = true; + } + catch (FileNotFoundException) + { + // WriteHost("The file cannot be found."); + } + catch (BadImageFormatException exception) + { + if (Regex.IsMatch(exception.Message, + ".*This assembly is built by a runtime newer than the currently loaded runtime and cannot be loaded.*", + RegexOptions.IgnoreCase)) + { + isDotNet = true; + } + } + catch + { + // WriteHost("The assembly has already been loaded."); + } + + try + { + var myFileVersionInfo = FileVersionInfo.GetVersionInfo(item.Path); + companyName = myFileVersionInfo.CompanyName; + } catch + { + companyName = null; + } + } + + if (Runtime.FilterResults) + { + if (string.IsNullOrEmpty(companyName) || + (companyName != null && + Regex.IsMatch(companyName, @"^Microsoft.*", RegexOptions.IgnoreCase))) + { + continue; + } + } + + var ProcessModules = new List(); + if (enumerateModules) + { + try + { + var modules = item.Process.Modules; + foreach (ProcessModule module in modules) + { + var ProcessModule = new Module() + { + ModuleName = module.ModuleName, + ModuleFileName = module.FileVersionInfo.FileName, + ModuleFileDescription = module.FileVersionInfo.FileDescription, + ModuleOriginalFilename = module.FileVersionInfo.OriginalFilename, + ModuleCompanyName = module.FileVersionInfo.CompanyName + }; + ProcessModules.Add(ProcessModule); + } + } + catch + { + // eat it + } + } + + yield return new ProcessesDTO() + { + ProcessName = item.Process.ProcessName, + CompanyName = companyName, + ProcessId = item.Process.Id, + Path = item.Path, + CommandLine = item.CommandLine, + IsDotNet = isDotNet, + Modules = ProcessModules + }; + } + } + } + } + } + + internal class ProcessesDTO : CommandDTOBase + { + public string ProcessName { get; set; } + public string CompanyName { get; set; } + public int ProcessId { get; set; } + public string Path { get; set; } + public string CommandLine { get; set; } + public bool? IsDotNet { get; set; } + public List Modules { get; set; } + } + + [CommandOutputType(typeof(ProcessesDTO))] + internal class ProcessFormatter : TextFormatterBase + { + public ProcessFormatter(ITextWriter writer) : base(writer) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + var dto = (ProcessesDTO)result; + + WriteLine(" {0,-40} : {1}", "ProcessName", dto.ProcessName); + WriteLine(" {0,-40} : {1}", "CompanyName", dto.CompanyName); + WriteLine(" {0,-40} : {1}", "ProcessId", dto.ProcessId); + WriteLine(" {0,-40} : {1}", "Path", dto.Path); + WriteLine(" {0,-40} : {1}", "CommandLine", dto.CommandLine); + WriteLine(" {0,-40} : {1}", "IsDotNet", dto.IsDotNet); + + if (dto.Modules.Count != 0) + { + WriteLine(" {0,-40} :", "Modules"); + foreach (var module in dto.Modules) + { + if (!filterResults || String.IsNullOrEmpty(module.ModuleCompanyName) || !Regex.IsMatch(module.ModuleCompanyName, @"^Microsoft.*", RegexOptions.IgnoreCase)) + { + WriteLine(" {0,40} : {1}", "Name", module.ModuleName); + WriteLine(" {0,40} : {1}", "CompanyName", module.ModuleCompanyName); + WriteLine(" {0,40} : {1}", "FileName", module.ModuleFileName); + WriteLine(" {0,40} : {1}", "OriginalFileName", module.ModuleOriginalFilename); + WriteLine(" {0,40} : {1}\n", "FileDescription", module.ModuleFileDescription); + } + } + } + WriteLine(); + } + } +} +#nullable enable \ No newline at end of file diff --git a/Seatbelt/Commands/Windows/RDPSavedConnectionCommand.cs b/Seatbelt/Commands/Windows/RDPSavedConnectionCommand.cs new file mode 100644 index 0000000..386897c --- /dev/null +++ b/Seatbelt/Commands/Windows/RDPSavedConnectionCommand.cs @@ -0,0 +1,108 @@ +#nullable disable +using Microsoft.Win32; +using System.Collections.Generic; +using Seatbelt.Output.TextWriters; +using Seatbelt.Output.Formatters; + + +namespace Seatbelt.Commands.Windows +{ + class RDPConnection + { + public string RemoteHost { get; set; } + public string UserNameHint { get; set; } + } + + internal class RDPSavedConnectionCommand : CommandBase + { + public override string Command => "RDPSavedConnections"; + public override string Description => "Saved RDP connections stored in the registry"; + public override CommandGroup[] Group => new[] { CommandGroup.User, CommandGroup.Remote }; + public override bool SupportRemote => true; + public Runtime ThisRunTime; + + public RDPSavedConnectionCommand(Runtime runtime) : base(runtime) + { + ThisRunTime = runtime; + } + + public override IEnumerable Execute(string[] args) + { + var SIDs = ThisRunTime.GetUserSIDs(); + //shows saved RDP connections, including username hints (if present) + foreach (var sid in SIDs) + { + if (!sid.StartsWith("S-1-5") || sid.EndsWith("_Classes")) + { + continue; + } + + var subkeys = ThisRunTime.GetSubkeyNames(RegistryHive.Users, $"{sid}\\Software\\Microsoft\\Terminal Server Client\\Servers"); + if (subkeys == null) + { + continue; + } + + if (subkeys.Length <= 0) + { + continue; + } + + var connections = new List(); + + foreach (var host in subkeys) + { + var usernameHint = ThisRunTime.GetStringValue(RegistryHive.Users, + $"{sid}\\Software\\Microsoft\\Terminal Server Client\\Servers\\{host}", "UsernameHint"); + + var connection = new RDPConnection(); + connection.RemoteHost = host; + connection.UserNameHint = usernameHint; + connections.Add(connection); + } + + yield return new RDPSavedConnectionDTO() + { + SID = sid, + Connections = connections + }; + } + + yield break; + } + + internal class RDPSavedConnectionDTO : CommandDTOBase + { + public string SID { get; set; } + public List Connections { get; set; } + } + + [CommandOutputType(typeof(RDPSavedConnectionDTO))] + internal class RDPSavedConnectionCommandFormatter : TextFormatterBase + { + public RDPSavedConnectionCommandFormatter(ITextWriter writer) : base(writer) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + var dto = (RDPSavedConnectionDTO)result; + + if (dto.Connections.Count > 0) + { + WriteLine("Saved RDP Connection Information ({0})\n", dto.SID); + + WriteLine(" RemoteHost UsernameHint"); + WriteLine(" ---------- ------------"); + + foreach (var connection in dto.Connections) + { + WriteLine($" {connection.RemoteHost.PadRight(35)}{connection.UserNameHint}"); + } + WriteLine(); + } + } + } + } +} +#nullable enable \ No newline at end of file diff --git a/Seatbelt/Commands/Windows/RDPSessionsCommand.cs b/Seatbelt/Commands/Windows/RDPSessionsCommand.cs new file mode 100644 index 0000000..e213a88 --- /dev/null +++ b/Seatbelt/Commands/Windows/RDPSessionsCommand.cs @@ -0,0 +1,97 @@ +#nullable disable +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using static Seatbelt.Interop.Wtsapi32; + + +namespace Seatbelt.Commands.Windows +{ + internal class RDPSessionsCommand : CommandBase + { + public override string Command => "RDPSessions"; + public override string Description => "Current incoming RDP sessions (argument == computername to enumerate)"; + public override CommandGroup[] Group => new[] { CommandGroup.System, CommandGroup.Remote }; + public override bool SupportRemote => true; + public Runtime ThisRunTime; + + public RDPSessionsCommand(Runtime runtime) : base(runtime) + { + ThisRunTime = runtime; + } + + public override IEnumerable Execute(string[] args) + { + // adapted from http://www.pinvoke.net/default.aspx/wtsapi32.wtsenumeratesessions + string computerName = "localhost"; + + if (!String.IsNullOrEmpty(ThisRunTime.ComputerName)) + { + computerName = ThisRunTime.ComputerName; + } + else if (args.Length == 1) + { + computerName = args[0]; + } + + var server = WTSOpenServer(computerName); + + try + { + var ppSessionInfo = IntPtr.Zero; + var count = 0; + var level = 1; + var retval = WTSEnumerateSessionsEx(server, ref level, 0, ref ppSessionInfo, ref count); + var dataSize = Marshal.SizeOf(typeof(WTS_SESSION_INFO_1)); + var current = (long)ppSessionInfo; + + if (retval != 0) + { + for (var i = 0; i < count; i++) + { + var si = (WTS_SESSION_INFO_1)Marshal.PtrToStructure((IntPtr)current, typeof(WTS_SESSION_INFO_1)); + current += dataSize; + + // Now use WTSQuerySessionInformation to get the remote IP (if any) for the connection + + WTSQuerySessionInformation(server, (uint)si.SessionID, WTS_INFO_CLASS.WTSClientAddress, out var addressPtr, out _); + var address = (WTS_CLIENT_ADDRESS)Marshal.PtrToStructure(addressPtr, typeof(WTS_CLIENT_ADDRESS)); + + string sourceIp = null; + if (address.Address[2] != 0) + { + sourceIp = $"{address.Address[2]}.{address.Address[3]}.{address.Address[4]}.{address.Address[5]}"; + } + + yield return new RDPSessionsDTO() + { + SessionID = si.SessionID, + SessionName = si.pSessionName, + UserName = si.pUserName, + DomainName = si.pDomainName, + State = si.State, + SourceIp = sourceIp + }; + } + + WTSFreeMemory(ppSessionInfo); + } + } + finally + { + WTSCloseServer(server); + } + } + } + + internal class RDPSessionsDTO : CommandDTOBase + { + public int SessionID { get; set; } + public string SessionName { get; set; } + public string UserName { get; set; } + public string DomainName { get; set; } + public WTS_CONNECTSTATE_CLASS State { get; set; } + public string SourceIp { get; set; } + } +} +#nullable enable \ No newline at end of file diff --git a/Seatbelt/Commands/Windows/RPCMappedEndpointsCommand.cs b/Seatbelt/Commands/Windows/RPCMappedEndpointsCommand.cs new file mode 100644 index 0000000..4bf0153 --- /dev/null +++ b/Seatbelt/Commands/Windows/RPCMappedEndpointsCommand.cs @@ -0,0 +1,179 @@ +#nullable disable +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using Seatbelt.Output.Formatters; +using static Seatbelt.Interop.Rpcrt4; +using Seatbelt.Output.TextWriters; + + +namespace Seatbelt.Commands.Windows +{ + internal class RPCMappedEndpointsCommand : CommandBase + { + public override string Command => "RPCMappedEndpoints"; + public override string Description => "Current RPC endpoints mapped"; + public override CommandGroup[] Group => new[] { CommandGroup.Misc }; + public override bool SupportRemote => false; + + public RPCMappedEndpointsCommand(Runtime runtime) : base(runtime) + { + } + + public override IEnumerable Execute(string[] args) + { + // Ref - https://stackoverflow.com/questions/21805038/how-do-i-pinvoke-rpcmgmtepeltinqnext + + uint retCode; // RPC_S_OK + uint status; // RPC_S_OK + + var bindingHandle = IntPtr.Zero; + var inquiryContext = IntPtr.Zero; + var ifId = new RPC_IF_ID(); + string host = null; + + if (args.Length >= 1) + { + // if we're specifying a remote host via arguments + host = args[0]; + } + + try + { + // built the RPC binding string we're going to use + retCode = RpcStringBindingCompose(null, "ncacn_ip_tcp", host, null, null, out var stringBinding); + if (retCode != 0) + { + WriteError($"Bad return value from RpcStringBindingCompose : {retCode}"); + yield break; + } + + // create the actual RPC binding (from the binding string) + retCode = RpcBindingFromStringBinding(stringBinding, out bindingHandle); + if (retCode != 0) + { + WriteError($"Bad return value from RpcBindingFromStringBinding : {retCode}"); + yield break; + } + + // create an inquiry context for viewing the elements in an endpoint map + retCode = RpcMgmtEpEltInqBegin(bindingHandle, 0, 0, 0, 0, out inquiryContext); + if (retCode != 0) + { + WriteError($"Bad return value from RpcMgmtEpEltInqBegin : {retCode}"); + yield break; + } + + var prev = new Guid(); + var result = new RPCMappedEndpointsDTO(); + result.Elements = new List(); + + do + { + // iterate through all of the elements in the RPC endpoint map + status = RpcMgmtEpEltInqNext(inquiryContext, ref ifId, out var elementBindingHandle, 0, out var elementAnnotation); + + if (status == 0) + { + if (ifId.Uuid != prev) + { + if (prev != new Guid()) + { + + var result2 = new RPCMappedEndpointsDTO(); + result2 = result; + result = new RPCMappedEndpointsDTO(); + result.Elements = new List(); + + yield return result2; + } + + var annotation = Marshal.PtrToStringAuto(elementAnnotation); + result.UUID = ifId.Uuid; + result.Annotation = annotation; + + if (!String.IsNullOrEmpty(annotation)) + { + RpcStringFree(ref elementAnnotation); + } + + prev = ifId.Uuid; + } + if (elementBindingHandle != IntPtr.Zero) + { + var stringBinding2 = IntPtr.Zero; + status = RpcBindingToStringBinding(elementBindingHandle, out stringBinding2); + + if (status == 0) + { + var stringBindingStr = Marshal.PtrToStringAuto(stringBinding2); + result.Elements.Add(stringBindingStr); + + RpcStringFree(ref stringBinding2); + RpcBindingFree(ref elementBindingHandle); + } + else + { + // throw new Exception("[X] RpcBindingToStringBinding: " + retCode); + } + } + } + } + while (status == 0); + + yield return result; + } + finally + { + retCode = RpcMgmtEpEltInqDone(ref inquiryContext); + if (retCode != 0) + { + WriteError($"Bad return value from RpcMgmtEpEltInqDone : {retCode}"); + } + + retCode = RpcBindingFree(ref bindingHandle); + if (retCode != 0) + { + WriteError($"Bad return value from RpcBindingFree : {retCode}"); + } + } + } + } + + internal class RPCMappedEndpointsDTO : CommandDTOBase + { + public Guid UUID { get; set; } + + public string Annotation { get; set; } + + public List Elements { get; set; } + } + + [CommandOutputType(typeof(RPCMappedEndpointsDTO))] + internal class RPCMappedEndpointsTextFormatter : TextFormatterBase + { + public RPCMappedEndpointsTextFormatter(ITextWriter writer) : base(writer) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + var dto = (RPCMappedEndpointsDTO)result; + + if (!String.IsNullOrEmpty(dto.Annotation)) + { + WriteLine(" UUID: {0} ({1})", dto.UUID, dto.Annotation); + } + else + { + WriteLine(" UUID: {0}", dto.UUID); + } + + foreach (var element in dto.Elements) + { + WriteLine(" {0}", element); + } + } + } +} +#nullable enable \ No newline at end of file diff --git a/Seatbelt/Commands/Windows/RecycleBinCommand.cs b/Seatbelt/Commands/Windows/RecycleBinCommand.cs new file mode 100644 index 0000000..ea87ba6 --- /dev/null +++ b/Seatbelt/Commands/Windows/RecycleBinCommand.cs @@ -0,0 +1,92 @@ +#nullable disable +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Runtime.InteropServices; + +namespace Seatbelt.Commands.Windows +{ + internal class RecycleBinCommand : CommandBase + { + public override string Command => "RecycleBin"; + public override string Description => "Items in the Recycle Bin deleted in the last 30 days - only works from a user context!"; + public override CommandGroup[] Group => new[] { CommandGroup.Misc }; + public override bool SupportRemote => false; + + public RecycleBinCommand(Runtime runtime) : base(runtime) + { + } + + public override IEnumerable Execute(string[] args) + { + // lists recently deleted files (needs to be run from a user context!) + + // Reference: https://stackoverflow.com/questions/18071412/list-filenames-in-the-recyclebin-with-c-sharp-without-using-any-external-files + WriteHost("Recycle Bin Files Within the last 30 Days\n"); + + var lastDays = 30; + + var startTime = DateTime.Now.AddDays(-lastDays); + + // Shell COM object GUID + var shell = Type.GetTypeFromProgID("Shell.Application"); + var shellObj = Activator.CreateInstance(shell); + + // namespace for recycle bin == 10 - https://msdn.microsoft.com/en-us/library/windows/desktop/bb762494(v=vs.85).aspx + var recycle = shellObj.GetType().InvokeMember("Namespace", BindingFlags.InvokeMethod, null, shellObj, new object[] { 10 }); + // grab all the deletes items + var items = recycle.GetType().InvokeMember("Items", BindingFlags.InvokeMethod, null, recycle, null); + // grab the number of deleted items + var count = items.GetType().InvokeMember("Count", BindingFlags.GetProperty, null, items, null); + var deletedCount = int.Parse(count.ToString()); + + // iterate through each item + for (var i = 0; i < deletedCount; i++) + { + // grab the specific deleted item + var item = items.GetType().InvokeMember("Item", BindingFlags.InvokeMethod, null, items, new object[] { i }); + var DateDeleted = item.GetType().InvokeMember("ExtendedProperty", BindingFlags.InvokeMethod, null, item, new object[] { "System.Recycle.DateDeleted" }); + var modifiedDate = DateTime.Parse(DateDeleted.ToString()); + if (modifiedDate > startTime) + { + // additional extended properties from https://blogs.msdn.microsoft.com/oldnewthing/20140421-00/?p=1183 + var Name = item.GetType().InvokeMember("Name", BindingFlags.GetProperty, null, item, null); + var Path = item.GetType().InvokeMember("Path", BindingFlags.GetProperty, null, item, null); + var Size = item.GetType().InvokeMember("Size", BindingFlags.GetProperty, null, item, null); + var DeletedFrom = item.GetType().InvokeMember("ExtendedProperty", BindingFlags.InvokeMethod, null, item, new object[] { "System.Recycle.DeletedFrom" }); + + yield return new RecycleBinDTO() + { + Name = Name.ToString(), + Path = Path.ToString(), + Size = (int)Size, + DeletedFrom = DeletedFrom.ToString(), + DateDeleted = (DateTime)DateDeleted + }; + } + + Marshal.ReleaseComObject(item); + item = null; + } + + Marshal.ReleaseComObject(recycle); + recycle = null; + Marshal.ReleaseComObject(shellObj); + shellObj = null; + } + + internal class RecycleBinDTO : CommandDTOBase + { + public string Name { get; set; } + + public string Path { get; set; } + + public int Size { get; set; } + + public string DeletedFrom { get; set; } + + public DateTime DateDeleted { get; set; } + } + } +} +#nullable enable \ No newline at end of file diff --git a/Seatbelt/Commands/Windows/RegistryValueCommand.cs b/Seatbelt/Commands/Windows/RegistryValueCommand.cs new file mode 100644 index 0000000..a7dfa13 --- /dev/null +++ b/Seatbelt/Commands/Windows/RegistryValueCommand.cs @@ -0,0 +1,215 @@ +using Microsoft.Win32; +using Seatbelt.Output.Formatters; +using Seatbelt.Output.TextWriters; +using Seatbelt.Util; +using System; +using System.Collections.Generic; +using System.Text.RegularExpressions; + + +namespace Seatbelt.Commands.Windows +{ + // TODO: Handle x64 vs x86 hives + internal class RegistryValueCommand : CommandBase + { + public override string Command => "reg"; + public override string Description => @"Registry key values (HKLM\Software by default) argument == [Path] [intDepth] [Regex] [boolIgnoreErrors]"; + public override CommandGroup[] Group => new[] { CommandGroup.Misc }; + public override bool SupportRemote => false; // TODO remote + + //private string _rootKey; + //private string _rootParentKey; + + public RegistryValueCommand(Runtime runtime) : base(runtime) + { + } + + public override IEnumerable Execute(string[] args) + { + var hive = RegistryHive.LocalMachine; + var keyPath = "Software"; + var depth = 0; + var regex = new Regex("."); + var ignoreErrors = true; + var computer = ""; + + if (args.Length == 0) + { + depth = 1; + regex = new Regex("default"); + } + if (args.Length >= 1) + { + var separatorPos = args[0].IndexOf("\\"); + if (separatorPos == -1) // e.g. HKLM + { + hive = RegistryUtil.GetHive(args[0]); + keyPath = ""; + } + else if (separatorPos == args[0].Length) // e.g. HKLM\ + { + var hiveStr = args[0].Substring(0, separatorPos); + hive = RegistryUtil.GetHive(hiveStr); + keyPath = ""; + } + else // e.g. HKLM\Software + { + var hiveStr = args[0].Substring(0, separatorPos); + hive = RegistryUtil.GetHive(hiveStr); + keyPath = args[0].Substring(separatorPos + 1); + } + + + } + if (args.Length >= 2) + { + if (!int.TryParse(args[1], out depth)) + { + WriteError("Could not parse depth argument"); + } + } + if (args.Length >= 3) { regex = new Regex(args[2], RegexOptions.IgnoreCase); } + if (args.Length >= 4) { ignoreErrors = bool.Parse(args[3]); } + if (args.Length >= 5) { computer = args[4]; } + + + foreach (var output in EnumerateRootKey(computer, hive, keyPath, regex, depth, ignoreErrors)) + { + yield return output; + } + } + + private IEnumerable EnumerateRootKey(string computer, RegistryHive hive, string keyPath, Regex regex, int depth, bool ignoreErrors) + { + using var rootHive = RegistryKey.OpenRemoteBaseKey(hive, computer); + using var key = rootHive.OpenSubKey(keyPath); + + foreach (var output in EnumerateRegistryKey(key, regex, depth, ignoreErrors)) + { + yield return output; + } + } + + private IEnumerable EnumerateRegistryKey(RegistryKey key, Regex regex, int depth, bool ignoreErrors) + { + if (key == null) + { + throw new Exception("NullRegistryHive"); + } + + if (depth < 0) + { + yield break; + } + + var outputKeyPath = key.ToString() + .Replace("HKEY_LOCAL_MACHINE", "HKLM") + .Replace("HKEY_CURRENT_USER", "HKCU") + .Replace("HKEY_CLASSES_ROOT", "HKCR") + .Replace("HKEY_USERS", "HKU"); + + // 1) Handle key values + // Get the default value since GetValueNames doesn't always return it + var defaultValue = key.GetValue(""); + if (regex.IsMatch("default") || (regex.IsMatch(outputKeyPath) || (defaultValue != null && regex.IsMatch($"{defaultValue}")))) + { + yield return new RegistryValueDTO( + outputKeyPath, + "(default)", + defaultValue, + RegistryValueKind.String + ); + } + + foreach (var valueName in key.GetValueNames()) + { + if (valueName == null || valueName == "") + continue; + + var valueKind = key.GetValueKind(valueName); + var value = key.GetValue(valueName); + + // Skip the default value and non-matching valueNames + if (regex.IsMatch(valueName) || regex.IsMatch($"{value}")) + { + yield return new RegistryValueDTO( + outputKeyPath, + valueName, + value, + valueKind + ); + } + } + + // 2) Handle subkeys + foreach (var subkeyName in key.GetSubKeyNames()) + { + RegistryKey? subkey = null; + try + { + subkey = key.OpenSubKey(subkeyName); + } + catch (Exception e) + { + if (!ignoreErrors) + { + throw new Exception($"Error accessing {(key + "\\" + subkeyName)}: " + e); + } + } + + if (subkey == null) + continue; + + foreach (var result in EnumerateRegistryKey(subkey, regex, (depth - 1), ignoreErrors)) + { + yield return result; + } + } + } + } + + public class RegistryValueDTO : CommandDTOBase + { + public RegistryValueDTO(string key, string valueName, object value, object valueKind) + { + Key = key; + ValueName = valueName; + Value = value; + ValueKind = valueKind; + } + public string Key { get; } + public string ValueName { get; } + public object Value { get; } + public object ValueKind { get; } + } + + [CommandOutputType(typeof(RegistryValueDTO))] + internal class RegistryValueTextFormatter : TextFormatterBase + { + public RegistryValueTextFormatter(ITextWriter writer) : base(writer) + { + } + + //WriteLine("Registry Values"); + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + if (result == null) + { + return; + } + + var dto = (RegistryValueDTO)result; + + if ((int)dto.ValueKind == (int)RegistryValueKind.MultiString) + { + var values = (string[])dto.Value; + WriteLine($"{dto.Key} ! {dto.ValueName} :\n{String.Join("\n", values)}"); + } + else + { + WriteLine($"{dto.Key} ! {dto.ValueName} : {dto.Value}"); + } + } + } +} diff --git a/Seatbelt/Commands/Windows/ScheduledTasksCommand.cs b/Seatbelt/Commands/Windows/ScheduledTasksCommand.cs new file mode 100644 index 0000000..91eb46a --- /dev/null +++ b/Seatbelt/Commands/Windows/ScheduledTasksCommand.cs @@ -0,0 +1,352 @@ +#nullable disable +using System; +using System.Collections.Generic; +using System.Management; +using System.Text.RegularExpressions; +using static Seatbelt.Interop.Secur32; +using Seatbelt.Output.Formatters; +using Seatbelt.Output.TextWriters; + + +namespace Seatbelt.Commands.Windows +{ + enum StateEnum + { + Unknown = 0, + Disabled = 1, + Queued = 2, + Ready = 3, + Running = 4 + }; + + enum RunlevelEnum + { + TASK_RUNLEVEL_LUA = 0, + TASK_RUNLEVEL_HIGHEST = 1 + } + + class ScheduledTaskPrincipal + { + public string DisplayName { get; set; } + public string GroupId { get; set; } + public string Id { get; set; } + public string LogonType { get; set; } + //public string ProcessTokenSidType { get; set; } + //public string RequiredPrivilege { get; set; } + public string RunLevel { get; set; } + public string UserId { get; set; } + } + + class ScheduledTaskTrigger + { + public object Type { get; set; } + public object Enabled { get; set; } + public object EndBoundary { get; set; } + public object ExecutionTimeLimit { get; set; } + public object StartBoundary { get; set; } + public object Duration { get; set; } + public object Interval { get; set; } + public object StopAtDurationEnd { get; set; } + public Dictionary Properties { get; set; } + } + + class ScheduledTaskAction + { + public object Type { get; set; } + + public object Id { get; set; } + + public Dictionary Properties { get; set; } + } + + internal class ScheduledTasksCommand : CommandBase + { + public override string Command => "ScheduledTasks"; + //public override string Description => "ScheduledTasks (via WMI)"; + public override string Description => "Scheduled tasks (via WMI) that aren't authored by 'Microsoft', \"-full\" dumps all Scheduled tasks"; + public override CommandGroup[] Group => new[] { CommandGroup.Misc }; + public override bool SupportRemote => true; + public Runtime ThisRunTime; + + public ScheduledTasksCommand(Runtime runtime) : base(runtime) + { + ThisRunTime = runtime; + } + + public override IEnumerable Execute(string[] args) + { + WriteHost(Runtime.FilterResults ? "Non Microsoft scheduled tasks (via WMI)\n" : "All scheduled tasks (via WMI)\n"); + + ManagementObjectCollection data = null; + + try + { + var wmiData = ThisRunTime.GetManagementObjectSearcher(@"Root\Microsoft\Windows\TaskScheduler", "SELECT * FROM MSFT_ScheduledTask"); + data = wmiData.Get(); + } + catch (ManagementException ex) when (ex.ErrorCode == ManagementStatus.InvalidNamespace) + { + WriteError(string.Format(" [X] 'MSFT_ScheduledTask' WMI class unavailable (minimum supported versions of Windows: 8/2012)", ex.Message)); + } + catch (Exception ex) + { + WriteError(ex.ToString()); + } + + + if (data == null) + { + yield break; + } + + foreach (var o in data) + { + var result = (ManagementObject)o; + + if (Runtime.FilterResults) + { + if (String.IsNullOrEmpty($"{result["Author"]}") || Regex.IsMatch($"{result["Author"]}", "Microsoft")) + { + continue; + } + } + + var tempPrincipal = (ManagementBaseObject)result["Principal"]; + var settings = (ManagementBaseObject)result["Settings"]; + var actions = (ManagementBaseObject[])result["Actions"]; + var triggers = (ManagementBaseObject[])result["Triggers"]; + + var Principal = new ScheduledTaskPrincipal(); + Principal.DisplayName = $"{tempPrincipal["DisplayName"]}"; + Principal.Id = $"{tempPrincipal["Id"]}"; + Principal.GroupId = $"{tempPrincipal["GroupId"]}"; + var tempLogonType = $"{tempPrincipal["LogonType"]}"; + Principal.LogonType = $"{(SECURITY_LOGON_TYPE)Int32.Parse(tempLogonType)}"; + //Principal.ProcessTokenSidType = $"{tempPrincipal["ProcessTokenSidType"]}"; + //Principal.RequiredPrivilege = $"{tempPrincipal["RequiredPrivilege"]}"; + var tempRunLevel = $"{tempPrincipal["RunLevel"]}"; + Principal.RunLevel = $"{(RunlevelEnum)Int32.Parse(tempRunLevel)}"; + Principal.UserId = $"{tempPrincipal["UserId"]}"; + + var Actions = new List(); + foreach (var obj in actions) + { + var action = new ScheduledTaskAction(); + action.Type = $"{obj.SystemProperties["__SUPERCLASS"].Value}"; + + var Properties = new Dictionary(); + + foreach (var prop in obj.Properties) + { + if (!prop.Name.Equals("PSComputerName")) + { + Properties[prop.Name] = prop.Value; + } + } + action.Properties = Properties; + + Actions.Add(action); + } + + var TriggerObjects = new List(); + if (triggers != null) + { + foreach (var obj in triggers) + { + var trigger = new ScheduledTaskTrigger(); + trigger.Type = $"{obj.SystemProperties["__CLASS"].Value}"; + + // MSFT_TaskTrigger base properties + trigger.Enabled = obj["Enabled"]; + trigger.EndBoundary = obj["EndBoundary"]; + trigger.ExecutionTimeLimit = obj["ExecutionTimeLimit"]; + trigger.StartBoundary = obj["StartBoundary"]; + + var repetitionobj = (ManagementBaseObject)obj["repetition"]; + trigger.Duration = repetitionobj["Duration"]; + trigger.Interval = repetitionobj["Interval"]; + trigger.StopAtDurationEnd = repetitionobj["StopAtDurationEnd"]; + + // additional properties for subclasses + var properties = new Dictionary(); + foreach (var prop in obj.Properties) + { + //if(prop.Name + if (!Regex.IsMatch($"{prop.Name}", "Id|Enabled|EndBoundary|ExecutionTimeLimit|StartBoundary|Repetition")) + { + properties.Add(prop.Name, $"{prop.Value}"); + } + } + trigger.Properties = properties; + + TriggerObjects.Add(trigger); + } + } + + yield return new ScheduledTasksDTO() + { + Name = result["TaskName"], + Principal = Principal, + Author = result["Author"], + Description = result["Description"], + Source = result["Source"], + State = (StateEnum)result["State"], + SDDL = result["SecurityDescriptor"], + Actions = Actions, + Triggers = TriggerObjects, + Enabled = settings["Enabled"], + TaskPath = result["TaskPath"], + Hidden = settings["Hidden"], + Date = result["Date"], + AllowDemandStart = settings["AllowDemandStart"], + AllowHardTerminate = settings["AllowHardTerminate"], + DisallowStartIfOnBatteries = settings["DisallowStartIfOnBatteries"], + ExecutionTimeLimit = settings["ExecutionTimeLimit"], + StopIfGoingOnBatteries = settings["StopIfGoingOnBatteries"] + }; + } + + data.Dispose(); + } + } + + internal class ScheduledTasksDTO : CommandDTOBase + { + public object Name { get; set; } + + public ScheduledTaskPrincipal Principal { get; set; } + + public object Author { get; set; } + + public object Description { get; set; } + + public object Source { get; set; } + + public object State { get; set; } + + public object SDDL { get; set; } + + public List Actions { get; set; } + + public List Triggers { get; set; } + + public object Enabled { get; set; } + + public object TaskPath { get; set; } + + public object Hidden { get; set; } + + public object Date { get; set; } + + public object AllowDemandStart { get; set; } + + public object AllowHardTerminate { get; set; } + + public object DisallowStartIfOnBatteries { get; set; } + + public object ExecutionTimeLimit { get; set; } + + public object StopIfGoingOnBatteries { get; set; } + } + + [CommandOutputType(typeof(ScheduledTasksDTO))] + internal class ScheduledTasksFormatter : TextFormatterBase + { + public ScheduledTasksFormatter(ITextWriter writer) : base(writer) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + var dto = (ScheduledTasksDTO)result; + + WriteLine(" {0,-30} : {1}", "Name", dto.Name); + + WriteLine(" {0,-30} :", "Principal"); + //WriteLine(" {0,-30}: {1}", "DisplayName", dto.Principal.DisplayName); + WriteLine(" {0,-30}: {1}", "GroupId", dto.Principal.GroupId); + WriteLine(" {0,-30}: {1}", "Id", dto.Principal.Id); + WriteLine(" {0,-30}: {1}", "LogonType", dto.Principal.LogonType); + WriteLine(" {0,-30}: {1}", "RunLevel", dto.Principal.RunLevel); + WriteLine(" {0,-30}: {1}", "UserId", dto.Principal.UserId); + + WriteLine(" {0,-30} : {1}", "Author", dto.Author); + WriteLine(" {0,-30} : {1}", "Description", dto.Description); + WriteLine(" {0,-30} : {1}", "Source", dto.Source); + WriteLine(" {0,-30} : {1}", "State", dto.State); + WriteLine(" {0,-30} : {1}", "SDDL", dto.SDDL); + //WriteLine(" {0,-30} : {1}", "Actions", dto.State); + WriteLine(" {0,-30} : {1}", "Enabled", dto.Enabled); + WriteLine(" {0,-30} : {1}", "Date", (DateTime)Convert.ToDateTime(dto.Date)); + WriteLine(" {0,-30} : {1}", "AllowDemandStart", dto.AllowDemandStart); + //WriteLine(" {0,-30} : {1}", "AllowHardTerminate", dto.AllowHardTerminate); + WriteLine(" {0,-30} : {1}", "DisallowStartIfOnBatteries", dto.DisallowStartIfOnBatteries); + WriteLine(" {0,-30} : {1}", "ExecutionTimeLimit", dto.ExecutionTimeLimit); + WriteLine(" {0,-30} : {1}", "StopIfGoingOnBatteries", dto.StopIfGoingOnBatteries); + + WriteLine(" {0,-30} :", "Actions"); + WriteLine(" ------------------------------"); + foreach (var action in dto.Actions) + { + //WriteLine(" {0,-30}: {1}", "Id", action.Id); + WriteLine(" {0,-30}: {1}", "Type", action.Type); + foreach (var kvp in (Dictionary)action.Properties) + { + if (!String.IsNullOrEmpty($"{kvp.Value}")) + { + WriteLine(" {0,-30}: {1}", kvp.Key, kvp.Value); + } + } + WriteLine(" ------------------------------"); + } + + WriteLine(" {0,-30} :", "Triggers"); + WriteLine(" ------------------------------"); + foreach (var trigger in dto.Triggers) + { + WriteLine(" {0,-30}: {1}", "Type", trigger.Type); + WriteLine(" {0,-30}: {1}", "Enabled", trigger.Enabled); + if (!String.IsNullOrEmpty($"{trigger.StartBoundary}")) + { + WriteLine(" {0,-30}: {1}", "StartBoundary", trigger.StartBoundary); + } + if (!String.IsNullOrEmpty($"{trigger.EndBoundary}")) + { + WriteLine(" {0,-30}: {1}", "EndBoundary", trigger.EndBoundary); + } + if (!String.IsNullOrEmpty($"{trigger.ExecutionTimeLimit}")) + { + WriteLine(" {0,-30}: {1}", "ExecutionTimeLimit", trigger.ExecutionTimeLimit); + } + if (!String.IsNullOrEmpty($"{trigger.Duration}")) + { + WriteLine(" {0,-30}: {1}", "Duration", trigger.Duration); + } + if (!String.IsNullOrEmpty($"{trigger.Interval}")) + { + WriteLine(" {0,-30}: {1}", "Interval", trigger.Interval); + } + if (!String.IsNullOrEmpty($"{trigger.StopAtDurationEnd}")) + { + WriteLine(" {0,-30}: {1}", "StopAtDurationEnd", trigger.StopAtDurationEnd); + } + + if (trigger.Properties != null) + { + foreach (var kvp in trigger.Properties) + { + if (!String.IsNullOrEmpty(kvp.Value)) + { + WriteLine(" {0,-30}: {1}", kvp.Key, kvp.Value); + } + } + } + + WriteLine(" ------------------------------"); + } + + WriteLine(); + } + } +} +#nullable enable \ No newline at end of file diff --git a/Seatbelt/Commands/Windows/SecurityPackagesCommand.cs b/Seatbelt/Commands/Windows/SecurityPackagesCommand.cs new file mode 100644 index 0000000..58ae6f9 --- /dev/null +++ b/Seatbelt/Commands/Windows/SecurityPackagesCommand.cs @@ -0,0 +1,70 @@ +#nullable disable +using System; +using System.Collections.Generic; +using static Seatbelt.Interop.Secur32; +using System.Runtime.InteropServices; + +namespace Seatbelt.Commands.Windows +{ + internal class SecurityPackagesCommand : CommandBase + { + public override string Command => "SecurityPackages"; + public override string Description => "Enumerates the security packages currently available using EnumerateSecurityPackagesA()"; + public override CommandGroup[] Group => new[] {CommandGroup.Misc}; + public override bool SupportRemote => false; + + public SecurityPackagesCommand(Runtime runtime) : base(runtime) + { + } + + public override IEnumerable Execute(string[] args) + { + // this code was partially adapted from Chris Haas' post at https://stackoverflow.com/a/5941873 + + WriteHost("Security Packages\n\n"); + + var securityPackages = new List(); + + var ret = EnumerateSecurityPackages(out var pcPackages, out var ppPackageInfo); + + var ppPackageInfoItr = ppPackageInfo; + + for (ulong i = 0; i < pcPackages; i++) + { + var packageInfo = (SecPkgInfo)Marshal.PtrToStructure(ppPackageInfoItr, typeof(SecPkgInfo)); + + var securityPackage = new SecurityPackagesDTO() + { + Name = packageInfo.Name.ToString(), + Comment = packageInfo.Comment.ToString(), + Capabilities = packageInfo.fCapabilities, + MaxToken = packageInfo.cbMaxToken, + RPCID = packageInfo.wRPCID, + Version = packageInfo.wVersion + }; + + securityPackages.Add(securityPackage); + + ppPackageInfoItr = (IntPtr)((long)ppPackageInfoItr.ToInt64() + Marshal.SizeOf(typeof(SecPkgInfo))); + } + + foreach (var securityPackage in securityPackages) + { + yield return securityPackage; + } + + FreeContextBuffer(ppPackageInfo); + } + + internal class SecurityPackagesDTO : CommandDTOBase + { + public string Name { get; set; } + public string Comment { get; set; } + public SECPKG_FLAGS Capabilities { get; set; } + public uint MaxToken { get; set; } + public short RPCID { get; set; } + public short Version { get; set; } + } + } +} +#nullable enable \ No newline at end of file diff --git a/Seatbelt/Commands/Windows/ServicesCommand.cs b/Seatbelt/Commands/Windows/ServicesCommand.cs new file mode 100644 index 0000000..6cade83 --- /dev/null +++ b/Seatbelt/Commands/Windows/ServicesCommand.cs @@ -0,0 +1,245 @@ +#nullable disable +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Management; +using System.Text.RegularExpressions; +using Microsoft.Win32; +using Seatbelt.Util; + +namespace Seatbelt.Commands.Windows +{ + internal class ServicesCommand : CommandBase + { + public override string Command => "Services"; + public override string Description => "Services with file info company names that don't contain 'Microsoft', \"-full\" dumps all processes"; + public override CommandGroup[] Group => new[] {CommandGroup.System}; + public override bool SupportRemote => false; // tracking back some of the service stuff needs local API calls + + public ServicesCommand(Runtime runtime) : base(runtime) + { + } + + public override IEnumerable Execute(string[] args) + { + // lists installed servics that don't have "Microsoft Corporation" as the company name in their file info + // or all services if "-full" is passed + + WriteHost(Runtime.FilterResults ? "Non Microsoft Services (via WMI)\n" : "All Services (via WMI)\n"); + + var wmiData = new ManagementObjectSearcher(@"root\cimv2", "SELECT * FROM win32_service"); + var data = wmiData.Get(); + + foreach (ManagementObject result in data) + { + var serviceName = result["Name"] == null ? null : (string)result["Name"]; + string serviceDll = null; + string serviceCommand = null; + string binaryPath = null; + var isDotNet = false; + string companyName = null; + string binaryPathSDDL = null; + string serviceSDDL = null; + + serviceCommand = GetServiceCommand(result); + serviceDll = GetServiceDll(serviceName); + + binaryPath = GetServiceBinaryPath(serviceCommand); + + // ServiceDll could be null if access to the Parameters key is denied + // - Examples: The lmhosts service on Win10 as an unprivileged user + if (binaryPath.ToLower().EndsWith("\\svchost.exe") && serviceDll != null) + { + binaryPath = serviceDll; + } + + if (!string.IsNullOrEmpty(binaryPath) && File.Exists(binaryPath)) + { + companyName = GetCompanyName(binaryPath); + + if (Runtime.FilterResults) + { + if (string.IsNullOrEmpty(companyName) || (companyName != null && Regex.IsMatch(companyName, @"^Microsoft.*", RegexOptions.IgnoreCase))) + { + continue; + } + } + + isDotNet = FileUtil.IsDotNetAssembly(binaryPath); + + binaryPathSDDL = File.GetAccessControl(binaryPath).GetSecurityDescriptorSddlForm(System.Security.AccessControl.AccessControlSections.All); + } + + try + { + var info = SecurityUtil.GetSecurityInfos(serviceName, Interop.Advapi32.SE_OBJECT_TYPE.SE_SERVICE); + serviceSDDL = info.SDDL; + } + catch + { + // eat it + } + + yield return new ServicesDTO() + { + Name = serviceName, + DisplayName = (string)result["DisplayName"], + Description = (string)result["Description"], + User = (string)result["StartName"], + State = (string)result["State"], + StartMode = (string)result["StartMode"], + ServiceCommand = serviceCommand, + BinaryPath = binaryPath, + BinaryPathSDDL = binaryPathSDDL, + ServiceDll = serviceDll, + ServiceSDDL = serviceSDDL, + CompanyName = companyName, + IsDotNet = isDotNet + }; + } + + // yield return null; + } + + private string? GetCompanyName(string path) + { + try + { + var myFileVersionInfo = FileVersionInfo.GetVersionInfo(path); + return myFileVersionInfo.CompanyName; + } + catch + { + return null; + } + } + + private string? GetServiceDll(string serviceName) + { + // ServiceDll's can be at the following locations + // - HKLM\\SYSTEM\\CurrentControlSet\\Services\\ ! ServiceDll + // - Ex: DoSvc on Win10 + // - HKLM\\SYSTEM\\CurrentControlSet\\Services\\Parameters ! ServiceDll + // - Ex: DnsCache on Win10 + + string path = null; + + try + { + path = RegistryUtil.GetStringValue(RegistryHive.LocalMachine, $"SYSTEM\\CurrentControlSet\\Services\\{serviceName}\\Parameters", "ServiceDll"); + } + catch + { + } + + if (path == null) + { + try + { + path = RegistryUtil.GetStringValue(RegistryHive.LocalMachine, $"SYSTEM\\CurrentControlSet\\Services\\{serviceName}", "ServiceDll"); + } + catch + { + } + } + + return path; + } + + private string? GetServiceCommandFromRegistry(string serviceName) + { + try + { + return RegistryUtil.GetStringValue(RegistryHive.LocalMachine, $"SYSTEM\\CurrentControlSet\\Services\\{serviceName}", "ImagePath"); + } + catch + { + return null; + } + } + + // TODO: Parsing binary paths is hard... + // - 1) We don't account for PATHEXT + // - Example image path: C:\windows\system32\cmd + // - 2) We don't account for the PATH environment variable + // - Example image path: cmd.exe + // - Example image path: cmd (combination of 1 & 2) + // - 3) We don't account for per-user services in Win 10 (see https://docs.microsoft.com/en-us/windows/application-management/per-user-services-in-windows) + private string GetServiceBinaryPath(string command) + { + //// The "Path Name" for a service can include a fully quoted path (that includes spaces), as well as + //// Program arguments (such as the ones that live inside svchost). Some paths, such as Carbon Black's agent) + //// don't even have a file extension. So it's fair to say that if there are quotes, we'll take what's inside + //// them, otherwise we'll split on spaces and take the first entry, regardless of its extension). + //// Example: "C:\Program Files\Windows Defender\MsMpEng.exe" + //if (command.StartsWith("\"")) + //{ + // // Quotes are present, so split on quotes. Given that this is a service path, + // // it's fair to assume that the path is valid (otherwise the service wouldn't + // // be installed) and so we can just rip out the bit between the quotes. This + // // split should result in a minimum of 2 parts, so taking the second should + // // give us what we need. + // return command.Split('"')[1]; + //} + //else + //{ + // // Exmaple image paths we have to deal with: + // // 1) C:\Program Files\Windows Identity Foundation\v3.5\c2wtshost.exe + // // 2) C:\WINDOWS\system32\msiexec.exe /V + // // 3) C:\WINDOWS\system32\svchost.exe -k appmodel -p + // if (File.Exists(command)) // Case 1 + // { + // return command; + // } + // else // Case 2 & 3 + // { + // return command.Split(' ')[0]; + // } + //} + + var path = Regex.Match(command, @"^\W*([a-z]:\\.+?(\.exe|\.dll|\.sys))\W*", RegexOptions.IgnoreCase); + return path.Groups[1].ToString(); + } + + private string? GetServiceCommand(ManagementObject result) + { + // Get the service's path. Sometimes result["PathName"] is not populated, so + // in those cases we'll try and get the value from the registry. The converse is + // also true - sometimes we can't acccess a registry key, but result["PathName"] + // is populated + string serviceCommand = null; + if (result["PathName"] != null) + { + serviceCommand = ((string)result["PathName"]).Trim(); + if (serviceCommand == string.Empty) + { + serviceCommand = GetServiceCommandFromRegistry((string)result["Name"]); + } + } + else + { + serviceCommand = GetServiceCommandFromRegistry((string)result["Name"]); + } + + return serviceCommand; + } + } + + internal class ServicesDTO : CommandDTOBase + { + public string Name { get; set; } + public string DisplayName { get; set; } + public string Description { get; set; } + public string User { get; set; } + public string State { get; set; } + public string StartMode { get; set; } + public string ServiceCommand { get; set; } + public string BinaryPath { get; set; } + public string BinaryPathSDDL { get; set; } + public string ServiceDll { get; set; } + public string ServiceSDDL { get; set; } + public string CompanyName { get; set; } + public bool IsDotNet { get; set; } + } +} +#nullable enable \ No newline at end of file diff --git a/Seatbelt/Commands/Windows/TCPConnectionsCommand.cs b/Seatbelt/Commands/Windows/TCPConnectionsCommand.cs new file mode 100644 index 0000000..fd49ff8 --- /dev/null +++ b/Seatbelt/Commands/Windows/TCPConnectionsCommand.cs @@ -0,0 +1,159 @@ +using Seatbelt.Interop; +using System; +using System.Collections.Generic; +using System.Management; +using System.Runtime.InteropServices; +using Seatbelt.Output.Formatters; +using static Seatbelt.Interop.Iphlpapi; +using Seatbelt.Output.TextWriters; + + +namespace Seatbelt.Commands.Windows +{ + internal class TcpConnectionsCommand : CommandBase + { + public override string Command => "TcpConnections"; + public override string Description => "Current TCP connections and their associated processes and services"; + public override CommandGroup[] Group => new[] {CommandGroup.System}; + public override bool SupportRemote => false; + + public TcpConnectionsCommand(Runtime runtime) : base(runtime) + { + } + + public override IEnumerable Execute(string[] args) + { + var AF_INET = 2; // IP_v4 + uint tableBufferSize = 0; + var tableBuffer = IntPtr.Zero; + var rowPtr = IntPtr.Zero; + var processes = new Dictionary(); + + WriteHost(" Local Address Foreign Address State PID Service ProcessName"); + + try + { + // Adapted from https://stackoverflow.com/questions/577433/which-pid-listens-on-a-given-port-in-c-sharp/577660#577660 + // Build a PID -> process name lookup table + var searcher = new ManagementObjectSearcher("SELECT * FROM Win32_Process"); + var retObjectCollection = searcher.Get(); + + foreach (ManagementObject Process in retObjectCollection) + { + if (Process["CommandLine"] != null) + { + processes.Add(Process["ProcessId"].ToString(), Process["CommandLine"].ToString()); + } + else + { + processes.Add(Process["ProcessId"].ToString(), Process["Name"].ToString()); + } + } + + // Figure out how much memory we need for the result struct + var ret = GetExtendedTcpTable(IntPtr.Zero, ref tableBufferSize, true, AF_INET, TCP_TABLE_CLASS.TCP_TABLE_OWNER_MODULE_ALL, 0); + if (ret != Win32Error.Success && ret != Win32Error.InsufficientBuffer) + { + // 122 == insufficient buffer size + WriteError($"Bad check value from GetExtendedTcpTable : {ret}"); + yield break; + } + + tableBuffer = Marshal.AllocHGlobal((int)tableBufferSize); + + ret = GetExtendedTcpTable(tableBuffer, ref tableBufferSize, true, AF_INET, TCP_TABLE_CLASS.TCP_TABLE_OWNER_MODULE_ALL, 0); + if (ret != Win32Error.Success) + { + WriteError($"Bad return value from GetExtendedTcpTable : {ret}"); + yield break; + } + + // get the number of entries in the table + var ownerModuleTable = (MIB_TCPTABLE_OWNER_MODULE)Marshal.PtrToStructure(tableBuffer, typeof(MIB_TCPTABLE_OWNER_MODULE)); + rowPtr = (IntPtr)(tableBuffer.ToInt64() + Marshal.OffsetOf(typeof(MIB_TCPTABLE_OWNER_MODULE), "Table").ToInt64()); + var TcpRows = new MIB_TCPROW_OWNER_MODULE[ownerModuleTable.NumEntries]; + + for (var i = 0; i < ownerModuleTable.NumEntries; i++) + { + var tcpRow = + (MIB_TCPROW_OWNER_MODULE)Marshal.PtrToStructure(rowPtr, typeof(MIB_TCPROW_OWNER_MODULE)); + TcpRows[i] = tcpRow; + // next entry + rowPtr = (IntPtr)((long)rowPtr + Marshal.SizeOf(tcpRow)); + } + + foreach (var entry in TcpRows) + { + string? processName = null; + try + { + processName = processes[entry.OwningPid.ToString()]; + } + catch { } + + var serviceName = Advapi32.GetServiceNameFromTag(entry.OwningPid, (uint)entry.OwningModuleInfo0); + + + yield return new TcpConnectionsDTO( + entry.LocalAddress.ToString(), + entry.LocalPort, + entry.RemoteAddress.ToString(), + entry.RemotePort, + entry.State, + entry.OwningPid, + serviceName, + processName + ); + } + } + finally + { + if (tableBuffer != IntPtr.Zero) + { + Marshal.FreeHGlobal(tableBuffer); + } + } + } + } + + internal class TcpConnectionsDTO : CommandDTOBase + { + public TcpConnectionsDTO(string localAddress, ushort localPort, string remoteAddress, ushort remotePort, MIB_TCP_STATE state, uint processId, string? service, string? command) + { + LocalAddress = localAddress; + LocalPort = localPort; + RemoteAddress = remoteAddress; + RemotePort = remotePort; + State = state; + ProcessId = processId; + Service = service; + Command = command; + } + + public string LocalAddress { get; } + public ushort LocalPort { get; } + public string RemoteAddress { get; } + public ushort RemotePort { get; } + public MIB_TCP_STATE State { get; } + public uint ProcessId { get; } + public string? Service { get; } + public string? Command { get; } + } + + [CommandOutputType(typeof(TcpConnectionsDTO))] + internal class TcpConnectionsTextFormatter : TextFormatterBase + { + public TcpConnectionsTextFormatter(ITextWriter writer) : base(writer) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + if (result != null) + { + var dto = (TcpConnectionsDTO)result; + WriteLine(" {0,-23}{1,-23}{2,-11}{3,-6}{4,-15} {5}", dto.LocalAddress + ":" + dto.LocalPort, dto.RemoteAddress + ":" + dto.RemotePort, dto.State, dto.ProcessId, dto.Service, dto.Command); + } + } + } +} diff --git a/Seatbelt/Commands/Windows/TokenGroupCommand.cs b/Seatbelt/Commands/Windows/TokenGroupCommand.cs new file mode 100644 index 0000000..128095d --- /dev/null +++ b/Seatbelt/Commands/Windows/TokenGroupCommand.cs @@ -0,0 +1,68 @@ +#nullable disable +using System.Collections.Generic; +using System.Security.Principal; +using Seatbelt.Output.Formatters; +using Seatbelt.Output.TextWriters; + + +namespace Seatbelt.Commands.Windows +{ + internal class TokenGroupCommand : CommandBase + { + public override string Command => "TokenGroups"; + public override string Description => "The current token's local and domain groups"; + public override CommandGroup[] Group => new[] { CommandGroup.User }; + public override bool SupportRemote => false; + + public TokenGroupCommand(Runtime runtime) : base(runtime) + { + } + + public override IEnumerable Execute(string[] args) + { + WriteHost("Current Token's Groups\n"); + + var wi = WindowsIdentity.GetCurrent(); + + foreach (var group in wi.Groups) + { + var groupName = ""; + + try + { + groupName = group.Translate(typeof(NTAccount)).ToString(); + } + catch { } + + yield return new TokenGroupsDTO() + { + GroupSID = $"{(SecurityIdentifier)group}", + GroupName = groupName + }; + } + } + } + + internal class TokenGroupsDTO : CommandDTOBase + { + //public System.Security.Principal.SecurityIdentifier GroupSID { get; set; } + public string GroupSID { get; set; } + public string GroupName { get; set; } + } + + [CommandOutputType(typeof(TokenGroupsDTO))] + internal class TokenGroupsTextFormatter : TextFormatterBase + { + public TokenGroupsTextFormatter(ITextWriter writer) : base(writer) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + var dto = (TokenGroupsDTO)result; + + WriteLine(" {0,-40} {1}", dto.GroupName, dto.GroupSID); + } + } +} +#nullable enable \ No newline at end of file diff --git a/Seatbelt/Commands/Windows/TokenPrivilegesCommand.cs b/Seatbelt/Commands/Windows/TokenPrivilegesCommand.cs new file mode 100644 index 0000000..fc21eae --- /dev/null +++ b/Seatbelt/Commands/Windows/TokenPrivilegesCommand.cs @@ -0,0 +1,91 @@ +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Security.Principal; +using static Seatbelt.Interop.Advapi32; +using static Seatbelt.Interop.Secur32; +using Seatbelt.Output.Formatters; +using Seatbelt.Output.TextWriters; + + +namespace Seatbelt.Commands.Windows +{ + // TODO: Most privileges are disabled by default. Better to move this to SharpUp? + internal class TokenPrivilegesCommand : CommandBase + { + public override string Command => "TokenPrivileges"; + public override string Description => "Currently enabled token privileges (e.g. SeDebugPrivilege/etc.)"; + public override CommandGroup[] Group => new[] {CommandGroup.System}; + public override bool SupportRemote => false; + + public TokenPrivilegesCommand(Runtime runtime) : base(runtime) + { + } + + public override IEnumerable Execute(string[] args) + { + // Returns all privileges that the current process/user possesses + // adapted from https://stackoverflow.com/questions/4349743/setting-size-of-token-privileges-luid-and-attributes-array-returned-by-gettokeni + + WriteHost("Current Token's Privileges\n"); + + var TokenInfLength = 0; + var ThisHandle = WindowsIdentity.GetCurrent().Token; + GetTokenInformation(ThisHandle, TOKEN_INFORMATION_CLASS.TokenPrivileges, IntPtr.Zero, TokenInfLength, out TokenInfLength); + var TokenInformation = Marshal.AllocHGlobal(TokenInfLength); + if (GetTokenInformation(WindowsIdentity.GetCurrent().Token, TOKEN_INFORMATION_CLASS.TokenPrivileges, TokenInformation, TokenInfLength, out TokenInfLength)) + { + var ThisPrivilegeSet = (TOKEN_PRIVILEGES)Marshal.PtrToStructure(TokenInformation, typeof(TOKEN_PRIVILEGES)); + for (var index = 0; index < ThisPrivilegeSet.PrivilegeCount; index++) + { + var laa = ThisPrivilegeSet.Privileges[index]; + var StrBuilder = new System.Text.StringBuilder(); + var luidNameLen = 0; + var luidPointer = Marshal.AllocHGlobal(Marshal.SizeOf(laa.Luid)); + Marshal.StructureToPtr(laa.Luid, luidPointer, true); + LookupPrivilegeName(null, luidPointer, null, ref luidNameLen); + StrBuilder.EnsureCapacity(luidNameLen + 1); + if (LookupPrivilegeName(null, luidPointer, StrBuilder, ref luidNameLen)) + { + var strPrivilege = StrBuilder.ToString(); + var strAttributes = String.Format("{0}", (LuidAttributes)laa.Attributes); + Marshal.FreeHGlobal(luidPointer); + + yield return new TokenPrivilegesDTO( + strPrivilege, + strAttributes + ); + } + + } + } + } + } + + internal class TokenPrivilegesDTO : CommandDTOBase + { + public TokenPrivilegesDTO(string privilege, string attributes) + { + Privilege = privilege; + Attributes = attributes; + } + + public string Privilege { get; set; } + public string Attributes { get; set; } + } + + [CommandOutputType(typeof(TokenPrivilegesDTO))] + internal class TokenPrivilegesTextFormatter : TextFormatterBase + { + public TokenPrivilegesTextFormatter(ITextWriter writer) : base(writer) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + var dto = (TokenPrivilegesDTO)result; + + WriteLine(" {0,43}: {1}", dto.Privilege, dto.Attributes); + } + } +} diff --git a/Seatbelt/Commands/Windows/UDPConnectionsCommand.cs b/Seatbelt/Commands/Windows/UDPConnectionsCommand.cs new file mode 100644 index 0000000..45793fe --- /dev/null +++ b/Seatbelt/Commands/Windows/UDPConnectionsCommand.cs @@ -0,0 +1,150 @@ +using Seatbelt.Interop; +using System; +using System.Collections.Generic; +using System.Management; +using System.Runtime.InteropServices; +using Seatbelt.Output.Formatters; +using static Seatbelt.Interop.Iphlpapi; +using Seatbelt.Output.TextWriters; + + +namespace Seatbelt.Commands.Windows +{ + internal class UdpConnectionsCommand : CommandBase + { + public override string Command => "UdpConnections"; + public override string Description => "Current UDP connections and associated processes and services"; + public override CommandGroup[] Group => new[] {CommandGroup.System}; + public override bool SupportRemote => false; + + public UdpConnectionsCommand(Runtime runtime) : base(runtime) + { + } + + public override IEnumerable Execute(string[] args) + { + var AF_INET = 2; // IP_v4 + uint tableBufferSize = 0; + var tableBuffer = IntPtr.Zero; + var rowPtr = IntPtr.Zero; + var processes = new Dictionary(); + + WriteHost(" Local Address PID Service ProcessName"); + + try + { + // Adapted from https://stackoverflow.com/questions/577433/which-pid-listens-on-a-given-port-in-c-sharp/577660#577660 + // Build a PID -> process name lookup table + var searcher = new ManagementObjectSearcher("SELECT * FROM Win32_Process"); + var retObjectCollection = searcher.Get(); + + foreach (ManagementObject Process in retObjectCollection) + { + if (Process["CommandLine"] != null) + { + processes.Add(Process["ProcessId"].ToString(), Process["CommandLine"].ToString()); + } + else + { + processes.Add(Process["ProcessId"].ToString(), Process["Name"].ToString()); + } + } + + // Figure out how much memory we need for the result struct + + var ret = GetExtendedUdpTable(IntPtr.Zero, ref tableBufferSize, true, AF_INET, UDP_TABLE_CLASS.UDP_TABLE_OWNER_MODULE, 0); + if (ret != Win32Error.Success && ret != Win32Error.InsufficientBuffer) + { + // 122 == insufficient buffer size + WriteError($"Bad check value from GetExtendedUdpTable : {ret}"); + yield break; + } + + tableBuffer = Marshal.AllocHGlobal((int)tableBufferSize); + + ret = GetExtendedUdpTable(tableBuffer, ref tableBufferSize, true, AF_INET, UDP_TABLE_CLASS.UDP_TABLE_OWNER_MODULE, 0); + if (ret != Win32Error.Success) + { + WriteError($"Bad return value from GetExtendedUdpTable : {ret}"); + yield break; + } + + //// get the number of entries in the table + var ownerModuleTable = (MIB_UDPTABLE_OWNER_MODULE)Marshal.PtrToStructure(tableBuffer, typeof(MIB_UDPTABLE_OWNER_MODULE)); + rowPtr = (IntPtr)(tableBuffer.ToInt64() + Marshal.OffsetOf(typeof(MIB_UDPTABLE_OWNER_MODULE), "Table").ToInt64()); + var UdpRows = new MIB_UDPROW_OWNER_MODULE[ownerModuleTable.NumEntries]; + + for (var i = 0; i < ownerModuleTable.NumEntries; i++) + { + var udpRow = + (MIB_UDPROW_OWNER_MODULE)Marshal.PtrToStructure(rowPtr, typeof(MIB_UDPROW_OWNER_MODULE)); + UdpRows[i] = udpRow; + // next entry + rowPtr = (IntPtr)((long)rowPtr + Marshal.SizeOf(udpRow)); + } + + foreach (var entry in UdpRows) + { + var processName = ""; + try + { + processName = processes[entry.OwningPid.ToString()]; + } + catch { } + + var serviceName = Advapi32.GetServiceNameFromTag(entry.OwningPid, (uint)entry.OwningModuleInfo0); + + yield return new UdpConnectionsDTO( + entry.LocalAddress.ToString(), + entry.LocalPort, + entry.OwningPid, + serviceName, + processName + ); + } + } + finally + { + if (tableBuffer != IntPtr.Zero) + { + Marshal.FreeHGlobal(tableBuffer); + } + } + + } + } + + internal class UdpConnectionsDTO : CommandDTOBase + { + public UdpConnectionsDTO(string localAddress, ushort localPort, uint processId, string? service, string processName) + { + LocalAddress = localAddress; + LocalPort = localPort; + ProcessId = processId; + Service = service; + ProcessName = processName; + } + public string LocalAddress { get; set; } + public ushort LocalPort { get; set; } + public uint ProcessId { get; set; } + public string? Service { get; set; } + public string ProcessName { get; set; } + } + + [CommandOutputType(typeof(UdpConnectionsDTO))] + internal class UdpConnectionsTextFormatter : TextFormatterBase + { + public UdpConnectionsTextFormatter(ITextWriter writer) : base(writer) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + if (result != null) + { + var dto = (UdpConnectionsDTO)result; + WriteLine(" {0,-23}{1,-7}{2,-23} {3}", dto.LocalAddress + ":" + dto.LocalPort, dto.ProcessId, dto.Service, dto.ProcessName); + } + } + } +} diff --git a/Seatbelt/Commands/Windows/UserAccountControlCommand.cs b/Seatbelt/Commands/Windows/UserAccountControlCommand.cs new file mode 100644 index 0000000..7ec310f --- /dev/null +++ b/Seatbelt/Commands/Windows/UserAccountControlCommand.cs @@ -0,0 +1,123 @@ +#nullable disable +using System.Collections.Generic; +using Microsoft.Win32; +using Seatbelt.Output.Formatters; +using Seatbelt.Output.TextWriters; + + +namespace Seatbelt.Commands.Windows +{ + internal class UserAccountControlCommand : CommandBase + { + public override string Command => "UAC"; + public override string Description => "UAC system policies via the registry"; + public override CommandGroup[] Group => new[] {CommandGroup.System}; + public override bool SupportRemote => true; + public Runtime ThisRunTime; + + public UserAccountControlCommand(Runtime runtime) : base(runtime) + { + ThisRunTime = runtime; + } + + public override IEnumerable Execute(string[] args) + { + // dump out various UAC system policies + + var consentPromptBehaviorAdmin = ThisRunTime.GetDwordValue(RegistryHive.LocalMachine, "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\System", "ConsentPromptBehaviorAdmin"); + + var enableLua = ThisRunTime.GetDwordValue(RegistryHive.LocalMachine, @"SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System", "EnableLUA"); + + var localAccountTokenFilterPolicy = ThisRunTime.GetDwordValue(RegistryHive.LocalMachine, "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\System", "LocalAccountTokenFilterPolicy"); + + var filterAdministratorToken = ThisRunTime.GetDwordValue(RegistryHive.LocalMachine, "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\System", "FilterAdministratorToken"); + + yield return new UserAccountControlDTO() + { + ConsentPromptBehaviorAdmin = consentPromptBehaviorAdmin.ToString(), + EnableLua = enableLua.ToString(), + FilterAdministratorToken = filterAdministratorToken.ToString(), + LocalAccountTokenFilterPolicy = localAccountTokenFilterPolicy.ToString() + }; + } + + class UserAccountControlDTO : CommandDTOBase + { + public string ConsentPromptBehaviorAdmin { get; set; } + + public string EnableLua { get; set; } + + public string FilterAdministratorToken { get; set; } + + public string LocalAccountTokenFilterPolicy { get; set; } + } + + [CommandOutputType(typeof(UserAccountControlDTO))] + internal class UserAccountControlFormatter : TextFormatterBase + { + public UserAccountControlFormatter(ITextWriter writer) : base(writer) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + var dto = (UserAccountControlDTO)result; + + switch (dto.ConsentPromptBehaviorAdmin) + { + case "0": + WriteLine(" {0,-30} : {1} - No prompting", "ConsentPromptBehaviorAdmin", dto.ConsentPromptBehaviorAdmin); + break; + case "1": + WriteLine(" {0,-30} : {1} - PromptOnSecureDesktop", "ConsentPromptBehaviorAdmin", dto.ConsentPromptBehaviorAdmin); + break; + case "2": + WriteLine(" {0,-30} : {1} - PromptPermitDenyOnSecureDesktop", "ConsentPromptBehaviorAdmin", dto.ConsentPromptBehaviorAdmin); + break; + case "3": + WriteLine(" {0,-30} : {1} - PromptForCredsNotOnSecureDesktop", "ConsentPromptBehaviorAdmin", dto.ConsentPromptBehaviorAdmin); + break; + case "4": + WriteLine(" {0,-30} : {1} - PromptForPermitDenyNotOnSecureDesktop", "ConsentPromptBehaviorAdmin", dto.ConsentPromptBehaviorAdmin); + break; + case "5": + WriteLine(" {0,-30} : {1} - PromptForNonWindowsBinaries", "ConsentPromptBehaviorAdmin", dto.ConsentPromptBehaviorAdmin); + break; + default: + WriteLine(" {0,-30} : PromptForNonWindowsBinaries", "ConsentPromptBehaviorAdmin"); + break; + } + + + WriteLine(" {0,-30} : {1}", "EnableLUA (Is UAC enabled?)", dto.EnableLua); + + if (!dto.EnableLua.Equals("1")) + { + WriteLine(" [*] EnableLUA != 1, UAC policies disabled.\n [*] Any local account can be used for lateral movement."); + } + + + WriteLine(" {0,-30} : {1}", "LocalAccountTokenFilterPolicy", dto.LocalAccountTokenFilterPolicy); + if (dto.EnableLua.Equals("1") && dto.LocalAccountTokenFilterPolicy.Equals("1")) + { + WriteLine(" [*] LocalAccountTokenFilterPolicy set to 1.\n [*] Any local account can be used for lateral movement."); + } + + + WriteLine(" {0,-30} : {1}", "FilterAdministratorToken", dto.FilterAdministratorToken); + + if (dto.EnableLua.Equals("1") && !dto.LocalAccountTokenFilterPolicy.Equals("1") && !dto.FilterAdministratorToken.Equals("1")) + { + WriteLine(" [*] LocalAccountTokenFilterPolicy set to 0 and FilterAdministratorToken != 1.\n [*] Only the RID-500 local admin account can be used for lateral movement."); + } + + + if (dto.EnableLua.Equals("1") && !dto.LocalAccountTokenFilterPolicy.Equals("1") && dto.FilterAdministratorToken.Equals("1")) + { + WriteLine(" [*] LocalAccountTokenFilterPolicy set to 0 and FilterAdministratorToken == 1.\n [*] No local accounts can be used for lateral movement."); + } + } + } + } +} +#nullable enable \ No newline at end of file diff --git a/Seatbelt/Commands/Windows/UserRightAssignmentsCommand.cs b/Seatbelt/Commands/Windows/UserRightAssignmentsCommand.cs new file mode 100644 index 0000000..783aacc --- /dev/null +++ b/Seatbelt/Commands/Windows/UserRightAssignmentsCommand.cs @@ -0,0 +1,219 @@ +#nullable disable +using Seatbelt.Output.Formatters; +using System; +using System.Collections.Generic; +using System.Linq; +using Seatbelt.Output.TextWriters; +using System.Text.RegularExpressions; +using static Seatbelt.Interop.Netapi32; +using Seatbelt.Util; + + +namespace Seatbelt.Commands.Windows +{ + internal class UserRightAssignmentsCommand : CommandBase + { + public override string Command => "UserRightAssignments"; + public override string Description => "Configured User Right Assignments (e.g. SeDenyNetworkLogonRight, SeShutdownPrivilege, etc.) argument == computername to enumerate"; + public override CommandGroup[] Group => new[] {CommandGroup.System}; + public override bool SupportRemote => false; + + + private readonly string[] _allPrivileges = new[] +{ + "SeAssignPrimaryTokenPrivilege", + "SeAuditPrivilege", + "SeBackupPrivilege", + "SeBatchLogonRight", + "SeChangeNotifyPrivilege", + "SeCreateGlobalPrivilege", + "SeCreatePagefilePrivilege", + "SeCreatePermanentPrivilege", + "SeCreateSymbolicLinkPrivilege", + "SeCreateTokenPrivilege", + "SeDebugPrivilege", + "SeDenyBatchLogonRight", + "SeDenyInteractiveLogonRight", + "SeDenyNetworkLogonRight", + "SeDenyRemoteInteractiveLogonRight", + "SeDenyServiceLogonRight", + "SeEnableDelegationPrivilege", + "SeImpersonatePrivilege", + "SeIncreaseBasePriorityPrivilege", + "SeIncreaseQuotaPrivilege", + "SeIncreaseWorkingSetPrivilege", + "SeInteractiveLogonRight", + "SeLoadDriverPrivilege", + "SeLockMemoryPrivilege", + "SeMachineAccountPrivilege", + "SeManageVolumePrivilege", + "SeNetworkLogonRight", + "SeProfileSingleProcessPrivilege", + "SeRelabelPrivilege", + "SeRemoteInteractiveLogonRight", + "SeRemoteShutdownPrivilege", + "SeRestorePrivilege", + "SeSecurityPrivilege", + "SeServiceLogonRight", + "SeShutdownPrivilege", + "SeSyncAgentPrivilege", + "SeSystemEnvironmentPrivilege", + "SeSystemProfilePrivilege", + "SeSystemtimePrivilege", + "SeTakeOwnershipPrivilege", + "SeTcbPrivilege", + "SeTimeZonePrivilege", + "SeTrustedCredManAccessPrivilege", + "SeUndockPrivilege" + }; + + + private readonly string[] _defaultPrivileges = new[] + { + "SeAssignPrimaryTokenPrivilege", + "SeAuditPrivilege", + "SeBackupPrivilege", + "SeBatchLogonRight", + //"SeChangeNotifyPrivilege", + //"SeCreateGlobalPrivilege", + //"SeCreatePagefilePrivilege", + //"SeCreatePermanentPrivilege", + "SeCreateSymbolicLinkPrivilege", + "SeCreateTokenPrivilege", + "SeDebugPrivilege", + "SeDenyBatchLogonRight", + "SeDenyInteractiveLogonRight", + "SeDenyNetworkLogonRight", + "SeDenyRemoteInteractiveLogonRight", + "SeDenyServiceLogonRight", + "SeEnableDelegationPrivilege", + "SeImpersonatePrivilege", + //"SeIncreaseBasePriorityPrivilege", + //"SeIncreaseQuotaPrivilege", + //"SeIncreaseWorkingSetPrivilege", + "SeInteractiveLogonRight", + "SeLoadDriverPrivilege", + //"SeLockMemoryPrivilege", + //"SeMachineAccountPrivilege", + //"SeManageVolumePrivilege", + "SeNetworkLogonRight", + //"SeProfileSingleProcessPrivilege", + "SeRelabelPrivilege", + "SeRemoteInteractiveLogonRight", + "SeRemoteShutdownPrivilege", + "SeRestorePrivilege", + "SeSecurityPrivilege", + "SeServiceLogonRight", + "SeShutdownPrivilege", + "SeSyncAgentPrivilege", + "SeSystemEnvironmentPrivilege", + //"SeSystemProfilePrivilege", + //"SeSystemtimePrivilege", + "SeTakeOwnershipPrivilege", + "SeTcbPrivilege", + //"SeTimeZonePrivilege", + "SeTrustedCredManAccessPrivilege", + //"SeUndockPrivilege" + }; + + public UserRightAssignmentsCommand(Runtime runtime) : base(runtime) + { + } + + public override IEnumerable Execute(string[] args) + { + if (!SecurityUtil.IsHighIntegrity()) + { + WriteHost("Must be an administrator to enumerate User Right Assignments"); + yield break; + } + + var computerName = "localhost"; + string filter = null; + LsaWrapper lsa = null; + if (args.Length >= 1) + { + computerName = args[0]; + } + + if (args.Length >= 2) + { + filter = ".*" + args[1] + ".*"; + } + + try + { + lsa = new LsaWrapper(computerName); + } + catch (UnauthorizedAccessException) + { + WriteError("Insufficient privileges"); + yield break; + } + catch (Exception e) + { + WriteError("Unhandled exception enumerating user right assignments: " + e); + yield break; + } + + var privilegeSet = filter == null ? _defaultPrivileges : _allPrivileges.Where(p => Regex.IsMatch(p, filter, RegexOptions.IgnoreCase)).ToArray(); + + foreach (var priv in privilegeSet) + { + var principals = lsa.ReadPrivilege(priv); + + yield return new UserRightAssignmentsDTO() + { + Right = priv, + Principals = principals + }; + } + + if (lsa != null) + { + lsa.Dispose(); + } + } + + + } + + internal class UserRightAssignmentsDTO : CommandDTOBase + { + public string Right { get; set; } + public List Principals { get; set; } + } + + [CommandOutputType(typeof(UserRightAssignmentsDTO))] + internal class UserRightAssignmentsTextFormatter : TextFormatterBase + { + public UserRightAssignmentsTextFormatter(ITextWriter writer) : base(writer) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + + var dto = (UserRightAssignmentsDTO)result; + WriteLine($"{dto.Right}:"); + + if (dto.Principals.Count <= 0) return; + foreach (var t in dto.Principals) + { + var accountName = ""; + + accountName = !string.IsNullOrEmpty(t.Domain) ? $"{t.Domain}\\{t.User}" : t.User; + + if (string.IsNullOrEmpty(accountName)) + { + accountName = t.Sid; + } + + WriteLine(" " + accountName); + } + + WriteLine(); + } + } +} +#nullable enable \ No newline at end of file diff --git a/Seatbelt/Commands/Windows/WMIEventConsumerCommand.cs b/Seatbelt/Commands/Windows/WMIEventConsumerCommand.cs new file mode 100644 index 0000000..b325af1 --- /dev/null +++ b/Seatbelt/Commands/Windows/WMIEventConsumerCommand.cs @@ -0,0 +1,96 @@ +using System.Collections.Generic; +using System.Management; +using System.Security.Principal; +using Seatbelt.Output.Formatters; +using Seatbelt.Output.TextWriters; + + +namespace Seatbelt.Commands.Windows +{ + internal class WMIEventConsumerCommand : CommandBase + { + public override string Command => "WMIEventConsumer"; + public override string Description => "Lists WMI Event Consumers"; + public override CommandGroup[] Group => new[] {CommandGroup.System}; + public override bool SupportRemote => false; // TODO: remote + + public WMIEventConsumerCommand(Runtime runtime) : base(runtime) + { + } + + public override IEnumerable Execute(string[] args) + { + // recurse and get members of the '__EventConsumer' SuperClass + var opt = new EnumerationOptions(); + opt.EnumerateDeep = true; + + var EventConsumerClass = new ManagementClass(@"\\.\ROOT\Subscription:__EventConsumer"); + // https://wutils.com/wmi/root/subscription/commandlineeventconsumer/cs-samples.html + + foreach (ManagementObject EventConsumer in EventConsumerClass.GetInstances(opt)) + { + var systemprops = EventConsumer.SystemProperties; + var ConsumerType = $"{systemprops["__CLASS"].Value}"; + + var sidBytes = (byte[])EventConsumer["CreatorSID"]; + var creatorSid = new SecurityIdentifier(sidBytes, 0); + + var properties = new Dictionary(); + + foreach (var prop in EventConsumer.Properties) + { + if(!prop.Name.Equals("CreatorSID")) + { + properties[prop.Name] = prop.Value; + } + } + + yield return new WMIEventConsumerDTO( + $"{EventConsumer["Name"]}", + creatorSid, + ConsumerType, + properties + ); + } + } + } + + internal class WMIEventConsumerDTO : CommandDTOBase + { + public WMIEventConsumerDTO(object name, object consumerType, object creatorSid, object properties) + { + Name = name; + ConsumerType = consumerType; + CreatorSid = creatorSid; + Properties = properties; + } + public object Name { get; } + + public object ConsumerType { get; } + + public object CreatorSid { get; } + + public object Properties { get; } + } + + [CommandOutputType(typeof(WMIEventConsumerDTO))] + internal class WMIEventConsumeFormatter : TextFormatterBase + { + public WMIEventConsumeFormatter(ITextWriter writer) : base(writer) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + var dto = (WMIEventConsumerDTO)result; + + WriteLine(" {0,-30} : {1}", "Name", dto.Name); + WriteLine(" {0,-30} : {1}", "ConsumerType", dto.ConsumerType); + WriteLine(" {0,-30} : {1}", "CreatorSID", dto.CreatorSid); + foreach(var kvp in (Dictionary)dto.Properties) + { + WriteLine(" {0,-30} : {1}", kvp.Key, kvp.Value); + } + } + } +} diff --git a/Seatbelt/Commands/Windows/WMIEventFilterCommand.cs b/Seatbelt/Commands/Windows/WMIEventFilterCommand.cs new file mode 100644 index 0000000..981f1c3 --- /dev/null +++ b/Seatbelt/Commands/Windows/WMIEventFilterCommand.cs @@ -0,0 +1,54 @@ +#nullable disable +using System.Collections.Generic; +using System.Management; +using System.Security.Principal; + +namespace Seatbelt.Commands.Windows +{ + internal class WMIEventFilterCommand : CommandBase + { + public override string Command => "WMIEventFilter"; + public override string Description => "Lists WMI Event Filters"; + public override CommandGroup[] Group => new[] {CommandGroup.System}; + public override bool SupportRemote => false; // TODO: remote + + public WMIEventFilterCommand(Runtime runtime) : base(runtime) + { + } + + public override IEnumerable Execute(string[] args) + { + var EventFilterClass = new ManagementClass(@"\\.\ROOT\Subscription:__EventFilter"); + + foreach (ManagementObject EventFilter in EventFilterClass.GetInstances()) + { + var sidBytes = (byte[])EventFilter["CreatorSID"]; + var CreatorSID = new SecurityIdentifier(sidBytes, 0); + + yield return new WMIEventFilterDTO() + { + Name = EventFilter["Name"], + Namespace = EventFilter["__NAMESPACE"], + EventNamespace = EventFilter["EventNamespace"], + Query = EventFilter["Query"], + QueryLanguage = EventFilter["QueryLanguage"], + EventAccess = EventFilter["EventAccess"], + CreatorSID = CreatorSID + }; + } + + } + } + + internal class WMIEventFilterDTO : CommandDTOBase + { + public object Name { get; set; } + public object Namespace { get; set; } + public object EventNamespace { get; set; } + public object Query { get; set; } + public object QueryLanguage { get; set; } + public object EventAccess { get; set; } + public object CreatorSID { get; set; } + } +} +#nullable enable \ No newline at end of file diff --git a/Seatbelt/Commands/Windows/WMIFilterToConsumerBindingCommand.cs b/Seatbelt/Commands/Windows/WMIFilterToConsumerBindingCommand.cs new file mode 100644 index 0000000..5e5acf8 --- /dev/null +++ b/Seatbelt/Commands/Windows/WMIFilterToConsumerBindingCommand.cs @@ -0,0 +1,50 @@ +using System.Collections.Generic; +using System.Management; +using System.Security.Principal; + +namespace Seatbelt.Commands.Windows +{ + internal class WMIFilterToConsumerBindingCommand : CommandBase + { + public override string Command => "WMIFilterBinding"; + public override string Description => "Lists WMI Filter to Consumer Bindings"; + public override CommandGroup[] Group => new[] {CommandGroup.System}; + public override bool SupportRemote => false; // TODO: remote + + public WMIFilterToConsumerBindingCommand(Runtime runtime) : base(runtime) + { + } + + public override IEnumerable Execute(string[] args) + { + var BindingrClass = new ManagementClass(@"\\.\ROOT\Subscription:__FilterToConsumerBinding"); + + foreach (ManagementObject Binding in BindingrClass.GetInstances()) + { + var sidBytes = (byte[])Binding["CreatorSID"]; + var CreatorSID = new SecurityIdentifier(sidBytes, 0); + + yield return new WMIFilterToConsumerBindingDTO( + Binding["Filter"], + Binding["Consumer"], + CreatorSID + ); + } + } + } + + internal class WMIFilterToConsumerBindingDTO : CommandDTOBase + { + public WMIFilterToConsumerBindingDTO(object consumer, object filter, object creatorSid) + { + Consumer = consumer; + Filter = filter; + CreatorSID = creatorSid; + } + public object Consumer { get; } + + public object Filter { get; } + + public object CreatorSID { get; } + } +} diff --git a/Seatbelt/Commands/Windows/WindowsAutoLogonCommand.cs b/Seatbelt/Commands/Windows/WindowsAutoLogonCommand.cs new file mode 100644 index 0000000..e677f19 --- /dev/null +++ b/Seatbelt/Commands/Windows/WindowsAutoLogonCommand.cs @@ -0,0 +1,56 @@ +#nullable disable +using System.Collections.Generic; +using Microsoft.Win32; + +namespace Seatbelt.Commands.Windows +{ + internal class WindowsAutoLogonCommand : CommandBase + { + public override string Command => "WindowsAutoLogon"; + public override string Description => "Registry autologon information"; + public override CommandGroup[] Group => new[] {CommandGroup.System}; + public Runtime ThisRunTime; + public override bool SupportRemote => true; + + public WindowsAutoLogonCommand(Runtime runtime) : base(runtime) + { + ThisRunTime = runtime; + } + + public override IEnumerable Execute(string[] args) + { + var strDefaultDomainName = ThisRunTime.GetStringValue(RegistryHive.LocalMachine, "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon", "DefaultDomainName"); + + var strDefaultUserName = ThisRunTime.GetStringValue(RegistryHive.LocalMachine, "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon", "DefaultUserName"); + + var strDefaultPassword = ThisRunTime.GetStringValue(RegistryHive.LocalMachine, "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon", "DefaultPassword"); + + var strAltDefaultDomainName = ThisRunTime.GetStringValue(RegistryHive.LocalMachine, "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon", "AltDefaultDomainName"); + + var strAltDefaultUserName = ThisRunTime.GetStringValue(RegistryHive.LocalMachine, "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon", "AltDefaultUserName"); + + var strAltDefaultPassword = ThisRunTime.GetStringValue(RegistryHive.LocalMachine, "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon", "AltDefaultPassword"); + + yield return new WindowsAutoLogonDTO() + { + DefaultDomainName = strDefaultDomainName, + DefaultUserName = strDefaultUserName, + DefaultPassword = strDefaultPassword, + AltDefaultDomainName = strAltDefaultDomainName, + AltDefaultUserName = strAltDefaultUserName, + AltDefaultPassword = strAltDefaultPassword + }; + } + + internal class WindowsAutoLogonDTO : CommandDTOBase + { + public string DefaultDomainName { get; set; } + public string DefaultUserName { get; set; } + public string DefaultPassword { get; set; } + public string AltDefaultDomainName { get; set; } + public string AltDefaultUserName { get; set; } + public string AltDefaultPassword { get; set; } + } + } +} +#nullable enable \ No newline at end of file diff --git a/Seatbelt/Commands/Windows/WindowsCredentialFileCommand.cs b/Seatbelt/Commands/Windows/WindowsCredentialFileCommand.cs new file mode 100644 index 0000000..10b563f --- /dev/null +++ b/Seatbelt/Commands/Windows/WindowsCredentialFileCommand.cs @@ -0,0 +1,193 @@ +#nullable disable +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using Seatbelt.Output.TextWriters; +using Seatbelt.Output.Formatters; + + +namespace Seatbelt.Commands.Windows +{ + class CredentialFile + { + public string FileName { get; set; } + + public string Description { get; set; } + + public Guid GUIDMasterKey { get; set; } + + public DateTime LastAccessed { get; set; } + + public DateTime LastModified { get; set; } + + public long Size { get; set; } + } + + internal class WindowsCredentialFileCommand : CommandBase + { + public override string Command => "WindowsCredentialFiles"; + public override string Description => "Windows credential DPAPI blobs"; + public override CommandGroup[] Group => new[] { CommandGroup.User }; + public override bool SupportRemote => false; + + public WindowsCredentialFileCommand(Runtime runtime) : base(runtime) + { + } + + public override IEnumerable Execute(string[] args) + { + // lists any found files in Local\Microsoft\Credentials\* + + var userFolder = $"{Environment.GetEnvironmentVariable("SystemDrive")}\\Users\\"; + var dirs = Directory.GetDirectories(userFolder); + + foreach (var dir in dirs) + { + if (dir.EndsWith("Public") || dir.EndsWith("Default") || dir.EndsWith("Default User") || + dir.EndsWith("All Users")) + { + continue; + } + + var userCredFilePath = $"{dir}\\AppData\\Local\\Microsoft\\Credentials\\"; + if (!Directory.Exists(userCredFilePath)) + { + continue; + } + + var userFiles = Directory.GetFiles(userCredFilePath); + if (userFiles.Length == 0) + { + continue; + } + + List UserCredentials = new List(); + + foreach (var file in userFiles) + { + var lastAccessed = File.GetLastAccessTime(file); + var lastModified = File.GetLastWriteTime(file); + var size = new FileInfo(file).Length; + var fileName = Path.GetFileName(file); + + // jankily parse the bytes to extract the credential type and master key GUID + // reference- https://github.com/gentilkiwi/mimikatz/blob/3d8be22fff9f7222f9590aa007629e18300cf643/modules/kull_m_dpapi.h#L24-L54 + var credentialArray = File.ReadAllBytes(file); + var guidMasterKeyArray = new byte[16]; + Array.Copy(credentialArray, 36, guidMasterKeyArray, 0, 16); + var guidMasterKey = new Guid(guidMasterKeyArray); + + var stringLenArray = new byte[16]; + Array.Copy(credentialArray, 56, stringLenArray, 0, 4); + var descLen = BitConverter.ToInt32(stringLenArray, 0); + + var descBytes = new byte[descLen-4]; + Array.Copy(credentialArray, 60, descBytes, 0, descBytes.Length); + + var desc = Encoding.Unicode.GetString(descBytes); + + CredentialFile userCredential = new CredentialFile(); + userCredential.FileName = Path.GetFileName(file); + userCredential.Description = desc; + userCredential.GUIDMasterKey = guidMasterKey; + userCredential.LastAccessed = File.GetLastAccessTime(file); + userCredential.LastModified = File.GetLastAccessTime(file); + userCredential.Size = size; + UserCredentials.Add(userCredential); + } + + yield return new WindowsCredentialFileDTO() + { + Folder = userCredFilePath, + CredentialFiles = UserCredentials + }; + } + + var systemFolder = $"{Environment.GetEnvironmentVariable("SystemRoot")}\\System32\\config\\systemprofile\\AppData\\Local\\Microsoft\\Credentials"; + + if (Directory.Exists(systemFolder)) + { + var files = Directory.GetFiles(systemFolder); + + if (files.Length != 0) + { + List SystemCredentials = new List(); + + foreach (var file in files) + { + var lastAccessed = File.GetLastAccessTime(file); + var lastModified = File.GetLastWriteTime(file); + var size = new FileInfo(file).Length; + var fileName = Path.GetFileName(file); + + // jankily parse the bytes to extract the credential type and master key GUID + // reference- https://github.com/gentilkiwi/mimikatz/blob/3d8be22fff9f7222f9590aa007629e18300cf643/modules/kull_m_dpapi.h#L24-L54 + var credentialArray = File.ReadAllBytes(file); + var guidMasterKeyArray = new byte[16]; + Array.Copy(credentialArray, 36, guidMasterKeyArray, 0, 16); + var guidMasterKey = new Guid(guidMasterKeyArray); + + var stringLenArray = new byte[16]; + Array.Copy(credentialArray, 56, stringLenArray, 0, 4); + var descLen = BitConverter.ToInt32(stringLenArray, 0); + + var descBytes = new byte[descLen - 4]; + Array.Copy(credentialArray, 60, descBytes, 0, descBytes.Length); + + var desc = Encoding.Unicode.GetString(descBytes); + + CredentialFile systemCredential = new CredentialFile(); + systemCredential.FileName = Path.GetFileName(file); + systemCredential.Description = desc; + systemCredential.GUIDMasterKey = guidMasterKey; + systemCredential.LastAccessed = File.GetLastAccessTime(file); + systemCredential.LastModified = File.GetLastAccessTime(file); + systemCredential.Size = size; + SystemCredentials.Add(systemCredential); + } + + yield return new WindowsCredentialFileDTO() + { + Folder = systemFolder, + CredentialFiles = SystemCredentials + }; + } + } + } + + internal class WindowsCredentialFileDTO : CommandDTOBase + { + public string Folder { get; set; } + public List CredentialFiles { get; set; } = new List(); + } + + [CommandOutputType(typeof(WindowsCredentialFileDTO))] + internal class WindowsCredentialFileFormatter : TextFormatterBase + { + public WindowsCredentialFileFormatter(ITextWriter writer) : base(writer) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + var dto = (WindowsCredentialFileDTO)result; + + WriteLine(" Folder : {0}\n", dto.Folder); + + foreach (CredentialFile credentialFile in dto.CredentialFiles) + { + WriteLine(" FileName : {0}", credentialFile.FileName); + WriteLine(" Description : {0}", credentialFile.Description); + WriteLine(" MasterKey : {0}", credentialFile.GUIDMasterKey); + WriteLine(" Accessed : {0}", credentialFile.LastAccessed); + WriteLine(" Modified : {0}", credentialFile.LastModified); + WriteLine(" Size : {0}\n", credentialFile.Size); + } + + WriteLine(); + } + } + } +} +#nullable enable \ No newline at end of file diff --git a/Seatbelt/Commands/Windows/WindowsDefenderCommand.cs b/Seatbelt/Commands/Windows/WindowsDefenderCommand.cs new file mode 100644 index 0000000..bf98f85 --- /dev/null +++ b/Seatbelt/Commands/Windows/WindowsDefenderCommand.cs @@ -0,0 +1,109 @@ +using Microsoft.Win32; +using System.Collections.Generic; +using Seatbelt.Output.TextWriters; +using Seatbelt.Output.Formatters; + + +namespace Seatbelt.Commands.Windows +{ + internal class WindowsDefenderCommand : CommandBase + { + public override string Command => "WindowsDefender"; + public override string Description => "Windows Defender settings (including exclusion locations)"; + public override CommandGroup[] Group => new[] { CommandGroup.System, CommandGroup.Remote }; + public override bool SupportRemote => true; + public Runtime ThisRunTime; + + public WindowsDefenderCommand(Runtime runtime) : base(runtime) + { + ThisRunTime = runtime; + } + + public override IEnumerable Execute(string[] args) + { + var pathExclusionData = ThisRunTime.GetValues(RegistryHive.LocalMachine, @"SOFTWARE\Microsoft\Windows Defender\Exclusions\Paths"); + List pathExclusions = new List(); + foreach (var kvp in pathExclusionData) + { + pathExclusions.Add(kvp.Key); + } + + var processExclusionData = ThisRunTime.GetValues(RegistryHive.LocalMachine, @"SOFTWARE\Microsoft\Windows Defender\Exclusions\Processes"); + List processExclusions = new List(); + foreach (var kvp in processExclusionData) + { + processExclusions.Add(kvp.Key); + } + + var extensionExclusionData = ThisRunTime.GetValues(RegistryHive.LocalMachine, @"SOFTWARE\Microsoft\Windows Defender\Exclusions\Extensions"); + List extensionExclusions = new List(); + foreach (var kvp in extensionExclusionData) + { + extensionExclusions.Add(kvp.Key); + } + + yield return new WindowsDefenderDTO( + pathExclusions, + processExclusions, + extensionExclusions + ); + } + + internal class WindowsDefenderDTO : CommandDTOBase + { + public WindowsDefenderDTO(List pathExclusions, List processExclusions, List extensionExclusions) + { + PathExclusions = pathExclusions; + ProcessExclusions = processExclusions; + ExtensionExclusions = extensionExclusions; + } + public List PathExclusions { get; } + public List ProcessExclusions { get; } + public List ExtensionExclusions { get; } + } + + [CommandOutputType(typeof(WindowsDefenderDTO))] + internal class WindowsDefenderFormatter : TextFormatterBase + { + public WindowsDefenderFormatter(ITextWriter writer) : base(writer) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + var dto = (WindowsDefenderDTO)result; + + List pathExclusions = dto.PathExclusions; + List processExclusions = dto.ProcessExclusions; + List extensionExclusions = dto.ExtensionExclusions; + + if (pathExclusions.Count != 0) + { + WriteLine("\r\n PathExclusions : "); + foreach (var path in pathExclusions) + { + WriteLine($" {path}"); + } + } + + if (processExclusions.Count != 0) + { + WriteLine("\r\n ProcessExclusions : "); + foreach (var process in processExclusions) + { + WriteLine($" {process}"); + } + } + + if (extensionExclusions.Count != 0) + { + WriteLine("\r\n ExtensionExclusions : "); + foreach (var extension in extensionExclusions) + { + WriteLine($" {extension}"); + } + } + } + } + } +} diff --git a/Seatbelt/Commands/Windows/WindowsEventForwardingCommand.cs b/Seatbelt/Commands/Windows/WindowsEventForwardingCommand.cs new file mode 100644 index 0000000..2d9cdd6 --- /dev/null +++ b/Seatbelt/Commands/Windows/WindowsEventForwardingCommand.cs @@ -0,0 +1,72 @@ +using Microsoft.Win32; +using System.Collections.Generic; +using Seatbelt.Output.TextWriters; +using Seatbelt.Output.Formatters; + + +namespace Seatbelt.Commands.Windows +{ + internal class WindowsEventForwardingCommand : CommandBase + { + public override string Command => "WindowsEventForwarding"; + public override string Description => "Windows Event Forwarding (WEF) settings via the registry"; + public override CommandGroup[] Group => new[] { CommandGroup.System, CommandGroup.Remote }; + public override bool SupportRemote => true; + public Runtime ThisRunTime; + + public WindowsEventForwardingCommand(Runtime runtime) : base(runtime) + { + ThisRunTime = runtime; + } + + public override IEnumerable Execute(string[] args) + { + + var settings = ThisRunTime.GetValues(RegistryHive.LocalMachine, "Software\\Policies\\Microsoft\\Windows\\EventLog\\EventForwarding\\SubscriptionManager"); + + if (settings != null) + { + foreach (var kvp in settings) + { + if (kvp.Value.GetType().IsArray && (kvp.Value.GetType().GetElementType().ToString() == "System.String")) + { + var result = string.Join(",", (string[])kvp.Value); + WriteHost(" {0,-30} : {1}", kvp.Key, result); + yield return new WindowsEventForwardingDTO(kvp.Key, result); + } + else + { + WriteHost(" {0,-30} : {1}", kvp.Key, kvp.Value); + yield return new WindowsEventForwardingDTO(kvp.Key, kvp.Value.ToString()); + } + } + } + } + + internal class WindowsEventForwardingDTO : CommandDTOBase + { + public WindowsEventForwardingDTO(string key, string value) + { + Key = key; + Value = value; + } + public string Key { get; } + public string Value { get; } + } + + [CommandOutputType(typeof(WindowsEventForwardingDTO))] + internal class WindowsEventForwardingFormatter : TextFormatterBase + { + public WindowsEventForwardingFormatter(ITextWriter writer) : base(writer) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + var dto = (WindowsEventForwardingDTO)result; + + WriteLine(" {0,-30} : {1}", dto.Key, dto.Value); + } + } + } +} diff --git a/Seatbelt/Commands/Windows/WindowsFirewallCommand.cs b/Seatbelt/Commands/Windows/WindowsFirewallCommand.cs new file mode 100644 index 0000000..a61c199 --- /dev/null +++ b/Seatbelt/Commands/Windows/WindowsFirewallCommand.cs @@ -0,0 +1,390 @@ +using System; +using System.Collections.Generic; +using Seatbelt.Output.TextWriters; +using Seatbelt.Output.Formatters; +using Microsoft.Win32; + +namespace Seatbelt.Commands.Windows +{ + enum FirewallAction + { + ALLOW = 0, + BLOCK = 1 + } + + internal class WindowsFirewallCommand : CommandBase + { + public override string Command => "WindowsFirewall"; + public override string Description => "Non-standard firewall rules, \"-full\" dumps all (arguments == allow/deny/tcp/udp/in/out/domain/private/public)"; + public override CommandGroup[] Group => new[] { CommandGroup.System, CommandGroup.Remote }; + public override bool SupportRemote => true; + public Runtime ThisRunTime; + + public WindowsFirewallCommand(Runtime runtime) : base(runtime) + { + ThisRunTime = runtime; + } + + public override IEnumerable Execute(string[] args) + { + // lists local firewall policies and rules + // by default, only "deny" result are output unless "-full" is passed + + var directionArgs = new List(); + var protocolsArgs = new List(); + var actionArgs = new List(); + var profileArgs = new List(); + + foreach (var arg in args) + { + if (arg.ToLower().Equals("allow")) + { + actionArgs.Add("Allow"); + } + else if (arg.ToLower().Equals("deny") || arg.ToLower().Equals("block")) + { + actionArgs.Add("Block"); + } + else if (arg.ToLower().Equals("tcp")) + { + protocolsArgs.Add("TCP"); + } + else if (arg.ToLower().Equals("udp")) + { + protocolsArgs.Add("UDP"); + } + else if (arg.ToLower().Equals("in")) + { + directionArgs.Add("In"); + } + else if (arg.ToLower().Equals("out")) + { + directionArgs.Add("Out"); + } + else if (arg.ToLower().Equals("domain")) + { + profileArgs.Add("Domain"); + } + else if (arg.ToLower().Equals("private")) + { + profileArgs.Add("Private"); + } + else if (arg.ToLower().Equals("Public")) + { + profileArgs.Add("Public"); + } + } + + WriteHost(Runtime.FilterResults ? "Collecting Windows Firewall Non-standard Rules\n\n" : "Collecting all Windows Firewall Rules\n\n"); + + // Translates Windows protocol numbers to strings + var protocols = new Dictionary() + { + { "0", "HOPOPT" }, { "1", "ICMP" }, { "2", "IGMP" }, { "3", "GGP" }, { "4", "IPv4" }, { "5", "ST" }, { "6", "TCP" }, { "7", "CBT" }, { "8", "EGP" }, { "9", "IGP" }, { "10", "BBN-RCC-MON" }, { "11", "NVP-II" }, { "12", "PUP" }, { "13", "ARGUS" }, { "14", "EMCON" }, { "15", "XNET" }, { "16", "CHAOS" }, { "17", "UDP" }, { "18", "MUX" }, { "19", "DCN-MEAS" }, { "20", "HMP" }, { "21", "PRM" }, { "22", "XNS-IDP" }, { "23", "TRUNK-1" }, { "24", "TRUNK-2" }, { "25", "LEAF-1" }, { "26", "LEAF-2" }, { "27", "RDP" }, { "28", "IRTP" }, { "29", "ISO-TP4" }, { "30", "NETBLT" }, { "31", "MFE-NSP" }, { "32", "MERIT-INP" }, { "33", "DCCP" }, { "34", "3PC" }, { "35", "IDPR" }, { "36", "XTP" }, { "37", "DDP" }, { "38", "IDPR-CMTP" }, { "39", "TP++" }, { "40", "IL" }, { "41", "IPv6" }, { "42", "SDRP" }, { "43", "IPv6-Route" }, { "44", "IPv6-Frag" }, { "45", "IDRP" }, { "46", "RSVP" }, { "47", "GRE" }, { "48", "DSR" }, { "49", "BNA" }, { "50", "ESP" }, { "51", "AH" }, { "52", "I-NLSP" }, { "53", "SWIPE" }, { "54", "NARP" }, { "55", "MOBILE" }, { "56", "TLSP" }, { "57", "SKIP" }, { "58", "IPv6-ICMP" }, { "59", "IPv6-NoNxt" }, { "60", "IPv6-Opts" }, { "61", "any host" }, { "62", "CFTP" }, { "63", "any local" }, { "64", "SAT-EXPAK" }, { "65", "KRYPTOLAN" }, { "66", "RVD" }, { "67", "IPPC" }, { "68", "any distributed file system" }, { "69", "SAT-MON" }, { "70", "VISA" }, { "71", "IPCV" }, { "72", "CPNX" }, { "73", "CPHB" }, { "74", "WSN" }, { "75", "PVP" }, { "76", "BR-SAT-MON" }, { "77", "SUN-ND" }, { "78", "WB-MON" }, { "79", "WB-EXPAK" }, { "80", "ISO-IP" }, { "81", "VMTP" }, { "82", "SECURE-VMTP" }, { "83", "VINES" }, { "84", "TTP" }, { "85", "NSFNET-IGP" }, { "86", "DGP" }, { "87", "TCF" }, { "88", "EIGRP" }, { "89", "OSPFIGP" }, { "90", "Sprite-RPC" }, { "91", "LARP" }, { "92", "MTP" }, { "93", "AX.25" }, { "94", "IPIP" }, { "95", "MICP" }, { "96", "SCC-SP" }, { "97", "ETHERIP" }, { "98", "ENCAP" }, { "99", "any private encryption scheme" }, { "100", "GMTP" }, { "101", "IFMP" }, { "102", "PNNI" }, { "103", "PIM" }, { "104", "ARIS" }, { "105", "SCPS" }, { "106", "QNX" }, { "107", "A/N" }, { "108", "IPComp" }, { "109", "SNP" }, { "110", "Compaq-Peer" }, { "111", "IPX-in-IP" }, { "112", "VRRP" }, { "113", "PGM" }, { "114", "0-hop" }, { "115", "L2TP" }, { "116", "DDX" }, { "117", "IATP" }, { "118", "STP" }, { "119", "SRP" }, { "120", "UTI" }, { "121", "SMP" }, { "122", "SM" }, { "123", "PTP" }, { "124", "ISIS" }, { "125", "FIRE" }, { "126", "CRTP" }, { "127", "CRUDP" }, { "128", "SSCOPMCE" }, { "129", "IPLT" }, { "130", "SPS" }, { "131", "PIPE" }, { "132", "SCTP" }, { "133", "FC" }, { "134", "RSVP-E2E-IGNORE" }, { "135", "Mobility" }, { "136", "UDPLite" }, { "137", "MPLS-in-IP" }, { "138", "manet" }, { "139", "HIP" }, { "140", "Shim6" }, { "141", "WESP" }, { "142", "ROHC" }, { "143", "Unassigned" }, { "253", "Experimentation" }, { "254", "Experimentation" }, { "255", "Reserved" } + }; + + // base locations to search + // omitted - @"SYSTEM\ControlSet001\Services\SharedAccess\Parameters\FirewallPolicy" + string[] ruleLocations = { @"SOFTWARE\Policies\Microsoft\WindowsFirewall", @"SYSTEM\CurrentControlSet\Services\SharedAccess\Parameters\FirewallPolicy" }; + foreach (var ruleLocation in ruleLocations) + { + var FirewallRules = ThisRunTime.GetValues(RegistryHive.LocalMachine, String.Format("{0}\\FirewallRules", ruleLocation)); + if (FirewallRules != null) + { + var output = new WindowsFirewallDTO(ruleLocation); + + var DomainProfileEnabled = ThisRunTime.GetDwordValue(RegistryHive.LocalMachine, String.Format("{0}\\DomainProfile", ruleLocation), "EnableFirewall"); + if (DomainProfileEnabled != null) + { + output.Domain.Present = true; + var DomainProfileInboundAction = ThisRunTime.GetDwordValue(RegistryHive.LocalMachine, String.Format("{0}\\DomainProfile", ruleLocation), "DefaultInboundAction"); + var DomainProfileOutboundAction = ThisRunTime.GetDwordValue(RegistryHive.LocalMachine, String.Format("{0}\\DomainProfile", ruleLocation), "DefaultOutboundAction"); + var DomainProfileDisableNotifications = ThisRunTime.GetDwordValue(RegistryHive.LocalMachine, String.Format("{0}\\DomainProfile", ruleLocation), "DisableNotifications"); + + output.Domain.Enabled = DomainProfileEnabled == 1; + if (DomainProfileEnabled != null) + { + if (DomainProfileDisableNotifications != null) + { + output.Domain.DisableNotifications = DomainProfileDisableNotifications == 1; + } + if (DomainProfileInboundAction != null) + { + output.Domain.DefaultInboundAction = (FirewallAction)DomainProfileInboundAction; + } + if (DomainProfileOutboundAction != null) + { + output.Domain.DefaultOutboundAction = (FirewallAction)DomainProfileOutboundAction; + } + } + } + + + var PublicProfileEnabled = ThisRunTime.GetDwordValue(RegistryHive.LocalMachine, String.Format("{0}\\PublicProfile", ruleLocation), "EnableFirewall"); + if (PublicProfileEnabled != null) + { + output.Public.Present = true; + var PublicProfileInboundAction = ThisRunTime.GetDwordValue(RegistryHive.LocalMachine, String.Format("{0}\\PublicProfile", ruleLocation), "DefaultInboundAction"); + var PublicProfileOutboundAction = ThisRunTime.GetDwordValue(RegistryHive.LocalMachine, String.Format("{0}\\PublicProfile", ruleLocation), "DefaultOutboundAction"); + var PublicProfileDisableNotifications = ThisRunTime.GetDwordValue(RegistryHive.LocalMachine, String.Format("{0}\\PublicProfile", ruleLocation), "DisableNotifications"); + + output.Public.Enabled = PublicProfileEnabled == 1; + if (PublicProfileDisableNotifications != null) + { + output.Public.DisableNotifications = PublicProfileDisableNotifications == 1; + } + if (PublicProfileInboundAction != null) + { + output.Public.DefaultInboundAction = (FirewallAction)PublicProfileInboundAction; + } + if (PublicProfileOutboundAction != null) + { + output.Public.DefaultOutboundAction = (FirewallAction)PublicProfileOutboundAction; + } + } + + + var PrivateProfileEnabled = ThisRunTime.GetDwordValue(RegistryHive.LocalMachine, String.Format("{0}\\PrivateProfile", ruleLocation), "EnableFirewall"); + if (PrivateProfileEnabled != null) + { + output.Private.Present = true; + var PrivateProfileInboundAction = ThisRunTime.GetDwordValue(RegistryHive.LocalMachine, String.Format("{0}\\PrivateProfile", ruleLocation), "DefaultInboundAction"); + var PrivateProfileOutboundAction = ThisRunTime.GetDwordValue(RegistryHive.LocalMachine, String.Format("{0}\\PrivateProfile", ruleLocation), "DefaultOutboundAction"); + var PrivateProfileDisableNotifications = ThisRunTime.GetDwordValue(RegistryHive.LocalMachine, String.Format("{0}\\PrivateProfile", ruleLocation), "DisableNotifications"); + + output.Private.Enabled = PrivateProfileEnabled == 1; + if (PrivateProfileDisableNotifications != null) + { + output.Private.DisableNotifications = PrivateProfileDisableNotifications == 1; + } + if (PrivateProfileInboundAction != null) + { + output.Private.DefaultInboundAction = (FirewallAction)PrivateProfileInboundAction; + } + if (PrivateProfileOutboundAction != null) + { + output.Private.DefaultOutboundAction = (FirewallAction)PrivateProfileOutboundAction; + } + } + + + var StandardProfileEnabled = ThisRunTime.GetDwordValue(RegistryHive.LocalMachine, String.Format("{0}\\StandardProfile", ruleLocation), "EnableFirewall"); + if (StandardProfileEnabled != null) + { + output.Standard.Present = true; + var StandardProfileInboundAction = ThisRunTime.GetDwordValue(RegistryHive.LocalMachine, String.Format("{0}\\StandardProfile", ruleLocation), "DefaultInboundAction"); + var StandardProfileOutboundAction = ThisRunTime.GetDwordValue(RegistryHive.LocalMachine, String.Format("{0}\\StandardProfile", ruleLocation), "DefaultOutboundAction"); + var StandardProfileDisableNotifications = ThisRunTime.GetDwordValue(RegistryHive.LocalMachine, String.Format("{0}\\StandardProfile", ruleLocation), "DisableNotifications"); + + output.Standard.Enabled = StandardProfileEnabled == 1; + if (StandardProfileDisableNotifications != null) + { + output.Standard.DisableNotifications = StandardProfileDisableNotifications == 1; + } + if (StandardProfileInboundAction != null) + { + output.Standard.DefaultInboundAction = (FirewallAction)StandardProfileInboundAction; + } + if (StandardProfileOutboundAction != null) + { + output.Standard.DefaultOutboundAction = (FirewallAction)StandardProfileOutboundAction; + } + } + + foreach (var kvp in FirewallRules) + { + var rule = new WindowsFirewallRule(); + + var props = ((string)kvp.Value).Split('|'); + foreach (var prop in props) + { + var onv = prop.Split('='); + // The first argument is the version number, which doesn't have a value. That number should not be parsed + if (onv.Length == 1) + { + continue; + } + string key = onv[0], value = onv[1]; + switch (onv[0]) + { + case "Action": + rule.Action = value; + break; + case "Active": + break; + case "Dir": + rule.Direction = value; + break; + case "Protocol": + rule.Protocol = protocols[value]; + break; + case "Name": + rule.Name = value; + break; + case "Desc": + rule.Description = value; + break; + case "App": + rule.ApplicationName = value; + break; + case "Profile": + rule.Profiles = value; + break; + case "RPort": + rule.RemotePorts = value; + break; + case "LPort": + rule.LocalPorts = value; + break; + case "RA4": + rule.RemoteAddresses = value; + break; + case "LA4": + rule.LocalAddresses = value; + break; + } + } + + if ( + !Runtime.FilterResults || + ( + ( + ((actionArgs.Count == 0 && protocolsArgs.Count == 0 && directionArgs.Count == 0 && profileArgs.Count == 0) && !rule.Name.StartsWith("@") && !rule.Name.Equals("Shell Input Application"))) || + ( + (actionArgs.Contains("Allow") && !String.IsNullOrEmpty(rule.Action) && rule.Action.Equals("Allow")) || + (actionArgs.Contains("Block") && !String.IsNullOrEmpty(rule.Action) && rule.Action.Equals("Block")) || + (protocolsArgs.Contains("TCP") && (String.IsNullOrEmpty(rule.Protocol) || rule.Protocol.Equals("TCP"))) || + (protocolsArgs.Contains("UDP") && (String.IsNullOrEmpty(rule.Protocol) || rule.Protocol.Equals("UDP"))) || + (directionArgs.Contains("In") && (String.IsNullOrEmpty(rule.Direction) || rule.Direction.Equals("In"))) || + (directionArgs.Contains("Out") && (String.IsNullOrEmpty(rule.Direction) || rule.Direction.Equals("Out"))) || + (profileArgs.Contains("Domain") && (String.IsNullOrEmpty(rule.Profiles) || rule.Profiles.Equals("Domain"))) || + (profileArgs.Contains("Private") && (String.IsNullOrEmpty(rule.Profiles) || rule.Profiles.Equals("Private"))) || + (profileArgs.Contains("Public") && (String.IsNullOrEmpty(rule.Profiles) || rule.Profiles.Equals("Public"))) + ) + ) + ) + { + output.Rules.Add(rule); + } + } + + yield return output; + } + } + } + + internal class WindowsFirewallProfileSettings + { + public bool Present { get; set; } + public bool Enabled { get; set; } + public FirewallAction DefaultInboundAction { get; set; } + public FirewallAction DefaultOutboundAction { get; set; } + public bool DisableNotifications { get; set; } + } + + internal class WindowsFirewallRule + { + public string Name { get; set; } = string.Empty; + public string Description { get; set; } = string.Empty; + public string ApplicationName { get; set; } = string.Empty; + public string Protocol { get; set; } = string.Empty; + public string Action { get; set; } = string.Empty; + public string Direction { get; set; } = string.Empty; + public string Profiles { get; set; } = string.Empty; + public string LocalAddresses { get; set; } = string.Empty; + public string LocalPorts { get; set; } = string.Empty; + public string RemoteAddresses { get; set; } = string.Empty; + public string RemotePorts { get; set; } = string.Empty; + } + + internal class WindowsFirewallDTO : CommandDTOBase + { + public WindowsFirewallDTO(string location) + { + Domain = new WindowsFirewallProfileSettings(); + Private = new WindowsFirewallProfileSettings(); + Public = new WindowsFirewallProfileSettings(); + Standard = new WindowsFirewallProfileSettings(); + Rules = new List(); + Location = location; + } + + public String Location { get; set; } + public WindowsFirewallProfileSettings Domain { get; set; } + public WindowsFirewallProfileSettings Private { get; set; } + public WindowsFirewallProfileSettings Public { get; set; } + public WindowsFirewallProfileSettings Standard { get; set; } + public List Rules { get; set; } + } + + + [CommandOutputType(typeof(WindowsFirewallDTO))] + internal class AuditPolicyTextFormatter : TextFormatterBase + { + public AuditPolicyTextFormatter(ITextWriter writer) : base(writer) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + var dto = (WindowsFirewallDTO)result; + + WriteLine("Location : {0}\n", dto.Location); + + if (dto.Domain.Present) + { + WriteLine("Domain Profile"); + WriteLine(" Enabled : {0}", dto.Domain.Enabled); + WriteLine(" DisableNotifications : {0}", dto.Domain.DisableNotifications); + WriteLine(" DefaultInboundAction : {0}", dto.Domain.DefaultInboundAction); + WriteLine(" DefaultOutboundAction : {0}\n", dto.Domain.DefaultOutboundAction); + } + + if (dto.Private.Present) + { + WriteLine("Private Profile"); + WriteLine(" Enabled : {0}", dto.Private.Enabled); + WriteLine(" DisableNotifications : {0}", dto.Private.DisableNotifications); + WriteLine(" DefaultInboundAction : {0}", dto.Private.DefaultInboundAction); + WriteLine(" DefaultOutboundAction : {0}\n", dto.Private.DefaultOutboundAction); + } + + if (dto.Public.Present) + { + WriteLine("Public Profile"); + WriteLine(" Enabled : {0}", dto.Public.Enabled); + WriteLine(" DisableNotifications : {0}", dto.Public.DisableNotifications); + WriteLine(" DefaultInboundAction : {0}", dto.Public.DefaultInboundAction); + WriteLine(" DefaultOutboundAction : {0}\n", dto.Public.DefaultOutboundAction); + } + + if (dto.Standard.Present) + { + WriteLine("Standard Profile"); + WriteLine(" Enabled : {0}", dto.Standard.Enabled); + WriteLine(" DisableNotifications : {0}", dto.Standard.DisableNotifications); + WriteLine(" DefaultInboundAction : {0}", dto.Standard.DefaultInboundAction); + WriteLine(" DefaultOutboundAction : {0}\n", dto.Standard.DefaultOutboundAction); + } + + if (dto.Rules.Count > 0) + { + WriteLine("Rules:\n"); + + foreach (var rule in dto.Rules) + { + WriteLine(" Name : {0}", rule.Name); + WriteLine(" Description : {0}", rule.Description); + WriteLine(" ApplicationName : {0}", rule.ApplicationName); + WriteLine(" Protocol : {0}", rule.Protocol); + WriteLine(" Action : {0}", rule.Action); + WriteLine(" Direction : {0}", rule.Direction); + WriteLine(" Profiles : {0}", rule.Profiles); + WriteLine(" Local Addr:Port : {0}:{1}", rule.LocalAddresses, rule.LocalPorts); + WriteLine(" Remote Addr:Port : {0}:{1}\n", rule.RemoteAddresses, rule.RemotePorts); + } + } + } + } + } +} diff --git a/Seatbelt/Commands/Windows/WindowsVaultCommand.cs b/Seatbelt/Commands/Windows/WindowsVaultCommand.cs new file mode 100644 index 0000000..3229c40 --- /dev/null +++ b/Seatbelt/Commands/Windows/WindowsVaultCommand.cs @@ -0,0 +1,307 @@ +#nullable disable +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using Seatbelt.Output.TextWriters; +using Seatbelt.Output.Formatters; + + +namespace Seatbelt.Commands.Windows +{ + class VaultEntry + { + public string Resource { get; set; } + public string Identity { get; set; } + + public string PackageSid { get; set; } + + public string Credential { get; set; } + + public DateTime LastModified { get; set; } + } + + internal class WindowsVaultCommand : CommandBase + { + public override string Command => "WindowsVault"; + public override string Description => "Credentials saved in the Windows Vault (i.e. logins from Internet Explorer and Edge)."; + public override CommandGroup[] Group => new[] { CommandGroup.User }; + public override bool SupportRemote => false; + + public WindowsVaultCommand(Runtime runtime) : base(runtime) + { + } + + public override IEnumerable Execute(string[] args) + { + // pulled directly from @djhohnstein's SharpWeb project: https://github.com/djhohnstein/SharpWeb/blob/master/Edge/SharpEdge.cs + var OSVersion = Environment.OSVersion.Version; + + Type VAULT_ITEM; + + //if (OSMajor >= 6 && OSMinor >= 2) + if (OSVersion > new Version("6.2")) + { + VAULT_ITEM = typeof(VaultCli.VAULT_ITEM_WIN8); + } + else + { + VAULT_ITEM = typeof(VaultCli.VAULT_ITEM_WIN7); + } + + var vaultCount = 0; + var vaultGuidPtr = IntPtr.Zero; + var result = VaultCli.VaultEnumerateVaults(0, ref vaultCount, ref vaultGuidPtr); + + //var result = CallVaultEnumerateVaults(VaultEnum, 0, ref vaultCount, ref vaultGuidPtr); + + if (result != 0) + { + WriteError("Unable to enumerate vaults. Error (0x" + result + ")"); + yield break; + } + + // Create dictionary to translate Guids to human readable elements + var guidAddress = vaultGuidPtr; + var vaultSchema = new Dictionary + { + { new Guid("2F1A6504-0641-44CF-8BB5-3612D865F2E5"), "Windows Secure Note" }, + { new Guid("3CCD5499-87A8-4B10-A215-608888DD3B55"), "Windows Web Password Credential" }, + { new Guid("154E23D0-C644-4E6F-8CE6-5069272F999F"), "Windows Credential Picker Protector" }, + { new Guid("4BF4C442-9B8A-41A0-B380-DD4A704DDB28"), "Web Credentials" }, + { new Guid("77BC582B-F0A6-4E15-4E80-61736B6F3B29"), "Windows Credentials" }, + { new Guid("E69D7838-91B5-4FC9-89D5-230D4D4CC2BC"), "Windows Domain Certificate Credential" }, + { new Guid("3E0E35BE-1B77-43E7-B873-AED901B6275B"), "Windows Domain Password Credential" }, + { new Guid("3C886FF3-2669-4AA2-A8FB-3F6759A77548"), "Windows Extended Credential" }, + { new Guid("00000000-0000-0000-0000-000000000000"), null } + }; + + for (var i = 0; i < vaultCount; i++) + { + // Open vault block + var vaultGuidString = Marshal.PtrToStructure(guidAddress, typeof(Guid)); + var vaultGuid = new Guid(vaultGuidString.ToString()); + guidAddress = (IntPtr)(guidAddress.ToInt64() + Marshal.SizeOf(typeof(Guid))); + var vaultHandle = IntPtr.Zero; + string vaultType; + + vaultType = vaultSchema.ContainsKey(vaultGuid) ? vaultSchema[vaultGuid] : vaultGuid.ToString(); + result = VaultCli.VaultOpenVault(ref vaultGuid, (uint)0, ref vaultHandle); + if (result != 0) + { + WriteError("Unable to open the following vault: " + vaultType + ". Error: 0x" + result); + continue; + } + // Vault opened successfully! Continue. + + var entries = new List(); + + // Fetch all items within Vault + var vaultItemCount = 0; + var vaultItemPtr = IntPtr.Zero; + result = VaultCli.VaultEnumerateItems(vaultHandle, 512, ref vaultItemCount, ref vaultItemPtr); + if (result != 0) + { + WriteError("Unable to enumerate vault items from the following vault: " + vaultType + ". Error 0x" + result); + continue; + } + var structAddress = vaultItemPtr; + if (vaultItemCount > 0) + { + // For each vault item... + for (var j = 1; j <= vaultItemCount; j++) + { + // Begin fetching vault item... + var currentItem = Marshal.PtrToStructure(structAddress, VAULT_ITEM); + structAddress = (IntPtr)(structAddress.ToInt64() + Marshal.SizeOf(VAULT_ITEM)); + + var passwordVaultItem = IntPtr.Zero; + // Field Info retrieval + var schemaIdInfo = currentItem.GetType().GetField("SchemaId"); + var schemaId = new Guid(schemaIdInfo.GetValue(currentItem).ToString()); + var pResourceElementInfo = currentItem.GetType().GetField("pResourceElement"); + var pResourceElement = (IntPtr)pResourceElementInfo.GetValue(currentItem); + var pIdentityElementInfo = currentItem.GetType().GetField("pIdentityElement"); + var pIdentityElement = (IntPtr)pIdentityElementInfo.GetValue(currentItem); + var dateTimeInfo = currentItem.GetType().GetField("LastModified"); + var lastModified = (ulong)dateTimeInfo.GetValue(currentItem); + + var pPackageSid = IntPtr.Zero; + if (OSVersion > new Version("6.2")) + { + // Newer versions have package sid + var pPackageSidInfo = currentItem.GetType().GetField("pPackageSid"); + pPackageSid = (IntPtr)pPackageSidInfo.GetValue(currentItem); + result = VaultCli.VaultGetItem_WIN8(vaultHandle, ref schemaId, pResourceElement, pIdentityElement, pPackageSid, IntPtr.Zero, 0, ref passwordVaultItem); + } + else + { + result = VaultCli.VaultGetItem_WIN7(vaultHandle, ref schemaId, pResourceElement, pIdentityElement, IntPtr.Zero, 0, ref passwordVaultItem); + } + + if (result != 0) + { + WriteError("Could not retrieve vault vault item. Error: 0x" + result); + continue; + } + + var passwordItem = Marshal.PtrToStructure(passwordVaultItem, VAULT_ITEM); + var pAuthenticatorElementInfo = passwordItem.GetType().GetField("pAuthenticatorElement"); + var pAuthenticatorElement = (IntPtr)pAuthenticatorElementInfo.GetValue(passwordItem); + + try + { + // Fetch the credential from the authenticator element + var cred = GetVaultElementValue(pAuthenticatorElement); + object packageSid = null; + if (pPackageSid != IntPtr.Zero && pPackageSid != null) + { + packageSid = GetVaultElementValue(pPackageSid); + } + + if (cred != null) // Indicates successful fetch + { + if (Runtime.FilterResults) + { + if (String.IsNullOrEmpty(cred.ToString().TrimEnd())) + { + continue; + } + } + + var entry = new VaultEntry(); + + var resource = GetVaultElementValue(pResourceElement); + if (resource != null) + { + entry.Resource = $"{resource}"; + } + var identity = GetVaultElementValue(pIdentityElement); + if (identity != null) + { + entry.Identity = $"{identity}"; + } + if (packageSid != null) + { + entry.PackageSid = $"{packageSid}"; + } + + entry.Credential = $"{cred}"; + entry.LastModified = DateTime.FromFileTimeUtc((long)lastModified); + + entries.Add(entry); + } + } + catch (Exception e) + { + WriteError("Exception: " + e.Message); + continue; + } + } + } + + yield return new WindowsVaultDTO() + { + VaultGUID = vaultGuid, + VaultType = vaultType, + VaultEntries = entries + }; + } + } + + private object GetVaultElementValue(IntPtr vaultElementPtr) + { + // Helper function to extract the ItemValue field from a VAULT_ITEM_ELEMENT struct + // pulled directly from @djhohnstein's SharpWeb project: https://github.com/djhohnstein/SharpWeb/blob/master/Edge/SharpEdge.cs + object results; + var partialElement = Marshal.PtrToStructure(vaultElementPtr, typeof(VaultCli.VAULT_ITEM_ELEMENT)); + var partialElementInfo = partialElement.GetType().GetField("Type"); + var partialElementType = partialElementInfo.GetValue(partialElement); + + // Types: https://github.com/SpiderLabs/portia/blob/master/modules/Get-VaultCredential.ps1#L40-L54 + var elementPtr = (IntPtr)(vaultElementPtr.ToInt64() + 16); + switch ((int)partialElementType) + { + case 0: // VAULT_ELEMENT_TYPE == bool + results = Marshal.ReadByte(elementPtr); + results = (bool)results; + break; + case 1: // VAULT_ELEMENT_TYPE == Short + results = Marshal.ReadInt16(elementPtr); + break; + case 2: // VAULT_ELEMENT_TYPE == Unsigned Short + results = Marshal.ReadInt16(elementPtr); + break; + case 3: // VAULT_ELEMENT_TYPE == Int + results = Marshal.ReadInt32(elementPtr); + break; + case 4: // VAULT_ELEMENT_TYPE == Unsigned Int + results = Marshal.ReadInt32(elementPtr); + break; + case 5: // VAULT_ELEMENT_TYPE == Double + results = Marshal.PtrToStructure(elementPtr, typeof(double)); + break; + case 6: // VAULT_ELEMENT_TYPE == GUID + results = Marshal.PtrToStructure(elementPtr, typeof(Guid)); + break; + case 7: // VAULT_ELEMENT_TYPE == String; These are the plaintext passwords! + var StringPtr = Marshal.ReadIntPtr(elementPtr); + results = Marshal.PtrToStringUni(StringPtr); + break; + case 12: // VAULT_ELEMENT_TYPE == Sid + var sidPtr = Marshal.ReadIntPtr(elementPtr); + var sidObject = new System.Security.Principal.SecurityIdentifier(sidPtr); + results = sidObject.Value; + break; + default: + /* Several VAULT_ELEMENT_TYPES are currently unimplemented according to + * Lord Graeber. Thus we do not implement them. */ + throw new Exception(String.Format("VAULT_ELEMENT_TYPE '{0}' is currently unimplemented", partialElementType)); + } + return results; + } + + internal class WindowsVaultDTO : CommandDTOBase + { + public Guid VaultGUID { get; set; } + + public string VaultType { get; set; } + + public List VaultEntries { get; set; } + } + + [CommandOutputType(typeof(WindowsVaultDTO))] + internal class WindowsVaultFormatter : TextFormatterBase + { + public WindowsVaultFormatter(ITextWriter writer) : base(writer) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + var dto = (WindowsVaultDTO)result; + + WriteLine($"\n Vault GUID : {dto.VaultGUID}"); + WriteLine($" Vault Type : {dto.VaultType}\n"); + + foreach(var entry in dto.VaultEntries) + { + if (!String.IsNullOrEmpty(entry.Resource)) + { + WriteLine($" Resource : {entry.Resource}"); + } + if (!String.IsNullOrEmpty(entry.Identity)) + { + WriteLine($" Identity : {entry.Identity}"); + } + if (!String.IsNullOrEmpty(entry.PackageSid)) + { + WriteLine($" PacakgeSid : {entry.PackageSid}"); + } + WriteLine($" Credential : {entry.Credential}"); + WriteLine($" LastModified : {entry.LastModified}\n"); + } + } + } + } +} +#nullable enable \ No newline at end of file diff --git a/Seatbelt/Interop/Advapi32.cs b/Seatbelt/Interop/Advapi32.cs new file mode 100644 index 0000000..e4a8bf6 --- /dev/null +++ b/Seatbelt/Interop/Advapi32.cs @@ -0,0 +1,389 @@ +using System; +using System.Runtime.InteropServices; +using System.Security; +using System.Security.Principal; +using System.Text; +using LSA_HANDLE = System.IntPtr; +using static Seatbelt.Interop.Secur32; + +namespace Seatbelt.Interop +{ + internal class Advapi32 + { + #region Function Definitions + [DllImport("kernel32.dll")] + public static extern IntPtr LocalFree(IntPtr hMem); + + [DllImport("advapi32.dll", SetLastError = true)] + public static extern bool GetTokenInformation( + IntPtr TokenHandle, + TOKEN_INFORMATION_CLASS TokenInformationClass, + IntPtr TokenInformation, + int TokenInformationLength, + out int ReturnLength); + + [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool LookupPrivilegeName( + string? lpSystemName, + IntPtr lpLuid, + StringBuilder? lpName, + ref int cchName); + + [DllImport("advapi32.dll", SetLastError = true)] + public static extern uint I_QueryTagInformation( + IntPtr Unknown, + SC_SERVICE_TAG_QUERY_TYPE Type, + ref SC_SERVICE_TAG_QUERY Query + ); + + [DllImport("advapi32", CharSet = CharSet.Unicode, SetLastError = true)] + public static extern bool ConvertSidToStringSid(IntPtr pSid, out string strSid); + + [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)] + public static extern bool LookupAccountSid( + string? lpSystemName, + [MarshalAs(UnmanagedType.LPArray)] byte[] Sid, + StringBuilder lpName, + ref uint cchName, + StringBuilder ReferencedDomainName, + ref uint cchReferencedDomainName, + out SID_NAME_USE peUse); + + [DllImport("advapi32", CharSet = CharSet.Unicode, SetLastError = true), SuppressUnmanagedCodeSecurityAttribute] + public static extern uint LsaOpenPolicy( + LSA_UNICODE_STRING[]? SystemName, + ref LSA_OBJECT_ATTRIBUTES ObjectAttributes, + int AccessMask, + out IntPtr PolicyHandle + ); + + [DllImport("advapi32", CharSet = CharSet.Unicode, SetLastError = true), SuppressUnmanagedCodeSecurityAttribute] + public static extern uint LsaEnumerateAccountsWithUserRight( + LSA_HANDLE PolicyHandle, + LSA_UNICODE_STRING[] UserRights, + out IntPtr EnumerationBuffer, + out int CountReturned + ); + + [DllImport("advapi32")] + public static extern int LsaNtStatusToWinError(int NTSTATUS); + + [DllImport("advapi32")] + public static extern int LsaClose(IntPtr PolicyHandle); + + [DllImport("advapi32")] + public static extern int LsaFreeMemory(IntPtr Buffer); + + [DllImport("advapi32.dll", CharSet = CharSet.Unicode)] + public static extern int RegOpenKeyEx( + UIntPtr hKey, + string subKey, + uint ulOptions, + uint samDesired, + out IntPtr hkResult); + + [DllImport("advapi32.dll", EntryPoint = "GetNamedSecurityInfoW", CharSet = CharSet.Unicode)] + public static extern int GetNamedSecurityInfo( + string objectName, + SE_OBJECT_TYPE objectType, + System.Security.AccessControl.SecurityInfos securityInfo, + out IntPtr sidOwner, + out IntPtr sidGroup, + out IntPtr dacl, + out IntPtr sacl, + out IntPtr securityDescriptor); + + [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)] + public static extern bool ConvertSecurityDescriptorToStringSecurityDescriptor( + IntPtr SecurityDescriptor, + uint StringSDRevision, + System.Security.AccessControl.SecurityInfos SecurityInformation, + out string StringSecurityDescriptor, + out int StringSecurityDescriptorSize); + + // for GetSystem() + [DllImport("advapi32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool OpenProcessToken( + IntPtr ProcessHandle, + UInt32 DesiredAccess, + out IntPtr TokenHandle); + + [DllImport("advapi32.dll")] + public static extern bool DuplicateToken( + IntPtr ExistingTokenHandle, + int SECURITY_IMPERSONATION_LEVEL, + ref IntPtr DuplicateTokenHandle); + + [DllImport("advapi32.dll", SetLastError = true)] + public static extern bool ImpersonateLoggedOnUser( + IntPtr hToken); + + [DllImport("advapi32.dll", SetLastError = true)] + public static extern bool RevertToSelf(); + + [DllImport("advapi32.dll", EntryPoint = "CredFree", SetLastError = true)] + internal static extern void CredFree( + [In] IntPtr cred); + + [DllImport("advapi32.dll", EntryPoint = "CredEnumerate", SetLastError = true, CharSet = CharSet.Unicode)] + public static extern bool CredEnumerate( + string filter, + int flag, + out int count, + out IntPtr pCredentials); + + [DllImport("Advapi32", SetLastError = false)] + public static extern bool IsTextUnicode( + byte[] buf, + int len, + ref IsTextUnicodeFlags opt + ); + + #endregion + + + #region Enum Definitions + + public enum SID_NAME_USE + { + SidTypeUser = 1, + SidTypeGroup, + SidTypeDomain, + SidTypeAlias, + SidTypeWellKnownGroup, + SidTypeDeletedAccount, + SidTypeInvalid, + SidTypeUnknown, + SidTypeComputer + } + + public enum TOKEN_INFORMATION_CLASS + { + TokenUser = 1, + TokenGroups, + TokenPrivileges, + TokenOwner, + TokenPrimaryGroup, + TokenDefaultDacl, + TokenSource, + TokenType, + TokenImpersonationLevel, + TokenStatistics, + TokenRestrictedSids, + TokenSessionId, + TokenGroupsAndPrivileges, + TokenSessionReference, + TokenSandBoxInert, + TokenAuditPolicy, + TokenOrigin + } + + public enum SC_SERVICE_TAG_QUERY_TYPE + { + ServiceNameFromTagInformation = 1, + ServiceNamesReferencingModuleInformation = 2, + ServiceNameTagMappingInformation = 3 + } + + public enum SE_OBJECT_TYPE + { + SE_UNKNOWN_OBJECT_TYPE = 0, + SE_FILE_OBJECT, + SE_SERVICE, + SE_PRINTER, + SE_REGISTRY_KEY, + SE_LMSHARE, + SE_KERNEL_OBJECT, + SE_WINDOW_OBJECT, + SE_DS_OBJECT, + SE_DS_OBJECT_ALL, + SE_PROVIDER_DEFINED_OBJECT, + SE_WMIGUID_OBJECT, + SE_REGISTRY_WOW64_32KEY + } + + + [Flags] + public enum LuidAttributes : uint + { + DISABLED = 0x00000000, + SE_PRIVILEGE_ENABLED_BY_DEFAULT = 0x00000001, + SE_PRIVILEGE_ENABLED = 0x00000002, + SE_PRIVILEGE_REMOVED = 0x00000004, + SE_PRIVILEGE_USED_FOR_ACCESS = 0x80000000 + } + + + public enum CredentialType : uint + { + None = 0, + Generic = 1, + DomainPassword = 2, + DomainCertificate = 3, + DomainVisiblePassword = 4, + GenericCertificate = 5, + DomainExtended = 6, + Maximum = 7, + CredTypeMaximum = Maximum + 1000 + } + + public enum PersistenceType : uint + { + Session = 1, + LocalComputer = 2, + Enterprise = 3 + } + + // for unicode detection + [Flags] + public enum IsTextUnicodeFlags : int + { + IS_TEXT_UNICODE_ASCII16 = 0x0001, + IS_TEXT_UNICODE_REVERSE_ASCII16 = 0x0010, + + IS_TEXT_UNICODE_STATISTICS = 0x0002, + IS_TEXT_UNICODE_REVERSE_STATISTICS = 0x0020, + + IS_TEXT_UNICODE_CONTROLS = 0x0004, + IS_TEXT_UNICODE_REVERSE_CONTROLS = 0x0040, + + IS_TEXT_UNICODE_SIGNATURE = 0x0008, + IS_TEXT_UNICODE_REVERSE_SIGNATURE = 0x0080, + + IS_TEXT_UNICODE_ILLEGAL_CHARS = 0x0100, + IS_TEXT_UNICODE_ODD_LENGTH = 0x0200, + IS_TEXT_UNICODE_DBCS_LEADBYTE = 0x0400, + IS_TEXT_UNICODE_NULL_BYTES = 0x1000, + + IS_TEXT_UNICODE_UNICODE_MASK = 0x000F, + IS_TEXT_UNICODE_REVERSE_MASK = 0x00F0, + IS_TEXT_UNICODE_NOT_UNICODE_MASK = 0x0F00, + IS_TEXT_UNICODE_NOT_ASCII_MASK = 0xF000 + } + + #endregion + + + #region Structure Defintions + + [StructLayout(LayoutKind.Sequential)] + public struct LSA_OBJECT_ATTRIBUTES + { + public int Length; + public IntPtr RootDirectory; + public IntPtr ObjectName; + public int Attributes; + public IntPtr SecurityDescriptor; + public IntPtr SecurityQualityOfService; + } + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + public struct LSA_UNICODE_STRING + { + public ushort Length; + public ushort MaximumLength; + [MarshalAs(UnmanagedType.LPWStr)] + public string Buffer; + } + + [StructLayout(LayoutKind.Sequential)] + public struct LSA_ENUMERATION_INFORMATION + { + public IntPtr PSid; + } + + [StructLayout(LayoutKind.Sequential)] + public struct SC_SERVICE_TAG_QUERY + { + public uint ProcessId; + public uint ServiceTag; + public uint Unknown; + public IntPtr Buffer; + } + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + internal struct TOKEN_STATISTICS + { + public LUID TokenId; + public LUID AuthenticationId; + public long ExpirationTime; + public uint TokenType; + public uint ImpersonationLevel; + public uint DynamicCharged; + public uint DynamicAvailable; + public uint GroupCount; + public uint PrivilegeCount; + public LUID ModifiedId; + } + + + [StructLayout(LayoutKind.Sequential)] + internal struct CREDENTIAL + { + public int Flags; + public CredentialType Type; + [MarshalAs(UnmanagedType.LPWStr)] public string TargetName; + [MarshalAs(UnmanagedType.LPWStr)] public string Comment; + public long LastWritten; + public int CredentialBlobSize; + public IntPtr CredentialBlob; + public PersistenceType Persist; + public int AttributeCount; + public IntPtr Attributes; + [MarshalAs(UnmanagedType.LPWStr)] public string TargetAlias; + [MarshalAs(UnmanagedType.LPWStr)] public string UserName; + } + + #endregion + + + #region Helper Functions + + // Based off of code by Lee Christensen(@tifkin_) from https://github.com/Invoke-IR/ACE/blob/master/ACE-Management/PS-ACE/Scripts/ACE_Get-NetworkConnection.ps1#L1046 + public static string? GetServiceNameFromTag(uint processId, uint serviceTag) + { + var serviceTagQuery = new SC_SERVICE_TAG_QUERY + { + ProcessId = processId, + ServiceTag = serviceTag + }; + + var res = I_QueryTagInformation(IntPtr.Zero, SC_SERVICE_TAG_QUERY_TYPE.ServiceNameFromTagInformation, ref serviceTagQuery); + + return res == Win32Error.Success ? Marshal.PtrToStringUni(serviceTagQuery.Buffer) : null; + } + + public static string TranslateSid(string sid) + { + // adapted from http://www.pinvoke.net/default.aspx/advapi32.LookupAccountSid + var accountSid = new SecurityIdentifier(sid); + var accountSidByes = new byte[accountSid.BinaryLength]; + accountSid.GetBinaryForm(accountSidByes, 0); + + var name = new StringBuilder(); + var cchName = (uint)name.Capacity; + var referencedDomainName = new StringBuilder(); + var cchReferencedDomainName = (uint)referencedDomainName.Capacity; + + var err = 0; + if (!LookupAccountSid(null, accountSidByes, name, ref cchName, referencedDomainName, ref cchReferencedDomainName, out var sidUse)) + { + err = Marshal.GetLastWin32Error(); + if (err == Win32Error.InsufficientBuffer) + { + name.EnsureCapacity((int)cchName); + referencedDomainName.EnsureCapacity((int)cchReferencedDomainName); + err = 0; + if (!LookupAccountSid(null, accountSidByes, name, ref cchName, referencedDomainName, ref cchReferencedDomainName, out sidUse)) + err = Marshal.GetLastWin32Error(); + } + } + + return err == 0 ? $"{referencedDomainName}\\{name}" : ""; + } + + #endregion + } +} diff --git a/Seatbelt/Interop/COM.cs b/Seatbelt/Interop/COM.cs new file mode 100644 index 0000000..45b60b4 --- /dev/null +++ b/Seatbelt/Interop/COM.cs @@ -0,0 +1,16 @@ +using System; + +namespace Seatbelt.Interop +{ + internal class COM + { + [Flags] + public enum FirewallProfiles : int + { + DOMAIN = 1, + PRIVATE = 2, + PUBLIC = 4, + ALL = 2147483647 + } + } +} diff --git a/Seatbelt/Interop/Iphlpapi.cs b/Seatbelt/Interop/Iphlpapi.cs new file mode 100644 index 0000000..13a2d10 --- /dev/null +++ b/Seatbelt/Interop/Iphlpapi.cs @@ -0,0 +1,202 @@ +using System; +using System.Net; +using System.Runtime.InteropServices; + +namespace Seatbelt.Interop +{ + public class Iphlpapi + { + [DllImport("iphlpapi.dll", SetLastError = true)] + public static extern uint GetExtendedTcpTable( + IntPtr pTcpTable, + ref uint dwOutBufLen, + bool sort, + int ipVersion, + TCP_TABLE_CLASS tblClass, + int reserved); + + [DllImport("iphlpapi.dll", SetLastError = true)] + public static extern uint GetExtendedUdpTable( + IntPtr pUdpTable, + ref uint dwOutBufLen, + bool sort, + int ipVersion, + UDP_TABLE_CLASS tblClass, + int reserved); + + [DllImport("IpHlpApi.dll")] + [return: MarshalAs(UnmanagedType.U4)] + internal static extern int GetIpNetTable(IntPtr pIpNetTable, [MarshalAs(UnmanagedType.U4)]ref int pdwSize, bool bOrder); + + [DllImport("IpHlpApi.dll", SetLastError = true, CharSet = CharSet.Auto)] + internal static extern int FreeMibTable(IntPtr plpNetTable); + + public enum TCP_TABLE_CLASS : int + { + TCP_TABLE_BASIC_LISTENER, + TCP_TABLE_BASIC_CONNECTIONS, + TCP_TABLE_BASIC_ALL, + TCP_TABLE_OWNER_PID_LISTENER, + TCP_TABLE_OWNER_PID_CONNECTIONS, + TCP_TABLE_OWNER_PID_ALL, + TCP_TABLE_OWNER_MODULE_LISTENER, + TCP_TABLE_OWNER_MODULE_CONNECTIONS, + TCP_TABLE_OWNER_MODULE_ALL + } + + public enum MIB_TCP_STATE + { + CLOSED = 1, + LISTEN = 2, + SYN_SENT = 3, + SYN_RCVD = 4, + ESTAB = 5, + FIN_WAIT1 = 6, + FIN_WAIT2 = 7, + CLOSE_WAIT = 8, + CLOSING = 9, + LAST_ACK = 10, + TIME_WAIT = 11, + DELETE_TCB = 12 + } + + [StructLayout(LayoutKind.Sequential)] + public struct MIB_TCPTABLE_OWNER_MODULE + { + public uint NumEntries; + private readonly MIB_TCPROW_OWNER_MODULE Table; + } + + [StructLayout(LayoutKind.Sequential)] + public struct MIB_TCPROW_OWNER_MODULE + { + public readonly MIB_TCP_STATE State; + public readonly uint LocalAddr; + private readonly byte LocalPort1; + private readonly byte LocalPort2; + private readonly byte LocalPort3; + private readonly byte LocalPort4; + public readonly uint RemoteAddr; + private readonly byte RemotePort1; + private readonly byte RemotePort2; + private readonly byte RemotePort3; + private readonly byte RemotePort4; + public readonly uint OwningPid; + public readonly ulong CreateTimestamp; + public readonly ulong OwningModuleInfo0; + public readonly ulong OwningModuleInfo1; + public readonly ulong OwningModuleInfo2; + public readonly ulong OwningModuleInfo3; + public readonly ulong OwningModuleInfo4; + public readonly ulong OwningModuleInfo5; + public readonly ulong OwningModuleInfo6; + public readonly ulong OwningModuleInfo7; + public readonly ulong OwningModuleInfo8; + public readonly ulong OwningModuleInfo9; + public readonly ulong OwningModuleInfo10; + public readonly ulong OwningModuleInfo11; + public readonly ulong OwningModuleInfo12; + public readonly ulong OwningModuleInfo13; + public readonly ulong OwningModuleInfo14; + public readonly ulong OwningModuleInfo15; + + + public ushort LocalPort => BitConverter.ToUInt16(new byte[2] { LocalPort2, LocalPort1 }, 0); + + public IPAddress LocalAddress => new IPAddress(LocalAddr); + + public IPAddress RemoteAddress => new IPAddress(RemoteAddr); + + public ushort RemotePort => BitConverter.ToUInt16(new byte[2] { RemotePort2, RemotePort1 }, 0); + } + + #region UDP Interop + public enum UDP_TABLE_CLASS : int + { + UDP_TABLE_BASIC, + UDP_TABLE_OWNER_PID, + UDP_TABLE_OWNER_MODULE + } + + [StructLayout(LayoutKind.Sequential)] + public struct MIB_UDPTABLE_OWNER_MODULE + { + public uint NumEntries; + private readonly MIB_UDPROW_OWNER_MODULE Table; + } + + [StructLayout(LayoutKind.Sequential)] + public struct MIB_UDPROW_OWNER_MODULE + { + public readonly uint LocalAddr; + private readonly byte LocalPort1; + private readonly byte LocalPort2; + private readonly byte LocalPort3; + private readonly byte LocalPort4; + public readonly uint OwningPid; + public readonly ulong CreateTimestamp; + public readonly uint SpecificPortBind_Flags; + // public readonly UInt32 Flags; + public readonly ulong OwningModuleInfo0; + public readonly ulong OwningModuleInfo1; + public readonly ulong OwningModuleInfo2; + public readonly ulong OwningModuleInfo3; + public readonly ulong OwningModuleInfo4; + public readonly ulong OwningModuleInfo5; + public readonly ulong OwningModuleInfo6; + public readonly ulong OwningModuleInfo7; + public readonly ulong OwningModuleInfo8; + public readonly ulong OwningModuleInfo9; + public readonly ulong OwningModuleInfo10; + public readonly ulong OwningModuleInfo11; + public readonly ulong OwningModuleInfo12; + public readonly ulong OwningModuleInfo13; + public readonly ulong OwningModuleInfo14; + public readonly ulong OwningModuleInfo15; + + public ushort LocalPort => BitConverter.ToUInt16(new byte[2] { LocalPort2, LocalPort1 }, 0); + + public IPAddress LocalAddress => new IPAddress(LocalAddr); + } + + public enum ArpEntryType + { + Other = 1, + Invalid = 2, + Dynamic = 3, + Static = 4, + } + + [StructLayout(LayoutKind.Sequential)] + public struct MIB_IPNETROW + { + [MarshalAs(UnmanagedType.U4)] + public int dwIndex; + [MarshalAs(UnmanagedType.U4)] + public int dwPhysAddrLen; + [MarshalAs(UnmanagedType.U1)] + public byte mac0; + [MarshalAs(UnmanagedType.U1)] + public byte mac1; + [MarshalAs(UnmanagedType.U1)] + public byte mac2; + [MarshalAs(UnmanagedType.U1)] + public byte mac3; + [MarshalAs(UnmanagedType.U1)] + public byte mac4; + [MarshalAs(UnmanagedType.U1)] + public byte mac5; + [MarshalAs(UnmanagedType.U1)] + public byte mac6; + [MarshalAs(UnmanagedType.U1)] + public byte mac7; + [MarshalAs(UnmanagedType.U4)] + public int dwAddr; + [MarshalAs(UnmanagedType.U4)] + public int dwType; + } + + #endregion + + } +} diff --git a/Seatbelt/Interop/Kernel32.cs b/Seatbelt/Interop/Kernel32.cs new file mode 100644 index 0000000..350923b --- /dev/null +++ b/Seatbelt/Interop/Kernel32.cs @@ -0,0 +1,55 @@ +using System; +using System.Runtime.InteropServices; + +namespace Seatbelt.Interop +{ + internal class Kernel32 + { + [DllImport("kernel32.dll", SetLastError = true, CallingConvention = CallingConvention.Winapi)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool IsWow64Process( + [In] IntPtr hProcess, + [Out] out bool wow64Process + ); + + [DllImport("kernel32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool CloseHandle(IntPtr hObject); + + [DllImport("kernel32.dll", EntryPoint = "CopyMemory", SetLastError = false)] + public static extern void CopyMemory(IntPtr dest, IntPtr src, uint count); + + [DllImport("kernel32.dll")] + public static extern IntPtr LocalAlloc(uint uFlags, uint uBytes); + + [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] + public static extern IntPtr FindFirstFile(string lpFileName, out WIN32_FIND_DATA lpFindFileData); + + [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] + public static extern bool FindNextFile(IntPtr hFindFile, out WIN32_FIND_DATA + lpFindFileData); + + [DllImport("kernel32.dll", SetLastError = true)] + public static extern bool FindClose(IntPtr hFindFile); + + [DllImport("kernel32.dll")] + public static extern int GetLastError(); + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] + public struct WIN32_FIND_DATA + { + public uint dwFileAttributes; + public System.Runtime.InteropServices.ComTypes.FILETIME ftCreationTime; + public System.Runtime.InteropServices.ComTypes.FILETIME ftLastAccessTime; + public System.Runtime.InteropServices.ComTypes.FILETIME ftLastWriteTime; + public uint nFileSizeHigh; + public uint nFileSizeLow; + public uint dwReserved0; + public uint dwReserved1; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)] + public string cFileName; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 14)] + public string cAlternateFileName; + } + } +} diff --git a/Seatbelt/Interop/Mpr.cs b/Seatbelt/Interop/Mpr.cs new file mode 100644 index 0000000..3a62a2f --- /dev/null +++ b/Seatbelt/Interop/Mpr.cs @@ -0,0 +1,15 @@ +using System.Runtime.InteropServices; +using System.Text; + +namespace Seatbelt.Interop +{ + public class Mpr + { + [DllImport("mpr.dll", CharSet = CharSet.Unicode, SetLastError = true)] + public static extern int WNetGetConnection( + [MarshalAs(UnmanagedType.LPTStr)] string localName, + [MarshalAs(UnmanagedType.LPTStr)] StringBuilder remoteName, + ref int length + ); + } +} diff --git a/Seatbelt/Interop/Netapi32.cs b/Seatbelt/Interop/Netapi32.cs new file mode 100644 index 0000000..750a994 --- /dev/null +++ b/Seatbelt/Interop/Netapi32.cs @@ -0,0 +1,265 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Runtime.InteropServices; + +namespace Seatbelt.Interop +{ + internal class Netapi32 + { + [DllImport("Netapi32.dll")] + public static extern uint NetLocalGroupGetMembers( + [MarshalAs(UnmanagedType.LPWStr)] string? ServerName, + [MarshalAs(UnmanagedType.LPWStr)] string LocalGroupName, + int Level, + out IntPtr BufPtr, + int PreferredMaxLength, + out uint EntriesRead, + out uint TotalEntries, + out IntPtr ResumeHandle); + + [DllImport("Netapi32.dll")] + public static extern uint NetLocalGroupEnum( + [MarshalAs(UnmanagedType.LPWStr)] string? ServerName, + int Level, + out IntPtr BufPtr, + int PreferredMaxLength, + out uint EntriesRead, + out uint TotalEntries, + out IntPtr ResumeHandle); + + [DllImport("Netapi32.dll")] + public static extern uint NetUserEnum( + [MarshalAs(UnmanagedType.LPWStr)] string ServerName, + uint Level, + uint Filter, + out IntPtr BufPtr, + uint PreferredMaxLength, + out uint EntriesRead, + out uint TotalEntries, + out IntPtr ResumeHandle); + + [DllImport("Netapi32.dll")] + public static extern int NetApiBufferFree(IntPtr Buffer); + + [DllImport("Netapi32.dll")] + public static extern uint NetGetJoinInformation( + [MarshalAs(UnmanagedType.LPWStr)] string lpServer, + [MarshalAs(UnmanagedType.LPWStr)] string LocalGroupName, + int Level, + out IntPtr BufPtr, + int PreferredMaxLength, + out int EntriesRead, + out int TotalEntries, + out IntPtr ResumeHandle); + + public static uint MAX_PREFERRED_LENGTH = unchecked((uint)-1); + + public enum SidNameUse + { + User = 1, + Group, + Domain, + Alias, + WellKnownGroup, + DeletedAccount, + Invalid, + Unknown, + Computer + } + + public enum Priv + { + Guest = 0, + User = 1, + Administrator = 2 + } + + // LOCALGROUP_MEMBERS_INFO_2 - Structure for holding members details + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + public struct LOCALGROUP_MEMBERS_INFO_2 + { + public IntPtr lgrmi2_sid; + public SidNameUse lgrmi2_sidusage; + public string lgrmi2_domainandname; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct LOCALGROUP_INFO_1 + { + [MarshalAs(UnmanagedType.LPWStr)] public string name; + [MarshalAs(UnmanagedType.LPWStr)] public string comment; + } + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + public struct USER_INFO_3 + { + [MarshalAs(UnmanagedType.LPWStr)] public string name; + [MarshalAs(UnmanagedType.LPWStr)] public string password; + public uint passwordAge; + public uint priv; + [MarshalAs(UnmanagedType.LPWStr)] public string home_dir; + [MarshalAs(UnmanagedType.LPWStr)] public string comment; + public uint flags; + [MarshalAs(UnmanagedType.LPWStr)] public string script_path; + public uint auth_flags; + [MarshalAs(UnmanagedType.LPWStr)] public string full_name; + [MarshalAs(UnmanagedType.LPWStr)] public string usr_comment; + [MarshalAs(UnmanagedType.LPWStr)] public string parms; + [MarshalAs(UnmanagedType.LPWStr)] public string workstations; + public uint last_logon; + public uint last_logoff; + public uint acct_expires; + public uint max_storage; + public uint units_per_week; + public IntPtr logon_hours; + public uint bad_pw_count; + public uint num_logons; + [MarshalAs(UnmanagedType.LPWStr)] public string logon_server; + public uint country_code; + public uint code_page; + public uint user_id; + public uint primary_group_id; + [MarshalAs(UnmanagedType.LPWStr)] public string profile; + [MarshalAs(UnmanagedType.LPWStr)] public string home_dir_drive; + public uint password_expired; + } + + // TODO: Need to refactor and create separate classes for everywhere this is used + public class Principal + { + public Principal(string sid, SidNameUse? @class, string user, string domain) + { + Sid = sid; + Class = @class; + User = user; + Domain = domain; + } + public string Sid { get; } + public SidNameUse? Class { get; } + public string User { get; } + public string Domain { get; } + } + + #region Helper Functions + public static IEnumerable? GetLocalGroupMembers(string? computerName, string groupName) + { + // returns the "DOMAIN\user" members for a specified local group name + // adapted from boboes' code at https://stackoverflow.com/questions/33935825/pinvoke-netlocalgroupgetmembers-runs-into-fatalexecutionengineerror/33939889#33939889 + var members = new List(); + var retVal = NetLocalGroupGetMembers(computerName, groupName, 2, out var bufPtr, -1, out var EntriesRead, out var TotalEntries, out var Resume); + + if (retVal != 0) + { + var errorMessage = new Win32Exception(Marshal.GetLastWin32Error()).Message; + throw new Exception("Error code " + retVal + ": " + errorMessage); + } + + if (EntriesRead == 0) + return members; + + var names = new string[EntriesRead]; + var memberInfo = new LOCALGROUP_MEMBERS_INFO_2[EntriesRead]; + var iter = bufPtr; + + for (var i = 0; i < EntriesRead; i++) + { + memberInfo[i] = (LOCALGROUP_MEMBERS_INFO_2)Marshal.PtrToStructure(iter, typeof(LOCALGROUP_MEMBERS_INFO_2)); + + //x64 safe + iter = new IntPtr(iter.ToInt64() + Marshal.SizeOf(typeof(LOCALGROUP_MEMBERS_INFO_2))); + + + var nameParts = memberInfo[i].lgrmi2_domainandname.Split('\\'); + var domain = nameParts[0]; + var userName = ""; + if (nameParts.Length > 1) + { + userName = nameParts[1]; + } + + Advapi32.ConvertSidToStringSid(memberInfo[i].lgrmi2_sid, out var sid); + + members.Add(new Principal( + sid, + memberInfo[i].lgrmi2_sidusage, + userName, + domain + )); + } + NetApiBufferFree(bufPtr); + + return members; + } + + public static IEnumerable GetLocalGroups(string? computerName) + { + // Returns local groups (and comments) + var retVal = NetLocalGroupEnum(computerName, 1, out var bufPtr, -1, out var entriesRead, out var totalEntries, out var resumeHandle); + + if (retVal != 0) + { + var errorMessage = new Win32Exception(Marshal.GetLastWin32Error()).Message; + throw new Exception("Error code " + retVal + ": " + errorMessage); + } + + var groups = new List(); + + if (entriesRead == 0) + return groups; + + var names = new string[entriesRead]; + var groupInfo = new LOCALGROUP_INFO_1[entriesRead]; + var iter = bufPtr; + + + for (var i = 0; i < entriesRead; i++) + { + groupInfo[i] = (LOCALGROUP_INFO_1)Marshal.PtrToStructure(iter, typeof(LOCALGROUP_INFO_1)); + groups.Add(groupInfo[i]); + + //x64 safe + iter = new IntPtr(iter.ToInt64() + Marshal.SizeOf(typeof(LOCALGROUP_INFO_1))); + } + NetApiBufferFree(bufPtr); + + return groups; + + } + + public static IEnumerable GetLocalUsers(string computerName) + { + // Returns local users + // FILTER_NORMAL_ACCOUNT == 2 + var users = new List(); + var retVal = NetUserEnum(computerName, 3, 2, out var bufPtr, MAX_PREFERRED_LENGTH, out var EntriesRead, out var TotalEntries, out var Resume); + + if (retVal != 0) + { + var errorMessage = new Win32Exception(Marshal.GetLastWin32Error()).Message; + throw new Exception("Error code " + retVal + ": " + errorMessage); + } + + if (EntriesRead == 0) + return users; + + var names = new string[EntriesRead]; + var userInfo = new USER_INFO_3[EntriesRead]; + var iter = bufPtr; + + + for (var i = 0; i < EntriesRead; i++) + { + userInfo[i] = (USER_INFO_3)Marshal.PtrToStructure(iter, typeof(USER_INFO_3)); + users.Add(userInfo[i]); + + //x64 safe + iter = new IntPtr(iter.ToInt64() + Marshal.SizeOf(typeof(USER_INFO_3))); + } + NetApiBufferFree(bufPtr); + + return users; + } + #endregion + } +} diff --git a/Seatbelt/Interop/Rpcrt4.cs b/Seatbelt/Interop/Rpcrt4.cs new file mode 100644 index 0000000..b8b732d --- /dev/null +++ b/Seatbelt/Interop/Rpcrt4.cs @@ -0,0 +1,76 @@ +using System; +using System.Runtime.InteropServices; + +namespace Seatbelt.Interop +{ + internal class Rpcrt4 + { + // Ref - https://github.com/microsoft/WindowsProtocolTestSuites/blob/e4f325ce2ecbdecaa1db7190c37a7941a28eb0e5/ProtoSDK/Common/Rpc/RpcNativeMethods.cs#L326-L527 + + // REGION: setup + [DllImport("rpcrt4.dll", CharSet = CharSet.Auto)] + public static extern uint RpcStringBindingCompose( + string ObjUuid, + string Protseq, + string NetworkAddr, + string Endpoint, + string Options, + out IntPtr StringBinding); + + [DllImport("rpcrt4.dll", CharSet = CharSet.Auto)] + public static extern uint RpcBindingFromStringBinding( + IntPtr StringBinding, + out IntPtr Binding); + + [DllImport("rpcrt4.dll", CharSet = CharSet.Auto)] + public static extern uint RpcBindingToStringBinding( + IntPtr Binding, + out IntPtr StringBinding); + + + // REGION: enumeration + + [DllImport("rpcrt4.dll", CharSet = CharSet.Auto)] + public static extern uint RpcMgmtEpEltInqBegin( + IntPtr EpBinding, + int InquiryType, // 0x00000000 = RPC_C_EP_ALL_ELTS + int IfId, // going to be 0/NULL, so we don't care about "ref RPC_IF_ID IfId" + int VersOption, + int ObjectUuid, // going to be 0/NULL, so we don't care about "ref RPC_IF_ID IfId" + out IntPtr InquiryContext); + + [DllImport("rpcrt4.dll", CharSet = CharSet.Auto)] + public static extern uint RpcMgmtEpEltInqNext( + IntPtr InquiryContext, + ref RPC_IF_ID IfId, + out IntPtr Binding, + int ObjectUuid, // going to be 0/NULL, so we don't care about "ref RPC_IF_ID IfId" + out IntPtr Annotation + ); + + + // REGION: cleanup + + [DllImport("rpcrt4.dll", CharSet = CharSet.Auto)] + public static extern uint RpcStringFree( + ref IntPtr String); + + [DllImport("rpcrt4.dll", CharSet = CharSet.Auto)] + public static extern uint RpcMgmtEpEltInqDone( + ref IntPtr InquiryContext); + + [DllImport("rpcrt4.dll", CharSet = CharSet.Auto)] + public static extern uint RpcBindingFree( + ref IntPtr Binding); + + + // REGION: structures + + public struct RPC_IF_ID + { + public Guid Uuid; + public ushort VersMajor; + public ushort VersMinor; + } + } +} diff --git a/Seatbelt/Interop/Secur32.cs b/Seatbelt/Interop/Secur32.cs new file mode 100644 index 0000000..3ef9268 --- /dev/null +++ b/Seatbelt/Interop/Secur32.cs @@ -0,0 +1,294 @@ +using System; +using System.Runtime.InteropServices; + +namespace Seatbelt.Interop +{ + public class Secur32 + { + #region Function Defintions + [DllImport("secur32.dll", SetLastError = false)] + public static extern uint LsaFreeReturnBuffer( + IntPtr buffer + ); + + [DllImport("Secur32.dll", SetLastError = false)] + public static extern uint LsaEnumerateLogonSessions( + out UInt64 LogonSessionCount, + out IntPtr LogonSessionList + ); + + [DllImport("Secur32.dll", SetLastError = false)] + public static extern uint LsaGetLogonSessionData( + IntPtr luid, + out IntPtr ppLogonSessionData + ); + + [DllImport("Secur32.dll", SetLastError = false)] + public static extern uint EnumerateSecurityPackages( + out UInt64 pcPackages, + out IntPtr ppPackageInfo + ); + + [DllImport("Secur32.dll")] + public static extern int FreeContextBuffer(IntPtr pvContextBuffer); + #endregion + + #region Structure Defintions + [StructLayout(LayoutKind.Sequential)] + public struct LSA_STRING_IN + { + public ushort Length; + public ushort MaximumLength; + public string Buffer; + } + + [StructLayout(LayoutKind.Sequential)] + public struct LSA_STRING_OUT + { + public ushort Length; + public ushort MaximumLength; + public IntPtr Buffer; + } + + [StructLayout(LayoutKind.Sequential)] + public struct UNICODE_STRING : IDisposable + { + public ushort Length; + public ushort MaximumLength; + public IntPtr buffer; + + public UNICODE_STRING(string s) + { + Length = (ushort)(s.Length * 2); + MaximumLength = (ushort)(Length + 2); + buffer = Marshal.StringToHGlobalUni(s); + } + + public void Dispose() + { + Marshal.FreeHGlobal(buffer); + buffer = IntPtr.Zero; + } + + public override string ToString() + { + return Marshal.PtrToStringUni(buffer); + } + } + + public enum SECURITY_LOGON_TYPE : uint + { + Interactive = 2, // logging on interactively. + Network, // logging using a network. + Batch, // logon for a batch process. + Service, // logon for a service account. + Proxy, // Not supported. + Unlock, // Tattempt to unlock a workstation. + NetworkCleartext, // network logon with cleartext credentials + NewCredentials, // caller can clone its current token and specify new credentials for outbound connections + RemoteInteractive, // terminal server session that is both remote and interactive + CachedInteractive, // attempt to use the cached credentials without going out across the network + CachedRemoteInteractive,// same as RemoteInteractive, except used internally for auditing purposes + CachedUnlock // attempt to unlock a workstation + } + + [StructLayout(LayoutKind.Sequential)] + public struct SECURITY_LOGON_SESSION_DATA + { + public uint Size; + public LUID LoginID; + public LSA_STRING_OUT Username; + public LSA_STRING_OUT LoginDomain; + public LSA_STRING_OUT AuthenticationPackage; + public uint LogonType; + public uint Session; + public IntPtr PSiD; + public ulong LoginTime; + public LSA_STRING_OUT LogonServer; + public LSA_STRING_OUT DnsDomainName; + public LSA_STRING_OUT Upn; + } + + [StructLayout(LayoutKind.Sequential)] + public struct SecPkgInfo + { + public SECPKG_FLAGS fCapabilities; + public short wVersion; + public short wRPCID; + public uint cbMaxToken; + [MarshalAs(UnmanagedType.LPStr)] + public string Name; + [MarshalAs(UnmanagedType.LPStr)] + public string Comment; + } + + [StructLayout(LayoutKind.Sequential)] + public struct LUID_AND_ATTRIBUTES + { + public LUID Luid; + public uint Attributes; + } + + [StructLayout(LayoutKind.Sequential)] + public struct LUID + { + public UInt32 LowPart; + public Int32 HighPart; + + public LUID(UInt64 value) + { + LowPart = (UInt32)(value & 0xffffffffL); + HighPart = (Int32)(value >> 32); + } + + public LUID(LUID value) + { + LowPart = value.LowPart; + HighPart = value.HighPart; + } + + public LUID(string value) + { + if (System.Text.RegularExpressions.Regex.IsMatch(value, @"^0x[0-9A-Fa-f]+$")) + { + // if the passed LUID string is of form 0xABC123 + UInt64 uintVal = Convert.ToUInt64(value, 16); + LowPart = (UInt32)(uintVal & 0xffffffffL); + HighPart = (Int32)(uintVal >> 32); + } + else if (System.Text.RegularExpressions.Regex.IsMatch(value, @"^\d+$")) + { + // if the passed LUID string is a decimal form + UInt64 uintVal = UInt64.Parse(value); + LowPart = (UInt32)(uintVal & 0xffffffffL); + HighPart = (Int32)(uintVal >> 32); + } + else + { + ArgumentException argEx = new ArgumentException("Passed LUID string value is not in a hex or decimal form", value); + throw argEx; + } + } + + public override int GetHashCode() + { + UInt64 Value = ((UInt64)HighPart << 32) + LowPart; + return Value.GetHashCode(); + } + + public override bool Equals(object obj) + { + return obj is LUID && (((ulong)this) == (LUID)obj); + } + + public byte[] GetBytes() + { + byte[] bytes = new byte[8]; + + byte[] lowBytes = BitConverter.GetBytes(LowPart); + byte[] highBytes = BitConverter.GetBytes(HighPart); + + Array.Copy(lowBytes, 0, bytes, 0, 4); + Array.Copy(highBytes, 0, bytes, 4, 4); + + return bytes; + } + + public override string ToString() + { + UInt64 Value = ((UInt64)HighPart << 32) + LowPart; + return String.Format("0x{0:x}", (ulong)Value); + } + + public static bool operator ==(LUID x, LUID y) + { + return (((ulong)x) == ((ulong)y)); + } + + public static bool operator !=(LUID x, LUID y) + { + return (((ulong)x) != ((ulong)y)); + } + + public static implicit operator ulong(LUID luid) + { + // enable casting to a ulong + UInt64 Value = ((UInt64)luid.HighPart << 32); + return Value + luid.LowPart; + } + } + + [StructLayout(LayoutKind.Sequential)] + public struct SID_AND_ATTRIBUTES + { + public IntPtr Sid; + public uint Attributes; + } + + [StructLayout(LayoutKind.Sequential)] + public struct TOKEN_GROUPS + { + public int GroupCount; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 1)] + public SID_AND_ATTRIBUTES[] Groups; + }; + + [StructLayout(LayoutKind.Sequential)] + public struct SECURITY_HANDLE + { + public IntPtr LowPart; + public IntPtr HighPart; + }; + + public struct TOKEN_PRIVILEGES + { + public uint PrivilegeCount; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 35)] + public LUID_AND_ATTRIBUTES[] Privileges; + + public TOKEN_PRIVILEGES(uint PrivilegeCount, LUID_AND_ATTRIBUTES[] Privileges) + { + this.PrivilegeCount = PrivilegeCount; + this.Privileges = Privileges; + } + } + + #endregion + + #region Enum Definitions + + [Flags] + public enum SECPKG_FLAGS : uint + { + INTEGRITY = 0x1, + PRIVACY = 0x2, + TOKEN_ONLY = 0x4, + DATAGRAM = 0x8, + CONNECTION = 0x10, + MULTI_REQUIRED = 0x20, + CLIENT_ONLY = 0x40, + EXTENDED_ERROR = 0x80, + IMPERSONATION = 0x100, + ACCEPT_WIN32_NAME = 0x200, + STREAM = 0x400, + NEGOTIABLE = 0X800, + GSS_COMPATIBLE = 0x1000, + LOGON = 0x2000, + ASCII_BUFFERS = 0x4000, + FRAGMENT = 0x8000, + MUTUAL_AUTH = 0x10000, + DELEGATION = 0x20000, + READONLY_WITH_CHECKSUM = 0x40000, + RESTRICTED_TOKENS = 0x80000, + NEGO_EXTENDER = 0x00100000, + NEGOTIABLE2 = 0x00200000, + APPCONTAINER_PASSTHROUGH = 0x00400000, + APPCONTAINER_CHECKS = 0x00800000 + } + #endregion + + #region Helper Functions + + #endregion + } +} diff --git a/Seatbelt/Interop/Shell32.cs b/Seatbelt/Interop/Shell32.cs new file mode 100644 index 0000000..309e7cf --- /dev/null +++ b/Seatbelt/Interop/Shell32.cs @@ -0,0 +1,34 @@ +using System; +using System.Runtime.InteropServices; + +namespace Seatbelt.Interop +{ + internal class Shell32 + { + [DllImport("shell32.dll", SetLastError = true)] + private static extern IntPtr CommandLineToArgvW( + [MarshalAs(UnmanagedType.LPWStr)] string lpCmdLine, out int pNumArgs); + + public static string[] CommandLineToArgs(string commandLine) + { + var argv = CommandLineToArgvW(commandLine, out var argc); + if (argv == IntPtr.Zero) + throw new System.ComponentModel.Win32Exception(); + try + { + var args = new string[argc]; + for (var i = 0; i < args.Length; i++) + { + var p = Marshal.ReadIntPtr(argv, i * IntPtr.Size); + args[i] = Marshal.PtrToStringUni(p); + } + + return args; + } + finally + { + Marshal.FreeHGlobal(argv); + } + } + } +} diff --git a/Seatbelt/Interop/Shlwapi.cs b/Seatbelt/Interop/Shlwapi.cs new file mode 100644 index 0000000..13ebd29 --- /dev/null +++ b/Seatbelt/Interop/Shlwapi.cs @@ -0,0 +1,17 @@ +using System.Runtime.InteropServices; + +namespace Seatbelt.Interop +{ + class Shlwapi + { + public static bool IsWindowsServer() + { + return IsOS(OS_ANYSERVER); + } + + const int OS_ANYSERVER = 29; + + [DllImport("shlwapi.dll", SetLastError = true, EntryPoint = "#437")] + private static extern bool IsOS(int os); + } +} diff --git a/Seatbelt/Interop/User32.cs b/Seatbelt/Interop/User32.cs new file mode 100644 index 0000000..43d51bb --- /dev/null +++ b/Seatbelt/Interop/User32.cs @@ -0,0 +1,19 @@ +using System.Runtime.InteropServices; + +namespace Seatbelt.Interop +{ + internal class User32 + { + [DllImport("User32.dll")] + public static extern bool GetLastInputInfo(ref LastInputInfo lastInputInfo); + + [DllImport("user32.dll")] + public static extern bool SetProcessDPIAware(); + + internal struct LastInputInfo + { + public uint Size; + public uint Time; + } + } +} diff --git a/Seatbelt/Interop/VaultCli.cs b/Seatbelt/Interop/VaultCli.cs new file mode 100644 index 0000000..2132bfd --- /dev/null +++ b/Seatbelt/Interop/VaultCli.cs @@ -0,0 +1,101 @@ +using System; +using System.Runtime.InteropServices; + +namespace Seatbelt +{ + public static class VaultCli + { + // pulled directly from @djhohnstein's SharpWeb project: https://github.com/djhohnstein/SharpWeb/blob/master/Edge/SharpEdge.cs + public enum VAULT_ELEMENT_TYPE : int + { + Undefined = -1, + Boolean = 0, + Short = 1, + UnsignedShort = 2, + Int = 3, + UnsignedInt = 4, + Double = 5, + Guid = 6, + String = 7, + ByteArray = 8, + TimeStamp = 9, + ProtectedArray = 10, + Attribute = 11, + Sid = 12, + Last = 13 + } + + public enum VAULT_SCHEMA_ELEMENT_ID : int + { + Illegal = 0, + Resource = 1, + Identity = 2, + Authenticator = 3, + Tag = 4, + PackageSid = 5, + AppStart = 100, + AppEnd = 10000 + } + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] + public struct VAULT_ITEM_WIN8 + { + public Guid SchemaId; + public IntPtr pszCredentialFriendlyName; + public IntPtr pResourceElement; + public IntPtr pIdentityElement; + public IntPtr pAuthenticatorElement; + public IntPtr pPackageSid; + public ulong LastModified; + public uint dwFlags; + public uint dwPropertiesCount; + public IntPtr pPropertyElements; + } + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] + public struct VAULT_ITEM_WIN7 + { + public Guid SchemaId; + public IntPtr pszCredentialFriendlyName; + public IntPtr pResourceElement; + public IntPtr pIdentityElement; + public IntPtr pAuthenticatorElement; + public ulong LastModified; + public uint dwFlags; + public uint dwPropertiesCount; + public IntPtr pPropertyElements; + } + + [StructLayout(LayoutKind.Explicit, CharSet = CharSet.Ansi)] + public struct VAULT_ITEM_ELEMENT + { + [FieldOffset(0)] + public VAULT_SCHEMA_ELEMENT_ID SchemaElementId; + [FieldOffset(8)] + public VAULT_ELEMENT_TYPE Type; + } + + [DllImport("vaultcli.dll")] + public static extern int VaultOpenVault(ref Guid vaultGuid, uint offset, ref IntPtr vaultHandle); + + [DllImport("vaultcli.dll")] + public static extern int VaultCloseVault(ref IntPtr vaultHandle); + + [DllImport("vaultcli.dll")] + public static extern int VaultFree(ref IntPtr vaultHandle); + + [DllImport("vaultcli.dll")] + public static extern int VaultEnumerateVaults(int offset, ref int vaultCount, ref IntPtr vaultGuid); + + [DllImport("vaultcli.dll")] + public static extern int VaultEnumerateItems(IntPtr vaultHandle, int chunkSize, ref int vaultItemCount, ref IntPtr vaultItem); + + [DllImport("vaultcli.dll", EntryPoint = "VaultGetItem")] + public static extern int VaultGetItem_WIN8(IntPtr vaultHandle, ref Guid schemaId, IntPtr pResourceElement, IntPtr pIdentityElement, IntPtr pPackageSid, IntPtr zero, int arg6, ref IntPtr passwordVaultPtr); + + [DllImport("vaultcli.dll", EntryPoint = "VaultGetItem")] + public static extern int VaultGetItem_WIN7(IntPtr vaultHandle, ref Guid schemaId, IntPtr pResourceElement, IntPtr pIdentityElement, IntPtr zero, int arg5, ref IntPtr passwordVaultPtr); + + } + +} diff --git a/Seatbelt/Interop/Win32Error.cs b/Seatbelt/Interop/Win32Error.cs new file mode 100644 index 0000000..f88e446 --- /dev/null +++ b/Seatbelt/Interop/Win32Error.cs @@ -0,0 +1,18 @@ +namespace Seatbelt.Interop +{ + // Defined at https://msdn.microsoft.com/en-us/library/cc231199.aspx + // Only list error codes that are actually used in our code + internal class Win32Error + { + public const int Success = 0; + public const int NERR_Success = 0; + public const int AccessDenied = 0x0000005; + public const int NotEnoughMemory = 0x00000008; + public const int InsufficientBuffer = 0x0000007A; + public const int MoreData = 0x00000EA; + public const int NoSuchAlias = 0x0000560; + public const int RpcServerUnavailable = 0x0006BA; + public const int NERR_GroupNotFound = 0x00008AC; + public const int NERR_InvalidComputer = 0x000092F; + } +} diff --git a/Seatbelt/Interop/Wtsapi32.cs b/Seatbelt/Interop/Wtsapi32.cs new file mode 100644 index 0000000..25336b2 --- /dev/null +++ b/Seatbelt/Interop/Wtsapi32.cs @@ -0,0 +1,134 @@ +using System; +using System.Runtime.InteropServices; + +namespace Seatbelt.Interop +{ + internal class Wtsapi32 + { + [DllImport("wtsapi32.dll", SetLastError = true)] + public static extern IntPtr WTSOpenServer([MarshalAs(UnmanagedType.LPStr)] string pServerName); + + [DllImport("wtsapi32.dll")] + public static extern void WTSCloseServer(IntPtr hServer); + + [DllImport("wtsapi32.dll", SetLastError = true)] + public static extern int WTSEnumerateSessions( + IntPtr hServer, + [MarshalAs(UnmanagedType.U4)] int Reserved, + [MarshalAs(UnmanagedType.U4)] int Version, + ref IntPtr ppSessionInfo, + [MarshalAs(UnmanagedType.U4)] ref int pCount); + + [DllImport("wtsapi32.dll", SetLastError = true)] + public static extern int WTSEnumerateSessionsEx( + IntPtr hServer, + [MarshalAs(UnmanagedType.U4)] ref int pLevel, + [MarshalAs(UnmanagedType.U4)] int Filter, + ref IntPtr ppSessionInfo, + [MarshalAs(UnmanagedType.U4)] ref int pCount); + + [DllImport("wtsapi32.dll")] + public static extern void WTSFreeMemory(IntPtr pMemory); + + [DllImport("Wtsapi32.dll", SetLastError = true)] + public static extern bool WTSQuerySessionInformation( + IntPtr hServer, + uint sessionId, + WTS_INFO_CLASS wtsInfoClass, + out IntPtr ppBuffer, + out uint pBytesReturned + ); + + [StructLayout(LayoutKind.Sequential)] + public struct WTS_SESSION_INFO + { + public int SessionID; + + [MarshalAs(UnmanagedType.LPStr)] + public string pWinStationName; + + public WTS_CONNECTSTATE_CLASS State; + } + + [StructLayout(LayoutKind.Sequential)] + public struct WTS_SESSION_INFO_1 + { + public int ExecEnvId; + + public WTS_CONNECTSTATE_CLASS State; + + public int SessionID; + + [MarshalAs(UnmanagedType.LPStr)] + public string pSessionName; + + [MarshalAs(UnmanagedType.LPStr)] + public string pHostName; + + [MarshalAs(UnmanagedType.LPStr)] + public string pUserName; + + [MarshalAs(UnmanagedType.LPStr)] + public string pDomainName; + + [MarshalAs(UnmanagedType.LPStr)] + public string pFarmName; + } + + [StructLayout(LayoutKind.Sequential)] + public struct WTS_CLIENT_ADDRESS + { + public uint AddressFamily; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 20)] + public byte[] Address; + } + + public enum WTS_CONNECTSTATE_CLASS + { + Active, + Connected, + ConnectQuery, + Shadow, + Disconnected, + Idle, + Listen, + Reset, + Down, + Init + } + + public enum WTS_INFO_CLASS + { + WTSInitialProgram = 0, + WTSApplicationName = 1, + WTSWorkingDirectory = 2, + WTSOEMId = 3, + WTSSessionId = 4, + WTSUserName = 5, + WTSWinStationName = 6, + WTSDomainName = 7, + WTSConnectState = 8, + WTSClientBuildNumber = 9, + WTSClientName = 10, + WTSClientDirectory = 11, + WTSClientProductId = 12, + WTSClientHardwareId = 13, + WTSClientAddress = 14, + WTSClientDisplay = 15, + WTSClientProtocolType = 16, + WTSIdleTime = 17, + WTSLogonTime = 18, + WTSIncomingBytes = 19, + WTSOutgoingBytes = 20, + WTSIncomingFrames = 21, + WTSOutgoingFrames = 22, + WTSClientInfo = 23, + WTSSessionInfo = 24, + WTSSessionInfoEx = 25, + WTSConfigInfo = 26, + WTSValidationInfo = 27, + WTSSessionAddressV4 = 28, + WTSIsRemoteSession = 29 + } + } +} diff --git a/Seatbelt/Output/Formatters/DefaultTextFormatter.cs b/Seatbelt/Output/Formatters/DefaultTextFormatter.cs new file mode 100644 index 0000000..c155272 --- /dev/null +++ b/Seatbelt/Output/Formatters/DefaultTextFormatter.cs @@ -0,0 +1,48 @@ +using Seatbelt.Commands; +using Seatbelt.Output.TextWriters; + +namespace Seatbelt.Output.Formatters +{ + // If a command doesn't customize its output text, then this will be called + internal class DefaultTextFormatter : TextFormatterBase + { + public DefaultTextFormatter(ITextWriter writer) : base(writer) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + if (result == null) + { + return; + } + + var type = result.GetType(); + + foreach (var p in type.GetProperties()) + { + if (p.PropertyType.IsArray) + { + var value = (object[])p.GetValue(result, null); + Write($" {p.Name,-30} : "); + for (var i = 0; i < value.Length; i++) + { + Write(value[i].ToString()); + if (value.Length-1 != i) + { + Write(", "); + } + } + WriteLine(); + } + else + { + var value = p.GetValue(result, null); + WriteLine(" {0,-30} : {1}", p.Name, value); + } + } + + WriteLine(); + } + } +} \ No newline at end of file diff --git a/Seatbelt/Output/Formatters/TextFormatterBase.cs b/Seatbelt/Output/Formatters/TextFormatterBase.cs new file mode 100644 index 0000000..9127091 --- /dev/null +++ b/Seatbelt/Output/Formatters/TextFormatterBase.cs @@ -0,0 +1,27 @@ +using Seatbelt.Commands; +using Seatbelt.Output.TextWriters; + +// Individual commands can impelement this this class if they want to format textual output in a certain way +namespace Seatbelt.Output.Formatters +{ + internal abstract class TextFormatterBase + { + private readonly ITextWriter _textWriter; + + protected TextFormatterBase(ITextWriter sink) + { + _textWriter = sink; + } + + // Children implement this method to customize the command's string output + public abstract void FormatResult(CommandBase? command, CommandDTOBase results, bool filterResults); + + protected void Write(string str) => _textWriter.Write(str); + protected void WriteLine() => _textWriter.WriteLine(); + protected void WriteLine(string str) => _textWriter.WriteLine(str); + protected void WriteLine(string format, object? arg0) => _textWriter.WriteLine(format, arg0); + protected void WriteLine(string format, object? arg0, object? arg1) => _textWriter.WriteLine(format, arg0, arg1); + protected void WriteLine(string format, object arg0, object arg1, object arg2) => _textWriter.WriteLine(format, arg0, arg1, arg2); + protected void WriteLine(string format, params object?[] args) => _textWriter.WriteLine(format, args); + } +} diff --git a/Seatbelt/Output/Sinks/IOutputSink.cs b/Seatbelt/Output/Sinks/IOutputSink.cs new file mode 100644 index 0000000..70f37e7 --- /dev/null +++ b/Seatbelt/Output/Sinks/IOutputSink.cs @@ -0,0 +1,13 @@ +using Seatbelt.Commands; + +namespace Seatbelt.Output.Sinks +{ + internal interface IOutputSink + { + void WriteOutput(CommandDTOBase dto); + void WriteHost(string message); + void WriteVerbose(string message); + void WriteWarning(string message); + void WriteError(string message); + } +} diff --git a/Seatbelt/Output/Sinks/JsonFileOutputSink.cs b/Seatbelt/Output/Sinks/JsonFileOutputSink.cs new file mode 100644 index 0000000..c33dc67 --- /dev/null +++ b/Seatbelt/Output/Sinks/JsonFileOutputSink.cs @@ -0,0 +1,73 @@ +using System; +using System.IO; +using System.Web.Script.Serialization; +using Seatbelt.Commands; + +namespace Seatbelt.Output.Sinks +{ + // Any sinks that output text to a location should inherit from this class + internal class JsonFileOutputSink : IOutputSink, IDisposable + { + private StreamWriter _stream; + + + public JsonFileOutputSink(string file, bool filterResults) + { + if (File.Exists(file)) + { + File.Delete(file); + } + + _stream = File.CreateText(file); + _stream.AutoFlush = true; + } + + public void WriteOutput(CommandDTOBase dto) + { + //var obj = dtoCollection?.FirstOrDefault(); + if (dto == null) + { + return; + } + + // If the dto has a custom output sink, use it. Otherwise, use the default output sink + var dtoType = dto.GetType(); + var json = new JavaScriptSerializer(); + + var obj = new + { + Type = dtoType.ToString(), + Data = dto + }; + + string jsonStr; + try + { + jsonStr = json.Serialize(obj); + } + catch(Exception e) + { + jsonStr = json.Serialize(new + { + Type = typeof(ErrorDTO).ToString(), + Data = json.Serialize(new ErrorDTO(e.ToString())) + }); + } + + _stream.WriteLine(jsonStr); + } + + public void WriteVerbose(string message) => WriteOutput(new VerboseDTO(message)); + + public void WriteWarning(string message) => WriteOutput(new WarningDTO(message)); + + public void WriteError(string message) => WriteOutput(new ErrorDTO(message)); + + public void WriteHost(string message) => WriteOutput(new HostDTO(message)); + + public void Dispose() + { + _stream?.Dispose(); + } + } +} diff --git a/Seatbelt/Output/Sinks/TextOutputSink.cs b/Seatbelt/Output/Sinks/TextOutputSink.cs new file mode 100644 index 0000000..5dc31a5 --- /dev/null +++ b/Seatbelt/Output/Sinks/TextOutputSink.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using Seatbelt.Commands; +using Seatbelt.Output.Formatters; +using Seatbelt.Output.TextWriters; + +namespace Seatbelt.Output.Sinks +{ + // Any sinks that output text to a location should inherit from this class + internal class TextOutputSink : IOutputSink + { + private readonly Dictionary _customSinks = new Dictionary(); + private readonly TextFormatterBase _defaultTextSink; + + private readonly ITextWriter _textWriter; + private readonly bool _filterResults; + + public TextOutputSink(ITextWriter writer, bool filterResults) + { + _textWriter = writer; + _filterResults = filterResults; + + // If a command doesn't customize its output, the default text outputter will be used + _defaultTextSink = new DefaultTextFormatter(_textWriter); + InitializeCustomTextFormatters(); + } + + private void InitializeCustomTextFormatters() + { + var currentAssembly = Assembly.GetExecutingAssembly(); + foreach (var formatter in currentAssembly.GetTypes().Where(t => typeof(TextFormatterBase).IsAssignableFrom(t))) + { + var attributes = Attribute.GetCustomAttributes(formatter); + + foreach (var t in attributes) + { + if (!(t is CommandOutputTypeAttribute)) continue; + + var outputTypeAttr = (CommandOutputTypeAttribute) t; + + if (_customSinks.ContainsKey(outputTypeAttr.Type)) + { + throw new Exception($"Custom sink {outputTypeAttr.Type} already assigned to {_customSinks[outputTypeAttr.Type]}. Could not associate DTO with the another formatter({formatter})"); + } + + _customSinks.Add(outputTypeAttr.Type, (TextFormatterBase)Activator.CreateInstance(formatter, new object[] { _textWriter })); + break; + } + } + + } + + public void WriteOutput(CommandDTOBase dto) + { + //var obj = dtoCollection?.FirstOrDefault(); + if (dto == null) + { + return; + } + + // If the dto has a custom output sink, use it. Otherwise, use the default output sink + var dtoType = dto.GetType(); + if (_customSinks.ContainsKey(dtoType)) + { + _customSinks[dtoType].FormatResult(null, dto, _filterResults); + } + else + { + _defaultTextSink.FormatResult(null, dto, _filterResults); + } + } + + public void WriteVerbose(string message) => WriteOutput(new VerboseDTO(message)); + + public void WriteWarning(string message) => WriteOutput(new WarningDTO(message)); + + public void WriteError(string message) => WriteOutput(new ErrorDTO(message)); + + public void WriteHost(string message) => WriteOutput(new HostDTO(message)); + } +} diff --git a/Seatbelt/Output/TextWriters/ConsoleTextWriter.cs b/Seatbelt/Output/TextWriters/ConsoleTextWriter.cs new file mode 100644 index 0000000..f615ae7 --- /dev/null +++ b/Seatbelt/Output/TextWriters/ConsoleTextWriter.cs @@ -0,0 +1,22 @@ +using System; + +namespace Seatbelt.Output.TextWriters +{ + internal class ConsoleTextWriter : ITextWriter + { + public void Write(string str) + => Console.Write(str); + + public void WriteLine() + => Console.WriteLine(); + + public void WriteLine(string str) + => Console.WriteLine(str); + + public void WriteLine(string format, params object?[] args) => Console.WriteLine(format, args); + + public void FlushAndClose() + { + } + } +} diff --git a/Seatbelt/Output/TextWriters/FileTextWriter.cs b/Seatbelt/Output/TextWriters/FileTextWriter.cs new file mode 100644 index 0000000..aa4e1b2 --- /dev/null +++ b/Seatbelt/Output/TextWriters/FileTextWriter.cs @@ -0,0 +1,46 @@ +using System; +using System.IO; + +namespace Seatbelt.Output.TextWriters +{ + internal class FileTextWriter : ITextWriter, IDisposable + { + private readonly StreamWriter _stream; + + public FileTextWriter(string fileName) + { + if (File.Exists(fileName)) + { + File.Delete(fileName); + } + + _stream = File.CreateText(fileName); + _stream.AutoFlush = true; + } + + + + public void Write(string str) + => _stream.Write(str); + + public void WriteLine() + => _stream.WriteLine(); + + public void WriteLine(string str) + => _stream.WriteLine(str); + + public void WriteLine(string format, params object?[] args) + => _stream.WriteLine(format, args); + + public void FlushAndClose() + { + _stream.Flush(); + _stream.Dispose(); + } + + public void Dispose() + { + _stream.Dispose(); + } + } +} diff --git a/Seatbelt/Output/TextWriters/ITextWriter.cs b/Seatbelt/Output/TextWriters/ITextWriter.cs new file mode 100644 index 0000000..5681ec0 --- /dev/null +++ b/Seatbelt/Output/TextWriters/ITextWriter.cs @@ -0,0 +1,11 @@ +namespace Seatbelt.Output.TextWriters +{ + internal interface ITextWriter + { + void Write(string str); + void WriteLine(); + void WriteLine(string str); + void WriteLine(string format, params object?[] args); + void FlushAndClose(); + } +} \ No newline at end of file diff --git a/Seatbelt/Program.cs b/Seatbelt/Program.cs index 51d189a..1de200a 100755 --- a/Seatbelt/Program.cs +++ b/Seatbelt/Program.cs @@ -1,6926 +1,10 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Diagnostics; -using System.Diagnostics.Eventing.Reader; -using System.IO; -using System.Linq; -using System.Management; -using System.Net; -using System.Net.NetworkInformation; -using System.Reflection; -using System.Runtime.InteropServices; -using System.Security.AccessControl; -using System.Security.Principal; -using System.Text; -using System.Text.RegularExpressions; -using System.Web.Script.Serialization; -using Microsoft.Win32; -using System.Xml; - -namespace Seatbelt +namespace Seatbelt { - // used to fignal whether filtering should be done on results - public static class FilterResults + public static class Program { - public static bool filter = true; - } - - public static class NetworkAPI - { - // from boboes' code at https://stackoverflow.com/questions/33935825/pinvoke-netlocalgroupgetmembers-runs-into-fatalexecutionengineerror/33939889#33939889 - - [DllImport("Netapi32.dll")] - public extern static uint NetLocalGroupGetMembers([MarshalAs(UnmanagedType.LPWStr)] string servername, [MarshalAs(UnmanagedType.LPWStr)] string localgroupname, int level, out IntPtr bufptr, int prefmaxlen, out int entriesread, out int totalentries, out IntPtr resumehandle); - - [DllImport("Netapi32.dll")] - public extern static int NetApiBufferFree(IntPtr Buffer); - - // LOCALGROUP_MEMBERS_INFO_2 - Structure for holding members details - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] - public struct LOCALGROUP_MEMBERS_INFO_2 + private static void Main(string[] args) { - public IntPtr lgrmi2_sid; - public int lgrmi2_sidusage; - public string lgrmi2_domainandname; - } - - // documented in MSDN - public const uint ERROR_ACCESS_DENIED = 0x0000005; - public const uint ERROR_MORE_DATA = 0x00000EA; - public const uint ERROR_NO_SUCH_ALIAS = 0x0000560; - public const uint NERR_InvalidComputer = 0x000092F; - - // found by testing - public const uint NERR_GroupNotFound = 0x00008AC; - public const uint SERVER_UNAVAILABLE = 0x0006BA; - } - - public static class VaultCli - { - // pulled directly from @djhohnstein's SharpWeb project: https://github.com/djhohnstein/SharpWeb/blob/master/Edge/SharpEdge.cs - public enum VAULT_ELEMENT_TYPE : Int32 - { - Undefined = -1, - Boolean = 0, - Short = 1, - UnsignedShort = 2, - Int = 3, - UnsignedInt = 4, - Double = 5, - Guid = 6, - String = 7, - ByteArray = 8, - TimeStamp = 9, - ProtectedArray = 10, - Attribute = 11, - Sid = 12, - Last = 13 - } - - public enum VAULT_SCHEMA_ELEMENT_ID : Int32 - { - Illegal = 0, - Resource = 1, - Identity = 2, - Authenticator = 3, - Tag = 4, - PackageSid = 5, - AppStart = 100, - AppEnd = 10000 - } - - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] - public struct VAULT_ITEM_WIN8 - { - public Guid SchemaId; - public IntPtr pszCredentialFriendlyName; - public IntPtr pResourceElement; - public IntPtr pIdentityElement; - public IntPtr pAuthenticatorElement; - public IntPtr pPackageSid; - public UInt64 LastModified; - public UInt32 dwFlags; - public UInt32 dwPropertiesCount; - public IntPtr pPropertyElements; - } - - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] - public struct VAULT_ITEM_WIN7 - { - public Guid SchemaId; - public IntPtr pszCredentialFriendlyName; - public IntPtr pResourceElement; - public IntPtr pIdentityElement; - public IntPtr pAuthenticatorElement; - public UInt64 LastModified; - public UInt32 dwFlags; - public UInt32 dwPropertiesCount; - public IntPtr pPropertyElements; - } - - [StructLayout(LayoutKind.Explicit, CharSet = CharSet.Ansi)] - public struct VAULT_ITEM_ELEMENT - { - [FieldOffset(0)] - public VAULT_SCHEMA_ELEMENT_ID SchemaElementId; - [FieldOffset(8)] - public VAULT_ELEMENT_TYPE Type; - } - - [DllImport("vaultcli.dll")] - public extern static Int32 VaultOpenVault(ref Guid vaultGuid, UInt32 offset, ref IntPtr vaultHandle); - - [DllImport("vaultcli.dll")] - public extern static Int32 VaultCloseVault(ref IntPtr vaultHandle); - - [DllImport("vaultcli.dll")] - public extern static Int32 VaultFree(ref IntPtr vaultHandle); - - [DllImport("vaultcli.dll")] - public extern static Int32 VaultEnumerateVaults(Int32 offset, ref Int32 vaultCount, ref IntPtr vaultGuid); - - [DllImport("vaultcli.dll")] - public extern static Int32 VaultEnumerateItems(IntPtr vaultHandle, Int32 chunkSize, ref Int32 vaultItemCount, ref IntPtr vaultItem); - - [DllImport("vaultcli.dll", EntryPoint = "VaultGetItem")] - public extern static Int32 VaultGetItem_WIN8(IntPtr vaultHandle, ref Guid schemaId, IntPtr pResourceElement, IntPtr pIdentityElement, IntPtr pPackageSid, IntPtr zero, Int32 arg6, ref IntPtr passwordVaultPtr); - - [DllImport("vaultcli.dll", EntryPoint = "VaultGetItem")] - public extern static Int32 VaultGetItem_WIN7(IntPtr vaultHandle, ref Guid schemaId, IntPtr pResourceElement, IntPtr pIdentityElement, IntPtr zero, Int32 arg5, ref IntPtr passwordVaultPtr); - - } - - - class Program - { - // PInvoke signature definitions - [DllImport("mpr.dll", CharSet = CharSet.Unicode, SetLastError = true)] - public static extern int WNetGetConnection( - [MarshalAs(UnmanagedType.LPTStr)] string localName, - [MarshalAs(UnmanagedType.LPTStr)] StringBuilder remoteName, - ref int length); - - [DllImport("advapi32", CharSet = CharSet.Auto, SetLastError = true)] - static extern bool ConvertSidToStringSid(IntPtr pSID, out IntPtr ptrSid); - - [DllImport("kernel32.dll")] - static extern IntPtr LocalFree(IntPtr hMem); - - [DllImport("advapi32.dll", SetLastError = true)] - static extern bool GetTokenInformation( - IntPtr TokenHandle, - TOKEN_INFORMATION_CLASS TokenInformationClass, - IntPtr TokenInformation, - int TokenInformationLength, - out int ReturnLength); - - [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Auto)] - [return: MarshalAs(UnmanagedType.Bool)] - protected static extern bool LookupPrivilegeName( - string lpSystemName, - IntPtr lpLuid, - System.Text.StringBuilder lpName, - ref int cchName); - - [DllImport("wtsapi32.dll", SetLastError = true)] - static extern IntPtr WTSOpenServer([MarshalAs(UnmanagedType.LPStr)] String pServerName); - - [DllImport("wtsapi32.dll")] - static extern void WTSCloseServer(IntPtr hServer); - - [DllImport("wtsapi32.dll", SetLastError = true)] - static extern Int32 WTSEnumerateSessions( - IntPtr hServer, - [MarshalAs(UnmanagedType.U4)] Int32 Reserved, - [MarshalAs(UnmanagedType.U4)] Int32 Version, - ref IntPtr ppSessionInfo, - [MarshalAs(UnmanagedType.U4)] ref Int32 pCount); - - [DllImport("wtsapi32.dll", SetLastError = true)] - static extern Int32 WTSEnumerateSessionsEx( - IntPtr hServer, - [MarshalAs(UnmanagedType.U4)] ref Int32 pLevel, - [MarshalAs(UnmanagedType.U4)] Int32 Filter, - ref IntPtr ppSessionInfo, - [MarshalAs(UnmanagedType.U4)] ref Int32 pCount); - - [DllImport("wtsapi32.dll")] - static extern void WTSFreeMemory(IntPtr pMemory); - - [DllImport("Wtsapi32.dll", SetLastError = true)] - static extern bool WTSQuerySessionInformation( - IntPtr hServer, - uint sessionId, - WTS_INFO_CLASS wtsInfoClass, - out IntPtr ppBuffer, - out uint pBytesReturned - ); - - [DllImport("iphlpapi.dll", SetLastError = true)] - public static extern uint GetExtendedTcpTable( - IntPtr pTcpTable, - ref uint dwOutBufLen, - bool sort, - int ipVersion, - TCP_TABLE_CLASS tblClass, - int reserved); - - [DllImport("advapi32.dll", SetLastError = true)] - public static extern uint I_QueryTagInformation( - IntPtr Unknown, - SC_SERVICE_TAG_QUERY_TYPE Type, - ref SC_SERVICE_TAG_QUERY Query - ); - - [DllImport("iphlpapi.dll", SetLastError = true)] - public static extern uint GetExtendedUdpTable( - IntPtr pUdpTable, - ref uint dwOutBufLen, - bool sort, - int ipVersion, - UDP_TABLE_CLASS tblClass, - int reserved); - - [DllImport("secur32.dll", SetLastError = false)] - private static extern int LsaConnectUntrusted([Out] out IntPtr LsaHandle); - - [DllImport("secur32.dll", SetLastError = true)] - public static extern int LsaRegisterLogonProcess(LSA_STRING_IN LogonProcessName, out IntPtr LsaHandle, out ulong SecurityMode); - - [DllImport("secur32.dll", SetLastError = false)] - private static extern int LsaDeregisterLogonProcess([In] IntPtr LsaHandle); - - [DllImport("secur32.dll", SetLastError = false)] - public static extern int LsaLookupAuthenticationPackage([In] IntPtr LsaHandle, [In] ref LSA_STRING_IN PackageName, [Out] out int AuthenticationPackage); - - [DllImport("secur32.dll", SetLastError = false)] - private static extern int LsaCallAuthenticationPackage(IntPtr LsaHandle, int AuthenticationPackage, ref KERB_QUERY_TKT_CACHE_REQUEST ProtocolSubmitBuffer, int SubmitBufferLength, out IntPtr ProtocolReturnBuffer, out int ReturnBufferLength, out int ProtocolStatus); - - [DllImport("secur32.dll", EntryPoint = "LsaCallAuthenticationPackage", SetLastError = false)] - private static extern int LsaCallAuthenticationPackage_KERB_RETRIEVE_TKT(IntPtr LsaHandle, int AuthenticationPackage, ref KERB_RETRIEVE_TKT_REQUEST ProtocolSubmitBuffer, int SubmitBufferLength, out IntPtr ProtocolReturnBuffer, out int ReturnBufferLength, out int ProtocolStatus); - - [DllImport("secur32.dll", EntryPoint = "LsaCallAuthenticationPackage", SetLastError = false)] - private static extern int LsaCallAuthenticationPackage_KERB_RETRIEVE_TKT_UNI(IntPtr LsaHandle, int AuthenticationPackage, ref KERB_RETRIEVE_TKT_REQUEST_UNI ProtocolSubmitBuffer, int SubmitBufferLength, out IntPtr ProtocolReturnBuffer, out int ReturnBufferLength, out int ProtocolStatus); - - [DllImport("secur32.dll", SetLastError = false)] - private static extern uint LsaFreeReturnBuffer(IntPtr buffer); - - [DllImport("Secur32.dll", SetLastError = false)] - private static extern uint LsaEnumerateLogonSessions(out UInt64 LogonSessionCount, out IntPtr LogonSessionList); - - [DllImport("Secur32.dll", SetLastError = false)] - private static extern uint LsaGetLogonSessionData(IntPtr luid, out IntPtr ppLogonSessionData); - - // for GetSystem() - [DllImport("advapi32.dll", SetLastError = true)] - [return: MarshalAs(UnmanagedType.Bool)] - static extern bool OpenProcessToken(IntPtr ProcessHandle, UInt32 DesiredAccess, out IntPtr TokenHandle); - - [DllImport("advapi32.dll")] - public extern static bool DuplicateToken(IntPtr ExistingTokenHandle, int SECURITY_IMPERSONATION_LEVEL, ref IntPtr DuplicateTokenHandle); - - [DllImport("advapi32.dll", SetLastError = true)] - static extern bool ImpersonateLoggedOnUser(IntPtr hToken); - - [DllImport("advapi32.dll", SetLastError = true)] - static extern bool RevertToSelf(); - - [DllImport("kernel32.dll", SetLastError = true)] - [return: MarshalAs(UnmanagedType.Bool)] - static extern bool CloseHandle(IntPtr hObject); - - [DllImport("kernel32.dll")] - static extern IntPtr LocalAlloc(uint uFlags, uint uBytes); - - [DllImport("kernel32.dll", EntryPoint = "CopyMemory", SetLastError = false)] - public static extern void CopyMemory(IntPtr dest, IntPtr src, uint count); - - [DllImport("IpHlpApi.dll")] - [return: MarshalAs(UnmanagedType.U4)] - internal static extern int GetIpNetTable(IntPtr pIpNetTable, [MarshalAs(UnmanagedType.U4)]ref int pdwSize, bool bOrder); - - [DllImport("IpHlpApi.dll", SetLastError = true, CharSet = CharSet.Auto)] - internal static extern int FreeMibTable(IntPtr plpNetTable); - - [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)] - static extern bool LookupAccountSid( - string lpSystemName, - [MarshalAs(UnmanagedType.LPArray)] byte[] Sid, - StringBuilder lpName, - ref uint cchName, - StringBuilder ReferencedDomainName, - ref uint cchReferencedDomainName, - out SID_NAME_USE peUse); - - // PInvoke structures/contants - public const uint SE_GROUP_LOGON_ID = 0xC0000000; // from winnt.h - public const int TokenGroups = 2; // from TOKEN_INFORMATION_CLASS - enum TOKEN_INFORMATION_CLASS - { - TokenUser = 1, - TokenGroups, - TokenPrivileges, - TokenOwner, - TokenPrimaryGroup, - TokenDefaultDacl, - TokenSource, - TokenType, - TokenImpersonationLevel, - TokenStatistics, - TokenRestrictedSids, - TokenSessionId, - TokenGroupsAndPrivileges, - TokenSessionReference, - TokenSandBoxInert, - TokenAuditPolicy, - TokenOrigin - } - - [StructLayout(LayoutKind.Sequential)] - public struct SID_AND_ATTRIBUTES - { - public IntPtr Sid; - public uint Attributes; - } - - [StructLayout(LayoutKind.Sequential)] - public struct TOKEN_GROUPS - { - public int GroupCount; - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 1)] - public SID_AND_ATTRIBUTES[] Groups; - }; - - protected struct TOKEN_PRIVILEGES - { - public UInt32 PrivilegeCount; - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 35)] - public LUID_AND_ATTRIBUTES[] Privileges; - } - - [StructLayout(LayoutKind.Sequential)] - protected struct LUID_AND_ATTRIBUTES - { - public LUID Luid; - public UInt32 Attributes; - } - - [StructLayout(LayoutKind.Sequential)] - protected struct LUID - { - public uint LowPart; - public int HighPart; - } - - [Flags] - public enum FirewallProfiles : int - { - DOMAIN = 1, - PRIVATE = 2, - PUBLIC = 4, - ALL = 2147483647 - } - - [Flags] - public enum LuidAttributes : uint - { - DISABLED = 0x00000000, - SE_PRIVILEGE_ENABLED_BY_DEFAULT = 0x00000001, - SE_PRIVILEGE_ENABLED = 0x00000002, - SE_PRIVILEGE_REMOVED = 0x00000004, - SE_PRIVILEGE_USED_FOR_ACCESS = 0x80000000 - } - - enum SID_NAME_USE - { - SidTypeUser = 1, - SidTypeGroup, - SidTypeDomain, - SidTypeAlias, - SidTypeWellKnownGroup, - SidTypeDeletedAccount, - SidTypeInvalid, - SidTypeUnknown, - SidTypeComputer - } - - [StructLayout(LayoutKind.Sequential)] - private struct WTS_SESSION_INFO - { - public Int32 SessionID; - - [MarshalAs(UnmanagedType.LPStr)] - public String pWinStationName; - - public WTS_CONNECTSTATE_CLASS State; - } - - [StructLayout(LayoutKind.Sequential)] - private struct WTS_SESSION_INFO_1 - { - public Int32 ExecEnvId; - - public WTS_CONNECTSTATE_CLASS State; - - public Int32 SessionID; - - [MarshalAs(UnmanagedType.LPStr)] - public String pSessionName; - - [MarshalAs(UnmanagedType.LPStr)] - public String pHostName; - - [MarshalAs(UnmanagedType.LPStr)] - public String pUserName; - - [MarshalAs(UnmanagedType.LPStr)] - public String pDomainName; - - [MarshalAs(UnmanagedType.LPStr)] - public String pFarmName; - } - - [StructLayout(LayoutKind.Sequential)] - public struct WTS_CLIENT_ADDRESS - { - public uint AddressFamily; - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 20)] - public byte[] Address; - } - - public enum WTS_CONNECTSTATE_CLASS - { - Active, - Connected, - ConnectQuery, - Shadow, - Disconnected, - Idle, - Listen, - Reset, - Down, - Init - } - - public enum WTS_INFO_CLASS - { - WTSInitialProgram = 0, - WTSApplicationName = 1, - WTSWorkingDirectory = 2, - WTSOEMId = 3, - WTSSessionId = 4, - WTSUserName = 5, - WTSWinStationName = 6, - WTSDomainName = 7, - WTSConnectState = 8, - WTSClientBuildNumber = 9, - WTSClientName = 10, - WTSClientDirectory = 11, - WTSClientProductId = 12, - WTSClientHardwareId = 13, - WTSClientAddress = 14, - WTSClientDisplay = 15, - WTSClientProtocolType = 16, - WTSIdleTime = 17, - WTSLogonTime = 18, - WTSIncomingBytes = 19, - WTSOutgoingBytes = 20, - WTSIncomingFrames = 21, - WTSOutgoingFrames = 22, - WTSClientInfo = 23, - WTSSessionInfo = 24, - WTSSessionInfoEx = 25, - WTSConfigInfo = 26, - WTSValidationInfo = 27, - WTSSessionAddressV4 = 28, - WTSIsRemoteSession = 29 - } - - public enum TCP_TABLE_CLASS : int - { - TCP_TABLE_BASIC_LISTENER, - TCP_TABLE_BASIC_CONNECTIONS, - TCP_TABLE_BASIC_ALL, - TCP_TABLE_OWNER_PID_LISTENER, - TCP_TABLE_OWNER_PID_CONNECTIONS, - TCP_TABLE_OWNER_PID_ALL, - TCP_TABLE_OWNER_MODULE_LISTENER, - TCP_TABLE_OWNER_MODULE_CONNECTIONS, - TCP_TABLE_OWNER_MODULE_ALL - } - - public enum UDP_TABLE_CLASS : int - { - UDP_TABLE_BASIC, - UDP_TABLE_OWNER_PID, - UDP_TABLE_OWNER_MODULE - } - - [StructLayout(LayoutKind.Sequential)] - public struct SC_SERVICE_TAG_QUERY - { - public uint ProcessId; - public uint ServiceTag; - public uint Unknown; - public IntPtr Buffer; - } - - public enum SC_SERVICE_TAG_QUERY_TYPE - { - ServiceNameFromTagInformation = 1, - ServiceNamesReferencingModuleInformation = 2, - ServiceNameTagMappingInformation = 3 - } - - [StructLayout(LayoutKind.Sequential)] - public struct MIB_TCPTABLE_OWNER_MODULE - { - public uint NumEntries; - MIB_TCPROW_OWNER_MODULE Table; - } - - [StructLayout(LayoutKind.Sequential)] - public struct MIB_TCPROW_OWNER_MODULE - { - public readonly MIB_TCP_STATE State; - public readonly uint LocalAddr; - private readonly byte LocalPort1; - private readonly byte LocalPort2; - private readonly byte LocalPort3; - private readonly byte LocalPort4; - public readonly uint RemoteAddr; - private readonly byte RemotePort1; - private readonly byte RemotePort2; - private readonly byte RemotePort3; - private readonly byte RemotePort4; - public readonly uint OwningPid; - public readonly UInt64 CreateTimestamp; - public readonly UInt64 OwningModuleInfo0; - public readonly UInt64 OwningModuleInfo1; - public readonly UInt64 OwningModuleInfo2; - public readonly UInt64 OwningModuleInfo3; - public readonly UInt64 OwningModuleInfo4; - public readonly UInt64 OwningModuleInfo5; - public readonly UInt64 OwningModuleInfo6; - public readonly UInt64 OwningModuleInfo7; - public readonly UInt64 OwningModuleInfo8; - public readonly UInt64 OwningModuleInfo9; - public readonly UInt64 OwningModuleInfo10; - public readonly UInt64 OwningModuleInfo11; - public readonly UInt64 OwningModuleInfo12; - public readonly UInt64 OwningModuleInfo13; - public readonly UInt64 OwningModuleInfo14; - public readonly UInt64 OwningModuleInfo15; - - - public ushort LocalPort - { - get - { - return BitConverter.ToUInt16( - new byte[2] { LocalPort2, LocalPort1 }, 0); - } - } - - public IPAddress LocalAddress - { - get { return new IPAddress(LocalAddr); } - } - - public IPAddress RemoteAddress - { - get { return new IPAddress(RemoteAddr); } - } - - public ushort RemotePort - { - get - { - return BitConverter.ToUInt16( - new byte[2] { RemotePort2, RemotePort1 }, 0); - } - } - } - - [StructLayout(LayoutKind.Sequential)] - public struct MIB_UDPTABLE_OWNER_MODULE - { - public uint NumEntries; - MIB_UDPROW_OWNER_MODULE Table; - } - - [StructLayout(LayoutKind.Sequential)] - public struct MIB_UDPROW_OWNER_MODULE - { - public readonly uint LocalAddr; - private readonly byte LocalPort1; - private readonly byte LocalPort2; - private readonly byte LocalPort3; - private readonly byte LocalPort4; - public readonly uint OwningPid; - public readonly UInt64 CreateTimestamp; - public readonly UInt32 SpecificPortBind_Flags; - // public readonly UInt32 Flags; - public readonly UInt64 OwningModuleInfo0; - public readonly UInt64 OwningModuleInfo1; - public readonly UInt64 OwningModuleInfo2; - public readonly UInt64 OwningModuleInfo3; - public readonly UInt64 OwningModuleInfo4; - public readonly UInt64 OwningModuleInfo5; - public readonly UInt64 OwningModuleInfo6; - public readonly UInt64 OwningModuleInfo7; - public readonly UInt64 OwningModuleInfo8; - public readonly UInt64 OwningModuleInfo9; - public readonly UInt64 OwningModuleInfo10; - public readonly UInt64 OwningModuleInfo11; - public readonly UInt64 OwningModuleInfo12; - public readonly UInt64 OwningModuleInfo13; - public readonly UInt64 OwningModuleInfo14; - public readonly UInt64 OwningModuleInfo15; - - public ushort LocalPort - { - get - { - return BitConverter.ToUInt16( - new byte[2] { LocalPort2, LocalPort1 }, 0); - } - } - - public IPAddress LocalAddress - { - get { return new IPAddress(LocalAddr); } - } - } - - [StructLayout(LayoutKind.Sequential)] - public struct MIB_TCPROW_OWNER_PID - { - public uint state; - public uint localAddr; - public byte localPort1; - public byte localPort2; - public byte localPort3; - public byte localPort4; - public uint remoteAddr; - public byte remotePort1; - public byte remotePort2; - public byte remotePort3; - public byte remotePort4; - public int owningPid; - - public ushort LocalPort - { - get - { - return BitConverter.ToUInt16( - new byte[2] { localPort2, localPort1 }, 0); - } - } - - public IPAddress LocalAddress - { - get { return new IPAddress(localAddr); } - } - - public IPAddress RemoteAddress - { - get { return new IPAddress(remoteAddr); } - } - - public ushort RemotePort - { - get - { - return BitConverter.ToUInt16( - new byte[2] { remotePort2, remotePort1 }, 0); - } - } - - public MIB_TCP_STATE State - { - get { return (MIB_TCP_STATE)state; } - } - } - - [StructLayout(LayoutKind.Sequential)] - public struct MIB_UDPROW_OWNER_PID - { - public uint localAddr; - //[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] - public byte localPort1; - public byte localPort2; - public byte localPort3; - public byte localPort4; - public int owningPid; - - public ushort LocalPort - { - get - { - return BitConverter.ToUInt16( - new byte[2] { localPort2, localPort1 }, 0); - } - } - - public IPAddress LocalAddress - { - get { return new IPAddress(localAddr); } - } - } - - [StructLayout(LayoutKind.Sequential)] - public struct MIB_TCPTABLE_OWNER_PID - { - public uint dwNumEntries; - MIB_TCPROW_OWNER_PID table; - } - - [StructLayout(LayoutKind.Sequential)] - public struct MIB_UDPTABLE_OWNER_PID - { - public uint dwNumEntries; - MIB_TCPROW_OWNER_PID table; - } - - public enum MIB_TCP_STATE - { - CLOSED = 1, - LISTEN = 2, - SYN_SENT = 3, - SYN_RCVD = 4, - ESTAB = 5, - FIN_WAIT1 = 6, - FIN_WAIT2 = 7, - CLOSE_WAIT = 8, - CLOSING = 9, - LAST_ACK = 10, - TIME_WAIT = 11, - DELETE_TCB = 12 - } - - [StructLayout(LayoutKind.Sequential)] - public struct LSA_STRING_IN - { - public UInt16 Length; - public UInt16 MaximumLength; - public string Buffer; - } - - [StructLayout(LayoutKind.Sequential)] - public struct LSA_STRING_OUT - { - public UInt16 Length; - public UInt16 MaximumLength; - public IntPtr Buffer; - } - - [StructLayout(LayoutKind.Sequential)] - public struct UNICODE_STRING : IDisposable - { - public ushort Length; - public ushort MaximumLength; - public IntPtr buffer; - - public UNICODE_STRING(string s) - { - Length = (ushort)(s.Length * 2); - MaximumLength = (ushort)(Length + 2); - buffer = Marshal.StringToHGlobalUni(s); - } - - public void Dispose() - { - Marshal.FreeHGlobal(buffer); - buffer = IntPtr.Zero; - } - - public override string ToString() - { - return Marshal.PtrToStringUni(buffer); - } - } - - public enum KERB_PROTOCOL_MESSAGE_TYPE : UInt32 - { - KerbDebugRequestMessage = 0, - KerbQueryTicketCacheMessage = 1, - KerbChangeMachinePasswordMessage = 2, - KerbVerifyPacMessage = 3, - KerbRetrieveTicketMessage = 4, - KerbUpdateAddressesMessage = 5, - KerbPurgeTicketCacheMessage = 6, - KerbChangePasswordMessage = 7, - KerbRetrieveEncodedTicketMessage = 8, - KerbDecryptDataMessage = 9, - KerbAddBindingCacheEntryMessage = 10, - KerbSetPasswordMessage = 11, - KerbSetPasswordExMessage = 12, - KerbVerifyCredentialsMessage = 13, - KerbQueryTicketCacheExMessage = 14, - KerbPurgeTicketCacheExMessage = 15, - KerbRefreshSmartcardCredentialsMessage = 16, - KerbAddExtraCredentialsMessage = 17, - KerbQuerySupplementalCredentialsMessage = 18, - KerbTransferCredentialsMessage = 19, - KerbQueryTicketCacheEx2Message = 20, - KerbSubmitTicketMessage = 21, - KerbAddExtraCredentialsExMessage = 22, - KerbQueryKdcProxyCacheMessage = 23, - KerbPurgeKdcProxyCacheMessage = 24, - KerbQueryTicketCacheEx3Message = 25, - KerbCleanupMachinePkinitCredsMessage = 26, - KerbAddBindingCacheEntryExMessage = 27, - KerbQueryBindingCacheMessage = 28, - KerbPurgeBindingCacheMessage = 29, - KerbQueryDomainExtendedPoliciesMessage = 30, - KerbQueryS4U2ProxyCacheMessage = 31 - } - - public enum KERB_ENCRYPTION_TYPE : UInt32 - { - reserved0 = 0, - des_cbc_crc = 1, - des_cbc_md4 = 2, - des_cbc_md5 = 3, - reserved1 = 4, - des3_cbc_md5 = 5, - reserved2 = 6, - des3_cbc_sha1 = 7, - dsaWithSHA1_CmsOID = 9, - md5WithRSAEncryption_CmsOID = 10, - sha1WithRSAEncryption_CmsOID = 11, - rc2CBC_EnvOID = 12, - rsaEncryption_EnvOID = 13, - rsaES_OAEP_ENV_OID = 14, - des_ede3_cbc_Env_OID = 15, - des3_cbc_sha1_kd = 16, - aes128_cts_hmac_sha1_96 = 17, - aes256_cts_hmac_sha1_96 = 18, - aes128_cts_hmac_sha256_128 = 19, - aes256_cts_hmac_sha384_192 = 20, - rc4_hmac = 23, - rc4_hmac_exp = 24, - camellia128_cts_cmac = 25, - camellia256_cts_cmac = 26, - subkey_keymaterial = 65 - } - - [Flags] - private enum KERB_CACHE_OPTIONS : UInt64 - { - KERB_RETRIEVE_TICKET_DEFAULT = 0x0, - KERB_RETRIEVE_TICKET_DONT_USE_CACHE = 0x1, - KERB_RETRIEVE_TICKET_USE_CACHE_ONLY = 0x2, - KERB_RETRIEVE_TICKET_USE_CREDHANDLE = 0x4, - KERB_RETRIEVE_TICKET_AS_KERB_CRED = 0x8, - KERB_RETRIEVE_TICKET_WITH_SEC_CRED = 0x10, - KERB_RETRIEVE_TICKET_CACHE_TICKET = 0x20, - KERB_RETRIEVE_TICKET_MAX_LIFETIME = 0x40, - } - - // TODO: double check these flags... - // https://docs.microsoft.com/en-us/windows/desktop/api/ntsecapi/ns-ntsecapi-_kerb_external_ticket - [Flags] - public enum KERB_TICKET_FLAGS : UInt32 - { - reserved = 2147483648, - forwardable = 0x40000000, - forwarded = 0x20000000, - proxiable = 0x10000000, - proxy = 0x08000000, - may_postdate = 0x04000000, - postdated = 0x02000000, - invalid = 0x01000000, - renewable = 0x00800000, - initial = 0x00400000, - pre_authent = 0x00200000, - hw_authent = 0x00100000, - ok_as_delegate = 0x00040000, - name_canonicalize = 0x00010000, - //cname_in_pa_data = 0x00040000, - enc_pa_rep = 0x00010000, - reserved1 = 0x00000001 - } - - [StructLayout(LayoutKind.Sequential)] - public struct SECURITY_HANDLE - { - public IntPtr LowPart; - public IntPtr HighPart; - public SECURITY_HANDLE(int dummy) - { - LowPart = HighPart = IntPtr.Zero; - } - }; - - [StructLayout(LayoutKind.Sequential)] - public struct KERB_TICKET_CACHE_INFO - { - public LSA_STRING_OUT ServerName; - public LSA_STRING_OUT RealmName; - public Int64 StartTime; - public Int64 EndTime; - public Int64 RenewTime; - public Int32 EncryptionType; - public UInt32 TicketFlags; - } - - [StructLayout(LayoutKind.Sequential)] - public struct KERB_TICKET_CACHE_INFO_EX - { - public LSA_STRING_OUT ClientName; - public LSA_STRING_OUT ClientRealm; - public LSA_STRING_OUT ServerName; - public LSA_STRING_OUT ServerRealm; - public Int64 StartTime; - public Int64 EndTime; - public Int64 RenewTime; - public Int32 EncryptionType; - public UInt32 TicketFlags; - } - - [StructLayout(LayoutKind.Sequential)] - private struct KERB_QUERY_TKT_CACHE_RESPONSE - { - public KERB_PROTOCOL_MESSAGE_TYPE MessageType; - public int CountOfTickets; - // public KERB_TICKET_CACHE_INFO[] Tickets; - public IntPtr Tickets; - } - - [StructLayout(LayoutKind.Sequential)] - private struct KERB_QUERY_TKT_CACHE_EX_RESPONSE - { - public KERB_PROTOCOL_MESSAGE_TYPE MessageType; - public int CountOfTickets; - // public KERB_TICKET_CACHE_INFO[] Tickets; - public IntPtr Tickets; - } - - [StructLayout(LayoutKind.Sequential)] - private struct KERB_QUERY_TKT_CACHE_REQUEST - { - public KERB_PROTOCOL_MESSAGE_TYPE MessageType; - public LUID LogonId; - } - - [StructLayout(LayoutKind.Sequential)] - private struct KERB_RETRIEVE_TKT_REQUEST - { - public KERB_PROTOCOL_MESSAGE_TYPE MessageType; - public LUID LogonId; - public LSA_STRING_IN TargetName; - public UInt64 TicketFlags; - public KERB_CACHE_OPTIONS CacheOptions; - public Int64 EncryptionType; - public SECURITY_HANDLE CredentialsHandle; - } - - [StructLayout(LayoutKind.Sequential)] - private struct KERB_RETRIEVE_TKT_REQUEST_UNI - { - public KERB_PROTOCOL_MESSAGE_TYPE MessageType; - public LUID LogonId; - public UNICODE_STRING TargetName; - public UInt64 TicketFlags; - public KERB_CACHE_OPTIONS CacheOptions; - public Int64 EncryptionType; - public SECURITY_HANDLE CredentialsHandle; - } - - [StructLayout(LayoutKind.Sequential)] - private struct KERB_CRYPTO_KEY - { - public Int32 KeyType; - public Int32 Length; - public IntPtr Value; - } - - [StructLayout(LayoutKind.Sequential)] - private struct KERB_EXTERNAL_NAME - { - public Int16 NameType; - public UInt16 NameCount; - public LSA_STRING_OUT Names; - } - - [StructLayout(LayoutKind.Sequential)] - private struct KERB_EXTERNAL_TICKET - { - public IntPtr ServiceName; - public IntPtr TargetName; - public IntPtr ClientName; - public LSA_STRING_OUT DomainName; - public LSA_STRING_OUT TargetDomainName; - public LSA_STRING_OUT AltTargetDomainName; - public KERB_CRYPTO_KEY SessionKey; - public UInt32 TicketFlags; - public UInt32 Flags; - public Int64 KeyExpirationTime; - public Int64 StartTime; - public Int64 EndTime; - public Int64 RenewUntil; - public Int64 TimeSkew; - public Int32 EncodedTicketSize; - public IntPtr EncodedTicket; - } - - [StructLayout(LayoutKind.Sequential)] - private struct KERB_RETRIEVE_TKT_RESPONSE - { - public KERB_EXTERNAL_TICKET Ticket; - } - - private enum SECURITY_LOGON_TYPE : uint - { - Interactive = 2, // logging on interactively. - Network, // logging using a network. - Batch, // logon for a batch process. - Service, // logon for a service account. - Proxy, // Not supported. - Unlock, // Tattempt to unlock a workstation. - NetworkCleartext, // network logon with cleartext credentials - NewCredentials, // caller can clone its current token and specify new credentials for outbound connections - RemoteInteractive, // terminal server session that is both remote and interactive - CachedInteractive, // attempt to use the cached credentials without going out across the network - CachedRemoteInteractive,// same as RemoteInteractive, except used internally for auditing purposes - CachedUnlock // attempt to unlock a workstation - } - - [StructLayout(LayoutKind.Sequential)] - private struct SECURITY_LOGON_SESSION_DATA - { - public UInt32 Size; - public LUID LoginID; - public LSA_STRING_OUT Username; - public LSA_STRING_OUT LoginDomain; - public LSA_STRING_OUT AuthenticationPackage; - public UInt32 LogonType; - public UInt32 Session; - public IntPtr PSiD; - public UInt64 LoginTime; - public LSA_STRING_OUT LogonServer; - public LSA_STRING_OUT DnsDomainName; - public LSA_STRING_OUT Upn; - } - - public const int MAXLEN_PHYSADDR = 8; - public const int ERROR_SUCCESS = 0; - public const int ERROR_INSUFFICIENT_BUFFER = 122; - - [StructLayout(LayoutKind.Sequential)] - internal struct MIB_IPNETROW - { - [MarshalAs(UnmanagedType.U4)] - public int dwIndex; - [MarshalAs(UnmanagedType.U4)] - public int dwPhysAddrLen; - [MarshalAs(UnmanagedType.U1)] - public byte mac0; - [MarshalAs(UnmanagedType.U1)] - public byte mac1; - [MarshalAs(UnmanagedType.U1)] - public byte mac2; - [MarshalAs(UnmanagedType.U1)] - public byte mac3; - [MarshalAs(UnmanagedType.U1)] - public byte mac4; - [MarshalAs(UnmanagedType.U1)] - public byte mac5; - [MarshalAs(UnmanagedType.U1)] - public byte mac6; - [MarshalAs(UnmanagedType.U1)] - public byte mac7; - [MarshalAs(UnmanagedType.U4)] - public int dwAddr; - [MarshalAs(UnmanagedType.U4)] - public int dwType; - } - - public enum ArpEntryType - { - Other = 1, - Invalid = 2, - Dynamic = 3, - Static = 4, - } - - - // helpers (registry, UNC paths, etc.) - - public static IntPtr OpenServer(String Name) - { - IntPtr server = WTSOpenServer(Name); - return server; - } - public static void CloseServer(IntPtr ServerHandle) - { - WTSCloseServer(ServerHandle); - } - - public static string TranslateSid(string Sid) - { - // adapted from http://www.pinvoke.net/default.aspx/advapi32.LookupAccountSid - SecurityIdentifier accountSid = new SecurityIdentifier(Sid); - byte[] accountSidByes = new byte[accountSid.BinaryLength]; - accountSid.GetBinaryForm(accountSidByes, 0); - - StringBuilder name = new StringBuilder(); - uint cchName = (uint)name.Capacity; - StringBuilder referencedDomainName = new StringBuilder(); - uint cchReferencedDomainName = (uint)referencedDomainName.Capacity; - SID_NAME_USE sidUse; - - int err = 0; - if (!LookupAccountSid(null, accountSidByes, name, ref cchName, referencedDomainName, ref cchReferencedDomainName, out sidUse)) - { - err = System.Runtime.InteropServices.Marshal.GetLastWin32Error(); - if (err == ERROR_INSUFFICIENT_BUFFER) - { - name.EnsureCapacity((int)cchName); - referencedDomainName.EnsureCapacity((int)cchReferencedDomainName); - err = 0; - if (!LookupAccountSid(null, accountSidByes, name, ref cchName, referencedDomainName, ref cchReferencedDomainName, out sidUse)) - err = System.Runtime.InteropServices.Marshal.GetLastWin32Error(); - } - } - if (err == 0) - return String.Format("{0}\\{1}", referencedDomainName.ToString(), name.ToString()); - else - return ""; - } - - public static void PrintLogo() - { - Console.WriteLine("\r\n\r\n %&&@@@&& "); - Console.WriteLine(" &&&&&&&%%%, #&&@@@@@@%%%%%%###############% "); - Console.WriteLine(" &%& %&%% &////(((&%%%%%#%################//((((###%%%%%%%%%%%%%%%"); - Console.WriteLine("%%%%%%%%%%%######%%%#%%####% &%%**# @////(((&%%%%%%######################((((((((((((((((((("); - Console.WriteLine("#%#%%%%%%%#######%#%%####### %&%,,,,,,,,,,,,,,,, @////(((&%%%%%#%#####################((((((((((((((((((("); - Console.WriteLine("#%#%%%%%%#####%%#%#%%####### %%%,,,,,, ,,. ,, @////(((&%%%%%%%######################(#(((#(#(((((((((("); - Console.WriteLine("#####%%%#################### &%%...... ... .. @////(((&%%%%%%%###############%######((#(#(####(((((((("); - Console.WriteLine("#######%##########%######### %%%...... ... .. @////(((&%%%%%#########################(#(#######((#####"); - Console.WriteLine("###%##%%#################### &%%............... @////(((&%%%%%%%%##############%#######(#########((#####"); - Console.WriteLine("#####%###################### %%%.. @////(((&%%%%%%%################ "); - Console.WriteLine(" &%& %%%%% Seatbelt %////(((&%%%%%%%%#############* "); - Console.WriteLine(" &%%&&&%%%%% v0.2.0 ,(((&%%%%%%%%%%%%%%%%%, "); - Console.WriteLine(" #%%%%##, \r\n\r\n"); - } - - public static string GetRegValue(string hive, string path, string value) - { - // returns a single registry value under the specified path in the specified hive (HKLM/HKCU) - string regKeyValue = ""; - if (hive == "HKCU") - { - var regKey = Registry.CurrentUser.OpenSubKey(path); - if (regKey != null) - { - regKeyValue = String.Format("{0}", regKey.GetValue(value)); - } - return regKeyValue; - } - else if (hive == "HKU") - { - var regKey = Registry.Users.OpenSubKey(path); - if (regKey != null) - { - regKeyValue = String.Format("{0}", regKey.GetValue(value)); - } - return regKeyValue; - } - else - { - var regKey = Registry.LocalMachine.OpenSubKey(path); - if (regKey != null) - { - regKeyValue = String.Format("{0}", regKey.GetValue(value)); - } - return regKeyValue; - } - } - - public static byte[] GetRegValueBytes(string hive, string path, string value) - { - // returns a byte array of single registry value under the specified path in the specified hive (HKLM/HKCU) - byte[] regKeyValue = null; - if (hive == "HKCU") - { - var regKey = Registry.CurrentUser.OpenSubKey(path); - if (regKey != null) - { - regKeyValue = (byte[])regKey.GetValue(value); - } - return regKeyValue; - } - else if (hive == "HKU") - { - var regKey = Registry.Users.OpenSubKey(path); - if (regKey != null) - { - regKeyValue = (byte[])regKey.GetValue(value); - } - return regKeyValue; - } - else - { - var regKey = Registry.LocalMachine.OpenSubKey(path); - if (regKey != null) - { - regKeyValue = (byte[])regKey.GetValue(value); - } - return regKeyValue; - } - } - - public static Dictionary GetRegValues(string hive, string path) - { - // returns all registry values under the specified path in the specified hive (HKLM/HKCU) - Dictionary keyValuePairs = null; - try - { - if (hive == "HKCU") - { - using (var regKeyValues = Registry.CurrentUser.OpenSubKey(path)) - { - if (regKeyValues != null) - { - var valueNames = regKeyValues.GetValueNames(); - keyValuePairs = valueNames.ToDictionary(name => name, regKeyValues.GetValue); - } - } - } - else if (hive == "HKU") - { - using (var regKeyValues = Registry.Users.OpenSubKey(path)) - { - if (regKeyValues != null) - { - var valueNames = regKeyValues.GetValueNames(); - keyValuePairs = valueNames.ToDictionary(name => name, regKeyValues.GetValue); - } - } - } - else - { - using (var regKeyValues = Registry.LocalMachine.OpenSubKey(path)) - { - if (regKeyValues != null) - { - var valueNames = regKeyValues.GetValueNames(); - keyValuePairs = valueNames.ToDictionary(name => name, regKeyValues.GetValue); - } - } - } - return keyValuePairs; - } - catch - { - return null; - } - } - - public static string[] GetRegSubkeys(string hive, string path) - { - // returns an array of the subkeys names under the specified path in the specified hive (HKLM/HKCU/HKU) - try - { - Microsoft.Win32.RegistryKey myKey = null; - if (hive == "HKLM") - { - myKey = Registry.LocalMachine.OpenSubKey(path); - } - else if (hive == "HKU") - { - myKey = Registry.Users.OpenSubKey(path); - } - else - { - myKey = Registry.CurrentUser.OpenSubKey(path); - } - String[] subkeyNames = myKey.GetSubKeyNames(); - return myKey.GetSubKeyNames(); - } - catch - { - return new string[0]; - } - } - - public static string GetUNCPath(string originalPath) - { - // uses WNetGetConnection to map a drive letter to a possible UNC mount path - // Pulled from @ambyte's gist at https://gist.github.com/ambyte/01664dc7ee576f69042c - - StringBuilder sb = new StringBuilder(512); - int size = sb.Capacity; - - // look for the {LETTER}: combination ... - if (originalPath.Length > 2 && originalPath[1] == ':') - { - // don't use char.IsLetter here - as that can be misleading - // the only valid drive letters are a-z && A-Z. - char c = originalPath[0]; - if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) - { - int error = WNetGetConnection(originalPath.Substring(0, 2), - sb, ref size); - if (error == 0) - { - DirectoryInfo dir = new DirectoryInfo(originalPath); - - string path = Path.GetFullPath(originalPath) - .Substring(Path.GetPathRoot(originalPath).Length); - return Path.Combine(sb.ToString().TrimEnd(), path); - } - } - } - - return originalPath; - } - - public static bool IsHighIntegrity() - { - // returns true if the current process is running with adminstrative privs in a high integrity context - WindowsIdentity identity = WindowsIdentity.GetCurrent(); - WindowsPrincipal principal = new WindowsPrincipal(identity); - return principal.IsInRole(WindowsBuiltInRole.Administrator); - } - - public static string[] GetLocalGroupMembers(string groupName) - { - // returns the "DOMAIN\user" members for a specified local group name - // adapted from boboes' code at https://stackoverflow.com/questions/33935825/pinvoke-netlocalgroupgetmembers-runs-into-fatalexecutionengineerror/33939889#33939889 - - string computerName = null; // null for the local machine - - int EntriesRead; - int TotalEntries; - IntPtr Resume; - IntPtr bufPtr; - - uint retVal = NetworkAPI.NetLocalGroupGetMembers(computerName, groupName, 2, out bufPtr, -1, out EntriesRead, out TotalEntries, out Resume); - - if (retVal != 0) - { - if (retVal == NetworkAPI.ERROR_ACCESS_DENIED) { Console.WriteLine("Access denied"); return null; } - if (retVal == NetworkAPI.ERROR_MORE_DATA) { Console.WriteLine("ERROR_MORE_DATA"); return null; } - if (retVal == NetworkAPI.ERROR_NO_SUCH_ALIAS) { Console.WriteLine("Group not found"); return null; } - if (retVal == NetworkAPI.NERR_InvalidComputer) { Console.WriteLine("Invalid computer name"); return null; } - if (retVal == NetworkAPI.NERR_GroupNotFound) { Console.WriteLine("Group not found"); return null; } - if (retVal == NetworkAPI.SERVER_UNAVAILABLE) { Console.WriteLine("Server unavailable"); return null; } - Console.WriteLine("Unexpected NET_API_STATUS: " + retVal.ToString()); - return null; - } - - if (EntriesRead > 0) - { - string[] names = new string[EntriesRead]; - NetworkAPI.LOCALGROUP_MEMBERS_INFO_2[] Members = new NetworkAPI.LOCALGROUP_MEMBERS_INFO_2[EntriesRead]; - IntPtr iter = bufPtr; - - for (int i = 0; i < EntriesRead; i++) - { - Members[i] = (NetworkAPI.LOCALGROUP_MEMBERS_INFO_2)Marshal.PtrToStructure(iter, typeof(NetworkAPI.LOCALGROUP_MEMBERS_INFO_2)); - - //x64 safe - iter = new IntPtr(iter.ToInt64() + Marshal.SizeOf(typeof(NetworkAPI.LOCALGROUP_MEMBERS_INFO_2))); - - names[i] = Members[i].lgrmi2_domainandname; - } - NetworkAPI.NetApiBufferFree(bufPtr); - - return names; - } - else - { - return null; - } - } - - public static string[] GetTokenGroupSIDs() - { - // Returns all SIDs that the current user is a part of, whether they are disabled or not. - // slightly adapted from https://stackoverflow.com/questions/2146153/how-to-get-the-logon-sid-in-c-sharp/2146418#2146418 - - int TokenInfLength = 0; - - // first call gets length of TokenInformation - bool Result = GetTokenInformation(WindowsIdentity.GetCurrent().Token, TOKEN_INFORMATION_CLASS.TokenGroups, IntPtr.Zero, TokenInfLength, out TokenInfLength); - IntPtr TokenInformation = Marshal.AllocHGlobal(TokenInfLength); - Result = GetTokenInformation(WindowsIdentity.GetCurrent().Token, TOKEN_INFORMATION_CLASS.TokenGroups, TokenInformation, TokenInfLength, out TokenInfLength); - - if (!Result) - { - Marshal.FreeHGlobal(TokenInformation); - return null; - } - - TOKEN_GROUPS groups = (TOKEN_GROUPS)Marshal.PtrToStructure(TokenInformation, typeof(TOKEN_GROUPS)); - string[] userSIDS = new string[groups.GroupCount]; - int sidAndAttrSize = Marshal.SizeOf(new SID_AND_ATTRIBUTES()); - for (int i = 0; i < groups.GroupCount; i++) - { - SID_AND_ATTRIBUTES sidAndAttributes = (SID_AND_ATTRIBUTES)Marshal.PtrToStructure( - new IntPtr(TokenInformation.ToInt64() + i * sidAndAttrSize + IntPtr.Size), typeof(SID_AND_ATTRIBUTES)); - - IntPtr pstr = IntPtr.Zero; - ConvertSidToStringSid(sidAndAttributes.Sid, out pstr); - userSIDS[i] = Marshal.PtrToStringAuto(pstr); - LocalFree(pstr); - } - - Marshal.FreeHGlobal(TokenInformation); - return userSIDS; - } - - public static bool GetSystem() - { - // helper to elevate to SYSTEM for Kerberos ticket enumeration via token impersonation - - if (IsHighIntegrity()) - { - IntPtr hToken = IntPtr.Zero; - - // Open winlogon's token with TOKEN_DUPLICATE accesss so ca can make a copy of the token with DuplicateToken - Process[] processes = Process.GetProcessesByName("winlogon"); - IntPtr handle = processes[0].Handle; - - // TOKEN_DUPLICATE = 0x0002 - bool success = OpenProcessToken(handle, 0x0002, out hToken); - if (!success) - { - //Console.WriteLine("OpenProcessToken failed!"); - return false; - } - - // make a copy of the NT AUTHORITY\SYSTEM token from winlogon - // 2 == SecurityImpersonation - IntPtr hDupToken = IntPtr.Zero; - success = DuplicateToken(hToken, 2, ref hDupToken); - if (!success) - { - //Console.WriteLine("DuplicateToken failed!"); - return false; - } - - success = ImpersonateLoggedOnUser(hDupToken); - if (!success) - { - //Console.WriteLine("ImpersonateLoggedOnUser failed!"); - return false; - } - - // clean up the handles we created - CloseHandle(hToken); - CloseHandle(hDupToken); - - string name = System.Security.Principal.WindowsIdentity.GetCurrent().Name; - if (name != "NT AUTHORITY\\SYSTEM") - { - return false; - } - - return true; - } - else - { - return false; - } - } - - public static IntPtr LsaRegisterLogonProcessHelper() - { - // helper that establishes a connection to the LSA server and verifies that the caller is a logon application - // used for Kerberos ticket enumeration - - string logonProcessName = "User32LogonProcesss"; - LSA_STRING_IN LSAString; - IntPtr lsaHandle = IntPtr.Zero; - UInt64 securityMode = 0; - - LSAString.Length = (ushort)logonProcessName.Length; - LSAString.MaximumLength = (ushort)(logonProcessName.Length + 1); - LSAString.Buffer = logonProcessName; - - int ret = LsaRegisterLogonProcess(LSAString, out lsaHandle, out securityMode); - - return lsaHandle; - } - - public static bool IsLocalAdmin() - { - // checks if the "S-1-5-32-544" in the current token groups set, meaning the user is a local administrator - string[] SIDs = GetTokenGroupSIDs(); - - foreach (string SID in SIDs) - { - if (SID == "S-1-5-32-544") - { - return true; - } - } - return false; - } - - public static bool IsVirtualMachine() - { - // returns true if the system is likely a virtual machine - // Adapted from RobSiklos' code from https://stackoverflow.com/questions/498371/how-to-detect-if-my-application-is-running-in-a-virtual-machine/11145280#11145280 - - using (var searcher = new System.Management.ManagementObjectSearcher("Select * from Win32_ComputerSystem")) - { - using (var items = searcher.Get()) - { - foreach (var item in items) - { - string manufacturer = item["Manufacturer"].ToString().ToLower(); - if ((manufacturer == "microsoft corporation" && item["Model"].ToString().ToUpperInvariant().Contains("VIRTUAL")) - || manufacturer.Contains("vmware") - || item["Model"].ToString() == "VirtualBox") - { - return true; - } - } - } - } - return false; - } - - public static bool CheckAccess(string Path, FileSystemRights AccessRight) - { - // checks if the current user has the specified AccessRight to the specified file or folder - // adapted from https://stackoverflow.com/questions/1410127/c-sharp-test-if-user-has-write-access-to-a-folder/21996345#21996345 - - if (string.IsNullOrEmpty(Path)) return false; - - try - { - AuthorizationRuleCollection rules = Directory.GetAccessControl(Path).GetAccessRules(true, true, typeof(System.Security.Principal.SecurityIdentifier)); - WindowsIdentity identity = WindowsIdentity.GetCurrent(); - - foreach (FileSystemAccessRule rule in rules) - { - if (identity.Groups.Contains(rule.IdentityReference)) - { - if ((AccessRight & rule.FileSystemRights) == AccessRight) - { - if (rule.AccessControlType == AccessControlType.Allow) - return true; - } - } - } - } - catch { } - - return false; - } - - public static bool CheckModifiableAccess(string Path) - { - // checks if the current user has rights to modify the given file/directory - // adapted from https://stackoverflow.com/questions/1410127/c-sharp-test-if-user-has-write-access-to-a-folder/21996345#21996345 - - if (string.IsNullOrEmpty(Path)) return false; - // TODO: check if file exists, check file's parent folder - - FileSystemRights[] ModifyRights = - { - FileSystemRights.ChangePermissions, - FileSystemRights.FullControl, - FileSystemRights.Modify, - FileSystemRights.TakeOwnership, - FileSystemRights.Write, - FileSystemRights.WriteData, - FileSystemRights.CreateDirectories, - FileSystemRights.CreateFiles - }; - - ArrayList paths = new ArrayList(); - paths.Add(Path); - - try - { - FileAttributes attr = System.IO.File.GetAttributes(Path); - if ((attr & FileAttributes.Directory) != FileAttributes.Directory) - { - string parentFolder = System.IO.Path.GetDirectoryName(Path); - paths.Add(parentFolder); - } - } - catch - { - return false; - } - - - try - { - foreach (string candidatePath in paths) - { - AuthorizationRuleCollection rules = Directory.GetAccessControl(candidatePath).GetAccessRules(true, true, typeof(System.Security.Principal.SecurityIdentifier)); - WindowsIdentity identity = WindowsIdentity.GetCurrent(); - - foreach (FileSystemAccessRule rule in rules) - { - if (identity.Groups.Contains(rule.IdentityReference)) - { - foreach (FileSystemRights AccessRight in ModifyRights) - { - if ((AccessRight & rule.FileSystemRights) == AccessRight) - { - if (rule.AccessControlType == AccessControlType.Allow) - return true; - } - } - } - } - } - return false; - } - catch - { - return false; - } - } - - public static List FindFiles(string path, string patterns) - { - // finds files matching one or more patterns under a given path, recursive - // adapted from http://csharphelper.com/blog/2015/06/find-files-that-match-multiple-patterns-in-c/ - // pattern: "*pass*;*.png;" - - var files = new List(); - - try - { - // search every pattern in this directory's files - foreach (string pattern in patterns.Split(';')) - { - files.AddRange(Directory.GetFiles(path, pattern, SearchOption.TopDirectoryOnly)); - } - - // go recurse in all sub-directories - foreach (var directory in Directory.GetDirectories(path)) - files.AddRange(FindFiles(directory, patterns)); - } - catch (UnauthorizedAccessException) { } - catch (PathTooLongException) { } - - return files; - } - - public static IEnumerable Split(string text, int partLength) - { - if (text == null) { Console.WriteLine("[ERROR] Split() - singleLineString"); } - if (partLength < 1) { Console.WriteLine("[ERROR] Split() - 'columns' must be greater than 0."); } - - var partCount = Math.Ceiling((double)text.Length / partLength); - if (partCount < 2) - { - yield return text; - } - - for (int i = 0; i < partCount; i++) - { - var index = i * partLength; - var lengthLeft = Math.Min(partLength, text.Length - index); - var line = text.Substring(index, lengthLeft); - yield return line; - } - } - - - // start of checks - - // system-focused checks - public static void ListBasicOSInfo() - { - // returns basic OS/host information, including: - // Windows version information - // integrity/admin levels - // processor count/architecture - // basic user and domain information - // whether the system is a VM - // etc. - - string ProductName = GetRegValue("HKLM", "Software\\Microsoft\\Windows NT\\CurrentVersion", "ProductName"); - string EditionID = GetRegValue("HKLM", "Software\\Microsoft\\Windows NT\\CurrentVersion", "EditionID"); - string ReleaseId = GetRegValue("HKLM", "Software\\Microsoft\\Windows NT\\CurrentVersion", "ReleaseId"); - string BuildBranch = GetRegValue("HKLM", "Software\\Microsoft\\Windows NT\\CurrentVersion", "BuildBranch"); - string CurrentMajorVersionNumber = GetRegValue("HKLM", "Software\\Microsoft\\Windows NT\\CurrentVersion", "CurrentMajorVersionNumber"); - string CurrentVersion = GetRegValue("HKLM", "Software\\Microsoft\\Windows NT\\CurrentVersion", "CurrentVersion"); - - bool isHighIntegrity = IsHighIntegrity(); - bool isLocalAdmin = IsLocalAdmin(); - - string arch = System.Environment.GetEnvironmentVariable("PROCESSOR_ARCHITECTURE"); - string userName = System.Environment.GetEnvironmentVariable("USERNAME"); - string ProcessorCount = System.Environment.ProcessorCount.ToString(); - bool isVM = IsVirtualMachine(); - - DateTime now = DateTime.UtcNow; - DateTime boot = now - TimeSpan.FromMilliseconds(Environment.TickCount); - DateTime BootTime = boot + TimeSpan.FromMilliseconds(System.Environment.TickCount); - - String strHostName = Dns.GetHostName(); - IPGlobalProperties properties = IPGlobalProperties.GetIPGlobalProperties(); - string dnsDomain = properties.DomainName; - - Console.WriteLine("\r\n\r\n=== Basic OS Information ===\r\n"); - Console.WriteLine(String.Format(" {0,-30}: {1}", "Hostname", strHostName)); - Console.WriteLine(String.Format(" {0,-30}: {1}", "Domain Name", dnsDomain)); - Console.WriteLine(String.Format(" {0,-30}: {1}", "Username", WindowsIdentity.GetCurrent().Name)); - Console.WriteLine(String.Format(" {0,-30}: {1}", "ProductName", ProductName)); - Console.WriteLine(String.Format(" {0,-30}: {1}", "EditionID", EditionID)); - Console.WriteLine(String.Format(" {0,-30}: {1}", "ReleaseId", ReleaseId)); - Console.WriteLine(String.Format(" {0,-30}: {1}", "BuildBranch", BuildBranch)); - Console.WriteLine(String.Format(" {0,-30}: {1}", "CurrentMajorVersionNumber", CurrentMajorVersionNumber)); - Console.WriteLine(String.Format(" {0,-30}: {1}", "CurrentVersion", CurrentVersion)); - Console.WriteLine(String.Format(" {0,-30}: {1}", "Architecture", arch)); - Console.WriteLine(String.Format(" {0,-30}: {1}", "ProcessorCount", ProcessorCount)); - Console.WriteLine(String.Format(" {0,-30}: {1}", "IsVirtualMachine", isVM)); - Console.WriteLine(String.Format(" {0,-30}: {1}", "BootTime (approx)", BootTime)); - Console.WriteLine(String.Format(" {0,-30}: {1}", "HighIntegrity", isHighIntegrity)); - Console.WriteLine(String.Format(" {0,-30}: {1}", "IsLocalAdmin", isLocalAdmin)); - if (!isHighIntegrity && isLocalAdmin) - { - Console.WriteLine(" [*] In medium integrity but user is a local administrator- UAC can be bypassed."); - } - } - - public static void ListRebootSchedule() - { - // queries event IDs 12 (kernel boot) and 13 (kernel shutdown), sorts, and gives reboot schedule - // grab events from the last X days - 15 for default - int lastDays = 15; - - Console.WriteLine("\r\n\r\n=== Reboot Schedule (event ID 12/13 from last {0} days) ===\r\n", lastDays); - - SortedDictionary events = new SortedDictionary(); - - var startTime = System.DateTime.Now.AddDays(-lastDays); - var endTime = System.DateTime.Now; - - // eventID 12 == start up - var query = string.Format(@"*[System/EventID=12] and *[System[TimeCreated[@SystemTime >= '{0}']]] and *[System[TimeCreated[@SystemTime <= '{1}']]]", - startTime.ToUniversalTime().ToString("o"), - endTime.ToUniversalTime().ToString("o")); - - EventLogQuery eventsQuery = new EventLogQuery("System", PathType.LogName, query); - - try - { - EventLogReader logReader = new EventLogReader(eventsQuery); - - for (EventRecord eventdetail = logReader.ReadEvent(); eventdetail != null; eventdetail = logReader.ReadEvent()) - { - System.DateTime time = DateTime.Parse(eventdetail.Properties[6].Value.ToString()); - events.Add(time, "startup"); - } - } - catch (Exception ex) - { - Console.WriteLine(" [X] Exception: {0}", ex.Message); - } - - // eventID 13 == shutdown - var query2 = string.Format(@"*[System/EventID=13] and *[System[TimeCreated[@SystemTime >= '{0}']]] and *[System[TimeCreated[@SystemTime <= '{1}']]]", - startTime.ToUniversalTime().ToString("o"), - endTime.ToUniversalTime().ToString("o")); - - EventLogQuery eventsQuery2 = new EventLogQuery("System", PathType.LogName, query2); - - try - { - EventLogReader logReader2 = new EventLogReader(eventsQuery2); - - for (EventRecord eventdetail2 = logReader2.ReadEvent(); eventdetail2 != null; eventdetail2 = logReader2.ReadEvent()) - { - System.DateTime time = DateTime.Parse(eventdetail2.Properties[0].Value.ToString()); - events.Add(time, "shutdown"); - } - } - catch (Exception ex) - { - Console.WriteLine(" [X] Exception: {0}", ex.Message); - } - - foreach (var kvp in events) - { - Console.WriteLine(String.Format(" {0,-23} : {1}", kvp.Key, kvp.Value)); - if (kvp.Value == "shutdown") - { - Console.WriteLine(); - } - } - } - - public static void ListTokenGroupPrivs() - { - // Returns all privileges that the current process/user possesses - // adapted from https://stackoverflow.com/questions/4349743/setting-size-of-token-privileges-luid-and-attributes-array-returned-by-gettokeni - - try - { - Console.WriteLine("\r\n\r\n=== Current Privileges ===\r\n"); - - int TokenInfLength = 0; - IntPtr ThisHandle = WindowsIdentity.GetCurrent().Token; - GetTokenInformation(ThisHandle, TOKEN_INFORMATION_CLASS.TokenPrivileges, IntPtr.Zero, TokenInfLength, out TokenInfLength); - IntPtr TokenInformation = Marshal.AllocHGlobal(TokenInfLength); - if (GetTokenInformation(WindowsIdentity.GetCurrent().Token, TOKEN_INFORMATION_CLASS.TokenPrivileges, TokenInformation, TokenInfLength, out TokenInfLength)) - { - TOKEN_PRIVILEGES ThisPrivilegeSet = (TOKEN_PRIVILEGES)Marshal.PtrToStructure(TokenInformation, typeof(TOKEN_PRIVILEGES)); - for (int index = 0; index < ThisPrivilegeSet.PrivilegeCount; index++) - { - LUID_AND_ATTRIBUTES laa = ThisPrivilegeSet.Privileges[index]; - System.Text.StringBuilder StrBuilder = new System.Text.StringBuilder(); - int LuidNameLen = 0; - IntPtr LuidPointer = Marshal.AllocHGlobal(Marshal.SizeOf(laa.Luid)); - Marshal.StructureToPtr(laa.Luid, LuidPointer, true); - LookupPrivilegeName(null, LuidPointer, null, ref LuidNameLen); - StrBuilder.EnsureCapacity(LuidNameLen + 1); - if (LookupPrivilegeName(null, LuidPointer, StrBuilder, ref LuidNameLen)) - { - Console.WriteLine(String.Format(" {0,43}: {1}", StrBuilder.ToString(), (LuidAttributes)laa.Attributes)); - } - Marshal.FreeHGlobal(LuidPointer); - } - } - } - catch (Exception ex) - { - Console.WriteLine(" [X] Exception: {0}", ex.Message); - } - } - - public static void ListUserEnvVariables() - { - try - { - // dumps out current user environment variables - Console.WriteLine("\r\n\r\n=== User Environment Variables ===\r\n"); - foreach (System.Collections.DictionaryEntry env in Environment.GetEnvironmentVariables()) - { - string name = (string)env.Key; - string value = (string)env.Value; - Console.WriteLine(" {0,-35} : {1}", name, value); - } - } - catch (Exception ex) - { - Console.WriteLine(" [X] Exception: {0}", ex.Message); - } - } - - public static void ListSystemEnvVariables() - { - // dumps out current system environment variables - Console.WriteLine("\r\n\r\n=== System Environment Variables ===\r\n"); - Dictionary settings = GetRegValues("HKLM", "SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment"); - if ((settings != null) && (settings.Count != 0)) - { - foreach (KeyValuePair kvp in settings) - { - Console.WriteLine(" {0,-35} : {1}", kvp.Key, kvp.Value); - } - } - } - - public static void ListNonstandardServices() - { - // lists installed servics that don't have "Microsoft Corporation" as the company name in their file info - // or all services if "full" is passed - - if (FilterResults.filter) - { - Console.WriteLine("\r\n\r\n=== Non Microsoft Services (via WMI) ===\r\n"); - } - else - { - Console.WriteLine("\r\n\r\n=== All Services (via WMI) ===\r\n"); - } - - try - { - ManagementObjectSearcher wmiData = new ManagementObjectSearcher(@"root\cimv2", "SELECT * FROM win32_service"); - ManagementObjectCollection data = wmiData.Get(); - - foreach (ManagementObject result in data) - { - //OLD - if ((result["PathName"] != null) && ((!FilterResults.filter) || (!Regex.IsMatch(result["PathName"].ToString(), "C:\\\\WINDOWS\\\\", RegexOptions.IgnoreCase)))) - if (result["PathName"] != null) - { - Match path = Regex.Match(result["PathName"].ToString(), @"^\W*([a-z]:\\.+?(\.exe|\.dll|\.sys))\W*", RegexOptions.IgnoreCase); - String binaryPath = path.Groups[1].ToString(); - FileVersionInfo myFileVersionInfo = FileVersionInfo.GetVersionInfo(binaryPath); - string companyName = myFileVersionInfo.CompanyName; - if ((String.IsNullOrEmpty(companyName)) || (!FilterResults.filter) || (!Regex.IsMatch(companyName, @"^Microsoft.*", RegexOptions.IgnoreCase))) - { - bool isDotNet = false; - try - { - AssemblyName myAssemblyName = AssemblyName.GetAssemblyName(binaryPath); - isDotNet = true; - } - catch (System.IO.FileNotFoundException) - { - // System.Console.WriteLine("The file cannot be found."); - } - catch (System.BadImageFormatException exception) - { - if (Regex.IsMatch(exception.Message, ".*This assembly is built by a runtime newer than the currently loaded runtime and cannot be loaded.*", RegexOptions.IgnoreCase)) - { - isDotNet = true; - } - } - catch - { - // System.Console.WriteLine("The assembly has already been loaded."); - } - - Console.WriteLine(" Name : {0}", result["Name"]); - Console.WriteLine(" DisplayName : {0}", result["DisplayName"]); - Console.WriteLine(" Company Name : {0}", companyName); - Console.WriteLine(" Description : {0}", result["Description"]); - Console.WriteLine(" State : {0}", result["State"]); - Console.WriteLine(" StartMode : {0}", result["StartMode"]); - Console.WriteLine(" PathName : {0}", result["PathName"]); - Console.WriteLine(" IsDotNet : {0}\r\n", isDotNet); - } - } - } - } - catch (Exception ex) - { - Console.WriteLine(" [X] Exception: {0}", ex.Message); - } - } - - public static void ListUserFolders() - { - // lists the folders in C:\Users\, showing users who have logged onto the system - try - { - Console.WriteLine("\r\n\r\n=== User Folders ===\r\n"); - string userPath = String.Format("{0}\\Users\\", Environment.GetEnvironmentVariable("SystemDrive")); - - string[] dirs = Directory.GetDirectories(userPath); - Console.WriteLine(" {0,-35} {1}", "Folder", "Last Modified Time"); - foreach (string dir in dirs) - { - if (!(dir.EndsWith("Public") || dir.EndsWith("Default") || dir.EndsWith("Default User") || dir.EndsWith("All Users"))) - { - DateTime dt = Directory.GetLastWriteTime(dir); - Console.WriteLine(" {0,-35} : {1}", dir, dt); - } - } - } - catch (Exception ex) - { - Console.WriteLine(" [X] Exception: {0}", ex.Message); - } - } - - public static void ListUACSystemPolicies() - { - // dump out various UAC system policies - Console.WriteLine("\r\n\r\n=== UAC System Policies ===\r\n"); - - string ConsentPromptBehaviorAdmin = GetRegValue("HKLM", "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\System", "ConsentPromptBehaviorAdmin"); - switch (ConsentPromptBehaviorAdmin) - { - case "0": - Console.WriteLine(" {0,-30} : {1} - No prompting", "ConsentPromptBehaviorAdmin", ConsentPromptBehaviorAdmin); - break; - case "1": - Console.WriteLine(" {0,-30} : {1} - PromptOnSecureDesktop", "ConsentPromptBehaviorAdmin", ConsentPromptBehaviorAdmin); - break; - case "2": - Console.WriteLine(" {0,-30} : {1} - PromptPermitDenyOnSecureDesktop", "ConsentPromptBehaviorAdmin", ConsentPromptBehaviorAdmin); - break; - case "3": - Console.WriteLine(" {0,-30} : {1} - PromptForCredsNotOnSecureDesktop", "ConsentPromptBehaviorAdmin", ConsentPromptBehaviorAdmin); - break; - case "4": - Console.WriteLine(" {0,-30} : {1} - PromptForPermitDenyNotOnSecureDesktop", "ConsentPromptBehaviorAdmin", ConsentPromptBehaviorAdmin); - break; - case "5": - Console.WriteLine(" {0,-30} : {1} - PromptForNonWindowsBinaries", "ConsentPromptBehaviorAdmin", ConsentPromptBehaviorAdmin); - break; - default: - Console.WriteLine(" {0,-30} : PromptForNonWindowsBinaries", "ConsentPromptBehaviorAdmin"); - break; - } - - string EnableLUA = GetRegValue("HKLM", "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\System", "EnableLUA"); - Console.WriteLine(" {0,-30} : {1}", "EnableLUA", EnableLUA); - if ((EnableLUA == "") || (EnableLUA == "0")) - { - Console.WriteLine(" [*] EnableLUA != 1, UAC policies disabled.\r\n [*] Any local account can be used for lateral movement."); - } - - string LocalAccountTokenFilterPolicy = GetRegValue("HKLM", "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\System", "LocalAccountTokenFilterPolicy"); - Console.WriteLine(" {0,-30} : {1}", "LocalAccountTokenFilterPolicy", LocalAccountTokenFilterPolicy); - if ((EnableLUA == "1") && (LocalAccountTokenFilterPolicy == "1")) - { - Console.WriteLine(" [*] LocalAccountTokenFilterPolicy set to 1.\r\n [*] Any local account can be used for lateral movement."); - } - - string FilterAdministratorToken = GetRegValue("HKLM", "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\System", "FilterAdministratorToken"); - Console.WriteLine(" {0,-30} : {1}", "FilterAdministratorToken", FilterAdministratorToken); - - if ((EnableLUA == "1") && (LocalAccountTokenFilterPolicy != "1") && (FilterAdministratorToken != "1")) - { - Console.WriteLine(" [*] LocalAccountTokenFilterPolicy set to 0 and FilterAdministratorToken != 1.\r\n [*] Only the RID-500 local admin account can be used for lateral movement."); - } - - if ((EnableLUA == "1") && (LocalAccountTokenFilterPolicy != "1") && (FilterAdministratorToken == "1")) - { - Console.WriteLine(" [*] LocalAccountTokenFilterPolicy set to 0 and FilterAdministratorToken == 1.\r\n [*] No local accounts can be used for lateral movement."); - } - } - - public static void ListPowerShellSettings() - { - Console.WriteLine("\r\n\r\n=== PowerShell Settings ===\r\n"); - - string PowerShellVersion2 = GetRegValue("HKLM", "SOFTWARE\\Microsoft\\PowerShell\\1\\PowerShellEngine", "PowerShellVersion"); - Console.WriteLine(" {0,-30} : {1}", "PowerShell v2 Version", PowerShellVersion2); - - string PowerShellVersion5 = GetRegValue("HKLM", "SOFTWARE\\Microsoft\\PowerShell\\3\\PowerShellEngine", "PowerShellVersion"); - Console.WriteLine(" {0,-30} : {1}", "PowerShell v5 Version", PowerShellVersion5); - - Dictionary transcriptionSettings = GetRegValues("HKLM", "SOFTWARE\\Policies\\Microsoft\\Windows\\PowerShell\\Transcription"); - Console.WriteLine("\r\n Transcription Settings:\r\n"); - if ((transcriptionSettings != null) && (transcriptionSettings.Count != 0)) - { - foreach (KeyValuePair kvp in transcriptionSettings) - { - Console.WriteLine(" {0,30} : {1}\r\n", kvp.Key, kvp.Value); - } - } - - Dictionary moduleLoggingSettings = GetRegValues("HKLM", "SOFTWARE\\Policies\\Microsoft\\Windows\\PowerShell\\ModuleLogging"); - Console.WriteLine(" Module Logging Settings:\r\n"); - if ((moduleLoggingSettings != null) && (moduleLoggingSettings.Count != 0)) - { - foreach (KeyValuePair kvp in moduleLoggingSettings) - { - Console.WriteLine(" {0,30} : {1}\r\n", kvp.Key, kvp.Value); - } - } - - Dictionary scriptBlockSettings = GetRegValues("HKLM", "SOFTWARE\\Policies\\Microsoft\\Windows\\PowerShell\\ScriptBlockLogging"); - Console.WriteLine(" Scriptblock Logging Settings:\r\n"); - if ((scriptBlockSettings != null) && (scriptBlockSettings.Count != 0)) - { - foreach (KeyValuePair kvp in scriptBlockSettings) - { - Console.WriteLine(" {0,30} : {1}\r\n", kvp.Key, kvp.Value); - } - } - } - - public static void ListInternetSettings() - { - // lists user/system internet settings, including default proxy info - - Dictionary proxySettings = GetRegValues("HKCU", "Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings"); - Console.WriteLine("\r\n\r\n=== HKCU Internet Settings ===\r\n"); - if ((proxySettings != null) && (proxySettings.Count != 0)) - { - foreach (KeyValuePair kvp in proxySettings) - { - Console.WriteLine(" {0,30} : {1}", kvp.Key, kvp.Value); - } - } - - Dictionary proxySettings2 = GetRegValues("HKLM", "Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings"); - Console.WriteLine("\r\n\r\n=== HKLM Internet Settings ===\r\n"); - if ((proxySettings2 != null) && (proxySettings2.Count != 0)) - { - foreach (KeyValuePair kvp in proxySettings2) - { - Console.WriteLine(" {0,30} : {1}", kvp.Key, kvp.Value); - } - } - } - - public static void ListLSASettings() - { - Console.WriteLine("\r\n\r\n=== LSA Settings ===\r\n"); - Dictionary settings = GetRegValues("HKLM", "SYSTEM\\CurrentControlSet\\Control\\Lsa"); - if ((settings != null) && (settings.Count != 0)) - { - foreach (KeyValuePair kvp in settings) - { - if (kvp.Value.GetType().IsArray && (kvp.Value.GetType().GetElementType().ToString() == "System.String")) - { - string result = string.Join(",", (string[])kvp.Value); - Console.WriteLine(" {0,-30} : {1}", kvp.Key, result); - - if (kvp.Key.ToString() == "Security Packages") - { - Regex regex = new Regex(@".*wdigest.*"); - Match m = regex.Match(result); - if (m.Success) - { - Console.WriteLine(" [*] Wdigest is enabled- plaintext password extraction is possible!"); - } - } - } - else - { - Console.WriteLine(" {0,-30} : {1}", kvp.Key, kvp.Value); - } - } - } - } - - public static void ListKerberosTickets() - { - if (IsHighIntegrity()) - { - ListKerberosTicketsAllUsers(); - } - else - { - ListKerberosTicketsCurrentUser(); - } - } - public static void ListKerberosTicketsAllUsers() - { - // adapted partially from Vincent LE TOUX' work - // https://github.com/vletoux/MakeMeEnterpriseAdmin/blob/master/MakeMeEnterpriseAdmin.ps1#L2939-L2950 - // and https://www.dreamincode.net/forums/topic/135033-increment-memory-pointer-issue/ - // also Jared Atkinson's work at https://github.com/Invoke-IR/ACE/blob/master/ACE-Management/PS-ACE/Scripts/ACE_Get-KerberosTicketCache.ps1 - - Console.WriteLine("\r\n\r\n=== Kerberos Tickets (All Users) ===\r\n"); - - IntPtr hLsa = LsaRegisterLogonProcessHelper(); - int totalTicketCount = 0; - - // if the original call fails then it is likely we don't have SeTcbPrivilege - // to get SeTcbPrivilege we can Impersonate a NT AUTHORITY\SYSTEM Token - if (hLsa == IntPtr.Zero) - { - GetSystem(); - // should now have the proper privileges to get a Handle to LSA - hLsa = LsaRegisterLogonProcessHelper(); - // we don't need our NT AUTHORITY\SYSTEM Token anymore so we can revert to our original token - RevertToSelf(); - } - - try - { - // first return all the logon sessions - - DateTime systime = new DateTime(1601, 1, 1, 0, 0, 0, 0); //win32 systemdate - UInt64 count; - IntPtr luidPtr = IntPtr.Zero; - IntPtr iter = luidPtr; - - uint ret = LsaEnumerateLogonSessions(out count, out luidPtr); // get an array of pointers to LUIDs - - for (ulong i = 0; i < count; i++) - { - IntPtr sessionData; - ret = LsaGetLogonSessionData(luidPtr, out sessionData); - SECURITY_LOGON_SESSION_DATA data = (SECURITY_LOGON_SESSION_DATA)Marshal.PtrToStructure(sessionData, typeof(SECURITY_LOGON_SESSION_DATA)); - - // if we have a valid logon - if (data.PSiD != IntPtr.Zero) - { - // user session data - string username = Marshal.PtrToStringUni(data.Username.Buffer).Trim(); - System.Security.Principal.SecurityIdentifier sid = new System.Security.Principal.SecurityIdentifier(data.PSiD); - string domain = Marshal.PtrToStringUni(data.LoginDomain.Buffer).Trim(); - string authpackage = Marshal.PtrToStringUni(data.AuthenticationPackage.Buffer).Trim(); - SECURITY_LOGON_TYPE logonType = (SECURITY_LOGON_TYPE)data.LogonType; - DateTime logonTime = systime.AddTicks((long)data.LoginTime); - string logonServer = Marshal.PtrToStringUni(data.LogonServer.Buffer).Trim(); - string dnsDomainName = Marshal.PtrToStringUni(data.DnsDomainName.Buffer).Trim(); - string upn = Marshal.PtrToStringUni(data.Upn.Buffer).Trim(); - - // now we want to get the tickets for this logon ID - string name = "kerberos"; - LSA_STRING_IN LSAString; - LSAString.Length = (ushort)name.Length; - LSAString.MaximumLength = (ushort)(name.Length + 1); - LSAString.Buffer = name; - - IntPtr ticketPointer = IntPtr.Zero; - IntPtr ticketsPointer = IntPtr.Zero; - DateTime sysTime = new DateTime(1601, 1, 1, 0, 0, 0, 0); - int authPack; - int returnBufferLength = 0; - int protocalStatus = 0; - int retCode; - - KERB_QUERY_TKT_CACHE_REQUEST tQuery = new KERB_QUERY_TKT_CACHE_REQUEST(); - KERB_QUERY_TKT_CACHE_RESPONSE tickets = new KERB_QUERY_TKT_CACHE_RESPONSE(); - KERB_TICKET_CACHE_INFO ticket; - - // obtains the unique identifier for the kerberos authentication package. - retCode = LsaLookupAuthenticationPackage(hLsa, ref LSAString, out authPack); - - // input object for querying the ticket cache for a specific logon ID - LUID userLogonID = new LUID(); - userLogonID.LowPart = data.LoginID.LowPart; - userLogonID.HighPart = 0; - tQuery.LogonId = userLogonID; - tQuery.MessageType = KERB_PROTOCOL_MESSAGE_TYPE.KerbQueryTicketCacheMessage; - - // query LSA, specifying we want the ticket cache - retCode = LsaCallAuthenticationPackage(hLsa, authPack, ref tQuery, Marshal.SizeOf(tQuery), out ticketPointer, out returnBufferLength, out protocalStatus); - - Console.WriteLine("\r\n UserName : {0}", username); - Console.WriteLine(" Domain : {0}", domain); - Console.WriteLine(" LogonId : {0}", data.LoginID.LowPart); - Console.WriteLine(" UserSID : {0}", sid.AccountDomainSid); - Console.WriteLine(" AuthenticationPackage : {0}", authpackage); - Console.WriteLine(" LogonType : {0}", logonType); - Console.WriteLine(" LogonType : {0}", logonTime); - Console.WriteLine(" LogonServer : {0}", logonServer); - Console.WriteLine(" LogonServerDNSDomain : {0}", dnsDomainName); - Console.WriteLine(" UserPrincipalName : {0}\r\n", upn); - - if (ticketPointer != IntPtr.Zero) - { - // parse the returned pointer into our initial KERB_QUERY_TKT_CACHE_RESPONSE structure - tickets = (KERB_QUERY_TKT_CACHE_RESPONSE)Marshal.PtrToStructure((System.IntPtr)ticketPointer, typeof(KERB_QUERY_TKT_CACHE_RESPONSE)); - int count2 = tickets.CountOfTickets; - - if (count2 != 0) - { - Console.WriteLine(" [*] Enumerated {0} ticket(s):\r\n", count2); - totalTicketCount += count2; - // get the size of the structures we're iterating over - Int32 dataSize = Marshal.SizeOf(typeof(KERB_TICKET_CACHE_INFO)); - - for (int j = 0; j < count2; j++) - { - // iterate through the structures - IntPtr currTicketPtr = (IntPtr)(long)((ticketPointer.ToInt64() + (int)(8 + j * dataSize))); - - // parse the new ptr to the appropriate structure - ticket = (KERB_TICKET_CACHE_INFO)Marshal.PtrToStructure(currTicketPtr, typeof(KERB_TICKET_CACHE_INFO)); - - // extract our fields - string serverName = Marshal.PtrToStringUni(ticket.ServerName.Buffer, ticket.ServerName.Length / 2); - string realmName = Marshal.PtrToStringUni(ticket.RealmName.Buffer, ticket.RealmName.Length / 2); - DateTime startTime = DateTime.FromFileTime(ticket.StartTime); - DateTime endTime = DateTime.FromFileTime(ticket.EndTime); - DateTime renewTime = DateTime.FromFileTime(ticket.RenewTime); - string encryptionType = ((KERB_ENCRYPTION_TYPE)ticket.EncryptionType).ToString(); - string ticketFlags = ((KERB_TICKET_FLAGS)ticket.TicketFlags).ToString(); - - Console.WriteLine(" ServerName : {0}", serverName); - Console.WriteLine(" RealmName : {0}", realmName); - Console.WriteLine(" StartTime : {0}", startTime); - Console.WriteLine(" EndTime : {0}", endTime); - Console.WriteLine(" RenewTime : {0}", renewTime); - Console.WriteLine(" EncryptionType : {0}", encryptionType); - Console.WriteLine(" TicketFlags : {0}\r\n", ticketFlags); - } - } - } - } - // move the pointer forward - luidPtr = (IntPtr)((long)luidPtr.ToInt64() + Marshal.SizeOf(typeof(LUID))); - LsaFreeReturnBuffer(sessionData); - } - LsaFreeReturnBuffer(luidPtr); - - // disconnect from LSA - LsaDeregisterLogonProcess(hLsa); - - Console.WriteLine("\r\n\r\n [*] Enumerated {0} total tickets\r\n", totalTicketCount); - } - catch (Exception ex) - { - Console.WriteLine(" [X] Exception: {0}", ex); - } - } - public static void ListKerberosTicketsCurrentUser() - { - // adapted partially from Vincent LE TOUX' work - // https://github.com/vletoux/MakeMeEnterpriseAdmin/blob/master/MakeMeEnterpriseAdmin.ps1#L2939-L2950 - // and https://www.dreamincode.net/forums/topic/135033-increment-memory-pointer-issue/ - // also Jared Atkinson's work at https://github.com/Invoke-IR/ACE/blob/master/ACE-Management/PS-ACE/Scripts/ACE_Get-KerberosTicketCache.ps1 - - Console.WriteLine("\r\n\r\n=== Kerberos Tickets (Current User) ===\r\n"); - - try - { - string name = "kerberos"; - LSA_STRING_IN LSAString; - LSAString.Length = (ushort)name.Length; - LSAString.MaximumLength = (ushort)(name.Length + 1); - LSAString.Buffer = name; - - IntPtr ticketPointer = IntPtr.Zero; - IntPtr ticketsPointer = IntPtr.Zero; - DateTime sysTime = new DateTime(1601, 1, 1, 0, 0, 0, 0); - int authPack; - int returnBufferLength = 0; - int protocalStatus = 0; - IntPtr lsaHandle; - int retCode; - - // If we want to look at tickets from a session other than our own - // then we need to use LsaRegisterLogonProcess instead of LsaConnectUntrusted - retCode = LsaConnectUntrusted(out lsaHandle); - - KERB_QUERY_TKT_CACHE_REQUEST tQuery = new KERB_QUERY_TKT_CACHE_REQUEST(); - KERB_QUERY_TKT_CACHE_RESPONSE tickets = new KERB_QUERY_TKT_CACHE_RESPONSE(); - KERB_TICKET_CACHE_INFO ticket; - - // obtains the unique identifier for the kerberos authentication package. - retCode = LsaLookupAuthenticationPackage(lsaHandle, ref LSAString, out authPack); - - // input object for querying the ticket cache (https://docs.microsoft.com/en-us/windows/desktop/api/ntsecapi/ns-ntsecapi-_kerb_query_tkt_cache_request) - tQuery.LogonId = new LUID(); - tQuery.MessageType = KERB_PROTOCOL_MESSAGE_TYPE.KerbQueryTicketCacheMessage; - - // query LSA, specifying we want the ticket cache - retCode = LsaCallAuthenticationPackage(lsaHandle, authPack, ref tQuery, Marshal.SizeOf(tQuery), out ticketPointer, out returnBufferLength, out protocalStatus); - - // parse the returned pointer into our initial KERB_QUERY_TKT_CACHE_RESPONSE structure - tickets = (KERB_QUERY_TKT_CACHE_RESPONSE)Marshal.PtrToStructure((System.IntPtr)ticketPointer, typeof(KERB_QUERY_TKT_CACHE_RESPONSE)); - int count = tickets.CountOfTickets; - Console.WriteLine(" [*] Returned {0} tickets\r\n", count); - - // get the size of the structures we're iterating over - Int32 dataSize = Marshal.SizeOf(typeof(KERB_TICKET_CACHE_INFO)); - - for (int i = 0; i < count; i++) - { - // iterate through the structures - IntPtr currTicketPtr = (IntPtr)(long)((ticketPointer.ToInt64() + (int)(8 + i * dataSize))); - - // parse the new ptr to the appropriate structure - ticket = (KERB_TICKET_CACHE_INFO)Marshal.PtrToStructure(currTicketPtr, typeof(KERB_TICKET_CACHE_INFO)); - - // extract our fields - string serverName = Marshal.PtrToStringUni(ticket.ServerName.Buffer, ticket.ServerName.Length / 2); - string realmName = Marshal.PtrToStringUni(ticket.RealmName.Buffer, ticket.RealmName.Length / 2); - DateTime startTime = DateTime.FromFileTime(ticket.StartTime); - DateTime endTime = DateTime.FromFileTime(ticket.EndTime); - DateTime renewTime = DateTime.FromFileTime(ticket.RenewTime); - string encryptionType = ((KERB_ENCRYPTION_TYPE)ticket.EncryptionType).ToString(); - string ticketFlags = ((KERB_TICKET_FLAGS)ticket.TicketFlags).ToString(); - - Console.WriteLine(" ServerName : {0}", serverName); - Console.WriteLine(" RealmName : {0}", realmName); - Console.WriteLine(" StartTime : {0}", startTime); - Console.WriteLine(" EndTime : {0}", endTime); - Console.WriteLine(" RenewTime : {0}", renewTime); - Console.WriteLine(" EncryptionType : {0}", encryptionType); - Console.WriteLine(" TicketFlags : {0}\r\n", ticketFlags); - } - - // disconnect from LSA - LsaDeregisterLogonProcess(lsaHandle); - } - catch (Exception ex) - { - Console.WriteLine(" [X] Exception: {0}", ex.Message); - } - } - - public static void ListKerberosTGTData() - { - if (IsHighIntegrity()) - { - ListKerberosTGTDataAllUsers(); - } - else - { - ListKerberosTGTDataCurrentUser(); - } - } - public static void ListKerberosTGTDataAllUsers() - { - // adapted partially from Vincent LE TOUX' work - // https://github.com/vletoux/MakeMeEnterpriseAdmin/blob/master/MakeMeEnterpriseAdmin.ps1#L2939-L2950 - // and https://www.dreamincode.net/forums/topic/135033-increment-memory-pointer-issue/ - // also Jared Atkinson's work at https://github.com/Invoke-IR/ACE/blob/master/ACE-Management/PS-ACE/Scripts/ACE_Get-KerberosTicketCache.ps1 - - Console.WriteLine("\r\n\r\n=== Kerberos TGT Data (All Users) ===\r\n"); - - IntPtr hLsa = LsaRegisterLogonProcessHelper(); - int totalTicketCount = 0; - - // if the original call fails then it is likely we don't have SeTcbPrivilege - // to get SeTcbPrivilege we can Impersonate a NT AUTHORITY\SYSTEM Token - if (hLsa == IntPtr.Zero) - { - GetSystem(); - // should now have the proper privileges to get a Handle to LSA - hLsa = LsaRegisterLogonProcessHelper(); - // we don't need our NT AUTHORITY\SYSTEM Token anymore so we can revert to our original token - RevertToSelf(); - } - - try - { - // first return all the logon sessions - - DateTime systime = new DateTime(1601, 1, 1, 0, 0, 0, 0); //win32 systemdate - UInt64 count; - IntPtr luidPtr = IntPtr.Zero; - IntPtr iter = luidPtr; - - uint ret = LsaEnumerateLogonSessions(out count, out luidPtr); // get an array of pointers to LUIDs - - for (ulong i = 0; i < count; i++) - { - IntPtr sessionData; - ret = LsaGetLogonSessionData(luidPtr, out sessionData); - SECURITY_LOGON_SESSION_DATA data = (SECURITY_LOGON_SESSION_DATA)Marshal.PtrToStructure(sessionData, typeof(SECURITY_LOGON_SESSION_DATA)); - - // if we have a valid logon - if (data.PSiD != IntPtr.Zero) - { - // user session data - string username = Marshal.PtrToStringUni(data.Username.Buffer).Trim(); - System.Security.Principal.SecurityIdentifier sid = new System.Security.Principal.SecurityIdentifier(data.PSiD); - string domain = Marshal.PtrToStringUni(data.LoginDomain.Buffer).Trim(); - string authpackage = Marshal.PtrToStringUni(data.AuthenticationPackage.Buffer).Trim(); - SECURITY_LOGON_TYPE logonType = (SECURITY_LOGON_TYPE)data.LogonType; - DateTime logonTime = systime.AddTicks((long)data.LoginTime); - string logonServer = Marshal.PtrToStringUni(data.LogonServer.Buffer).Trim(); - string dnsDomainName = Marshal.PtrToStringUni(data.DnsDomainName.Buffer).Trim(); - string upn = Marshal.PtrToStringUni(data.Upn.Buffer).Trim(); - - // now we want to get the tickets for this logon ID - string name = "kerberos"; - LSA_STRING_IN LSAString; - LSAString.Length = (ushort)name.Length; - LSAString.MaximumLength = (ushort)(name.Length + 1); - LSAString.Buffer = name; - - IntPtr responsePointer = IntPtr.Zero; - int authPack; - int returnBufferLength = 0; - int protocalStatus = 0; - int retCode; - - KERB_RETRIEVE_TKT_REQUEST tQuery = new KERB_RETRIEVE_TKT_REQUEST(); - KERB_RETRIEVE_TKT_RESPONSE response = new KERB_RETRIEVE_TKT_RESPONSE(); - - // obtains the unique identifier for the kerberos authentication package. - retCode = LsaLookupAuthenticationPackage(hLsa, ref LSAString, out authPack); - - // input object for querying the TGT for a specific logon ID (https://docs.microsoft.com/en-us/windows/desktop/api/ntsecapi/ns-ntsecapi-_kerb_retrieve_tkt_request) - LUID userLogonID = new LUID(); - userLogonID.LowPart = data.LoginID.LowPart; - userLogonID.HighPart = 0; - tQuery.LogonId = userLogonID; - tQuery.MessageType = KERB_PROTOCOL_MESSAGE_TYPE.KerbRetrieveTicketMessage; - // indicate we want kerb creds yo' - tQuery.CacheOptions = KERB_CACHE_OPTIONS.KERB_RETRIEVE_TICKET_AS_KERB_CRED; - - // query LSA, specifying we want the the TGT data - retCode = LsaCallAuthenticationPackage_KERB_RETRIEVE_TKT(hLsa, authPack, ref tQuery, Marshal.SizeOf(tQuery), out responsePointer, out returnBufferLength, out protocalStatus); - - if ((retCode) == 0 && (responsePointer != IntPtr.Zero)) - { - Console.WriteLine("\r\n UserName : {0}", username); - Console.WriteLine(" Domain : {0}", domain); - Console.WriteLine(" LogonId : {0}", data.LoginID.LowPart); - Console.WriteLine(" UserSID : {0}", sid.AccountDomainSid); - Console.WriteLine(" AuthenticationPackage : {0}", authpackage); - Console.WriteLine(" LogonType : {0}", logonType); - Console.WriteLine(" LogonType : {0}", logonTime); - Console.WriteLine(" LogonServer : {0}", logonServer); - Console.WriteLine(" LogonServerDNSDomain : {0}", dnsDomainName); - Console.WriteLine(" UserPrincipalName : {0}", upn); - - // parse the returned pointer into our initial KERB_RETRIEVE_TKT_RESPONSE structure - response = (KERB_RETRIEVE_TKT_RESPONSE)Marshal.PtrToStructure((System.IntPtr)responsePointer, typeof(KERB_RETRIEVE_TKT_RESPONSE)); - - KERB_EXTERNAL_NAME serviceNameStruct = (KERB_EXTERNAL_NAME)Marshal.PtrToStructure(response.Ticket.ServiceName, typeof(KERB_EXTERNAL_NAME)); - string serviceName = Marshal.PtrToStringUni(serviceNameStruct.Names.Buffer, serviceNameStruct.Names.Length / 2).Trim(); - - string targetName = ""; - if (response.Ticket.TargetName != IntPtr.Zero) - { - KERB_EXTERNAL_NAME targetNameStruct = (KERB_EXTERNAL_NAME)Marshal.PtrToStructure(response.Ticket.TargetName, typeof(KERB_EXTERNAL_NAME)); - targetName = Marshal.PtrToStringUni(targetNameStruct.Names.Buffer, targetNameStruct.Names.Length / 2).Trim(); - } - - KERB_EXTERNAL_NAME clientNameStruct = (KERB_EXTERNAL_NAME)Marshal.PtrToStructure(response.Ticket.ClientName, typeof(KERB_EXTERNAL_NAME)); - string clientName = Marshal.PtrToStringUni(clientNameStruct.Names.Buffer, clientNameStruct.Names.Length / 2).Trim(); - - string domainName = Marshal.PtrToStringUni(response.Ticket.DomainName.Buffer, response.Ticket.DomainName.Length / 2).Trim(); - string targetDomainName = Marshal.PtrToStringUni(response.Ticket.TargetDomainName.Buffer, response.Ticket.TargetDomainName.Length / 2).Trim(); - string altTargetDomainName = Marshal.PtrToStringUni(response.Ticket.AltTargetDomainName.Buffer, response.Ticket.AltTargetDomainName.Length / 2).Trim(); - - // extract the session key - KERB_ENCRYPTION_TYPE sessionKeyType = (KERB_ENCRYPTION_TYPE)response.Ticket.SessionKey.KeyType; - Int32 sessionKeyLength = response.Ticket.SessionKey.Length; - byte[] sessionKey = new byte[sessionKeyLength]; - Marshal.Copy(response.Ticket.SessionKey.Value, sessionKey, 0, sessionKeyLength); - string base64SessionKey = Convert.ToBase64String(sessionKey); - - DateTime keyExpirationTime = DateTime.FromFileTime(response.Ticket.KeyExpirationTime); - DateTime startTime = DateTime.FromFileTime(response.Ticket.StartTime); - DateTime endTime = DateTime.FromFileTime(response.Ticket.EndTime); - DateTime renewUntil = DateTime.FromFileTime(response.Ticket.RenewUntil); - Int64 timeSkew = response.Ticket.TimeSkew; - Int32 encodedTicketSize = response.Ticket.EncodedTicketSize; - - string ticketFlags = ((KERB_TICKET_FLAGS)response.Ticket.TicketFlags).ToString(); - - // extract the TGT and base64 encode it - byte[] encodedTicket = new byte[encodedTicketSize]; - Marshal.Copy(response.Ticket.EncodedTicket, encodedTicket, 0, encodedTicketSize); - string base64TGT = Convert.ToBase64String(encodedTicket); - - Console.WriteLine(" ServiceName : {0}", serviceName); - Console.WriteLine(" TargetName : {0}", targetName); - Console.WriteLine(" ClientName : {0}", clientName); - Console.WriteLine(" DomainName : {0}", domainName); - Console.WriteLine(" TargetDomainName : {0}", targetDomainName); - Console.WriteLine(" AltTargetDomainName : {0}", altTargetDomainName); - Console.WriteLine(" SessionKeyType : {0}", sessionKeyType); - Console.WriteLine(" Base64SessionKey : {0}", base64SessionKey); - Console.WriteLine(" KeyExpirationTime : {0}", keyExpirationTime); - Console.WriteLine(" TicketFlags : {0}", ticketFlags); - Console.WriteLine(" StartTime : {0}", startTime); - Console.WriteLine(" EndTime : {0}", endTime); - Console.WriteLine(" RenewUntil : {0}", renewUntil); - Console.WriteLine(" TimeSkew : {0}", timeSkew); - Console.WriteLine(" EncodedTicketSize : {0}", encodedTicketSize); - Console.WriteLine(" Base64EncodedTicket :\r\n"); - // display the TGT, columns of 100 chararacters - foreach (string line in Split(base64TGT, 100)) - { - Console.WriteLine(" {0}", line); - } - Console.WriteLine(); - totalTicketCount++; - } - } - luidPtr = (IntPtr)((long)luidPtr.ToInt64() + Marshal.SizeOf(typeof(LUID))); - //move the pointer forward - LsaFreeReturnBuffer(sessionData); - //free the SECURITY_LOGON_SESSION_DATA memory in the struct - } - LsaFreeReturnBuffer(luidPtr); //free the array of LUIDs - - // disconnect from LSA - LsaDeregisterLogonProcess(hLsa); - - Console.WriteLine("\r\n\r\n [*] Extracted {0} total tickets\r\n", totalTicketCount); - } - catch (Exception ex) - { - Console.WriteLine(" [X] Exception: {0}", ex); - } - } - public static void ListKerberosTGTDataCurrentUser() - { - // adapted partially from Vincent LE TOUX' work - // https://github.com/vletoux/MakeMeEnterpriseAdmin/blob/master/MakeMeEnterpriseAdmin.ps1#L2939-L2950 - // and https://www.dreamincode.net/forums/topic/135033-increment-memory-pointer-issue/ - // also Jared Atkinson's work at https://github.com/Invoke-IR/ACE/blob/master/ACE-Management/PS-ACE/Scripts/ACE_Get-KerberosTicketCache.ps1 - - Console.WriteLine("\r\n\r\n=== Kerberos TGT Data (Current User) ===\r\n"); - - try - { - string name = "kerberos"; - LSA_STRING_IN LSAString; - LSAString.Length = (ushort)name.Length; - LSAString.MaximumLength = (ushort)(name.Length + 1); - LSAString.Buffer = name; - - IntPtr responsePointer = IntPtr.Zero; - int authPack; - int returnBufferLength = 0; - int protocalStatus = 0; - IntPtr lsaHandle; - int retCode; - - // If we want to look at tickets from a session other than our own - // then we need to use LsaRegisterLogonProcess instead of LsaConnectUntrusted - retCode = LsaConnectUntrusted(out lsaHandle); - - KERB_RETRIEVE_TKT_REQUEST tQuery = new KERB_RETRIEVE_TKT_REQUEST(); - KERB_RETRIEVE_TKT_RESPONSE response = new KERB_RETRIEVE_TKT_RESPONSE(); - - // obtains the unique identifier for the kerberos authentication package. - retCode = LsaLookupAuthenticationPackage(lsaHandle, ref LSAString, out authPack); - - // input object for querying the TGT (https://docs.microsoft.com/en-us/windows/desktop/api/ntsecapi/ns-ntsecapi-_kerb_retrieve_tkt_request) - tQuery.LogonId = new LUID(); - tQuery.MessageType = KERB_PROTOCOL_MESSAGE_TYPE.KerbRetrieveTicketMessage; - // indicate we want kerb creds yo' - //tQuery.CacheOptions = KERB_CACHE_OPTIONS.KERB_RETRIEVE_TICKET_AS_KERB_CRED; - - // query LSA, specifying we want the the TGT data - retCode = LsaCallAuthenticationPackage_KERB_RETRIEVE_TKT(lsaHandle, authPack, ref tQuery, Marshal.SizeOf(tQuery), out responsePointer, out returnBufferLength, out protocalStatus); - - // parse the returned pointer into our initial KERB_RETRIEVE_TKT_RESPONSE structure - response = (KERB_RETRIEVE_TKT_RESPONSE)Marshal.PtrToStructure((System.IntPtr)responsePointer, typeof(KERB_RETRIEVE_TKT_RESPONSE)); - - KERB_EXTERNAL_NAME serviceNameStruct = (KERB_EXTERNAL_NAME)Marshal.PtrToStructure(response.Ticket.ServiceName, typeof(KERB_EXTERNAL_NAME)); - string serviceName = Marshal.PtrToStringUni(serviceNameStruct.Names.Buffer, serviceNameStruct.Names.Length / 2).Trim(); - - string targetName = ""; - if (response.Ticket.TargetName != IntPtr.Zero) - { - KERB_EXTERNAL_NAME targetNameStruct = (KERB_EXTERNAL_NAME)Marshal.PtrToStructure(response.Ticket.TargetName, typeof(KERB_EXTERNAL_NAME)); - targetName = Marshal.PtrToStringUni(targetNameStruct.Names.Buffer, targetNameStruct.Names.Length / 2).Trim(); - } - - KERB_EXTERNAL_NAME clientNameStruct = (KERB_EXTERNAL_NAME)Marshal.PtrToStructure(response.Ticket.ClientName, typeof(KERB_EXTERNAL_NAME)); - string clientName = Marshal.PtrToStringUni(clientNameStruct.Names.Buffer, clientNameStruct.Names.Length / 2).Trim(); - - string domainName = Marshal.PtrToStringUni(response.Ticket.DomainName.Buffer, response.Ticket.DomainName.Length / 2).Trim(); - string targetDomainName = Marshal.PtrToStringUni(response.Ticket.TargetDomainName.Buffer, response.Ticket.TargetDomainName.Length / 2).Trim(); - string altTargetDomainName = Marshal.PtrToStringUni(response.Ticket.AltTargetDomainName.Buffer, response.Ticket.AltTargetDomainName.Length / 2).Trim(); - - // extract the session key - KERB_ENCRYPTION_TYPE sessionKeyType = (KERB_ENCRYPTION_TYPE)response.Ticket.SessionKey.KeyType; - Int32 sessionKeyLength = response.Ticket.SessionKey.Length; - byte[] sessionKey = new byte[sessionKeyLength]; - Marshal.Copy(response.Ticket.SessionKey.Value, sessionKey, 0, sessionKeyLength); - string base64SessionKey = Convert.ToBase64String(sessionKey); - - DateTime keyExpirationTime = DateTime.FromFileTime(response.Ticket.KeyExpirationTime); - DateTime startTime = DateTime.FromFileTime(response.Ticket.StartTime); - DateTime endTime = DateTime.FromFileTime(response.Ticket.EndTime); - DateTime renewUntil = DateTime.FromFileTime(response.Ticket.RenewUntil); - Int64 timeSkew = response.Ticket.TimeSkew; - Int32 encodedTicketSize = response.Ticket.EncodedTicketSize; - - string ticketFlags = ((KERB_TICKET_FLAGS)response.Ticket.TicketFlags).ToString(); - - // extract the TGT and base64 encode it - byte[] encodedTicket = new byte[encodedTicketSize]; - Marshal.Copy(response.Ticket.EncodedTicket, encodedTicket, 0, encodedTicketSize); - string base64TGT = Convert.ToBase64String(encodedTicket); - - Console.WriteLine(" ServiceName : {0}", serviceName); - Console.WriteLine(" TargetName : {0}", targetName); - Console.WriteLine(" ClientName : {0}", clientName); - Console.WriteLine(" DomainName : {0}", domainName); - Console.WriteLine(" TargetDomainName : {0}", targetDomainName); - Console.WriteLine(" AltTargetDomainName : {0}", altTargetDomainName); - Console.WriteLine(" SessionKeyType : {0}", sessionKeyType); - Console.WriteLine(" Base64SessionKey : {0}", base64SessionKey); - Console.WriteLine(" KeyExpirationTime : {0}", keyExpirationTime); - Console.WriteLine(" TicketFlags : {0}", ticketFlags); - Console.WriteLine(" StartTime : {0}", startTime); - Console.WriteLine(" EndTime : {0}", endTime); - Console.WriteLine(" RenewUntil : {0}", renewUntil); - Console.WriteLine(" TimeSkew : {0}", timeSkew); - Console.WriteLine(" EncodedTicketSize : {0}", encodedTicketSize); - Console.WriteLine(" Base64EncodedTicket :\r\n"); - // display the TGT, columns of 100 chararacters - foreach (string line in Split(base64TGT, 100)) - { - Console.WriteLine(" {0}", line); - } - Console.WriteLine(); - - // disconnect from LSA - LsaDeregisterLogonProcess(lsaHandle); - } - catch (Exception ex) - { - Console.WriteLine(" [X] Exception: {0}", ex.Message); - } - } - - // https://github.com/pauldotknopf/WindowsSDK7-Samples/blob/master/security/authorization/klist/KList.c#L585 - // currently not working :( - //public static void ListKerberosTicketDataCurrentUser() - //{ - // // adapted partially from Vincent LE TOUX' work - // // https://github.com/vletoux/MakeMeEnterpriseAdmin/blob/master/MakeMeEnterpriseAdmin.ps1#L2939-L2950 - // // and https://www.dreamincode.net/forums/topic/135033-increment-memory-pointer-issue/ - // // also Jared Atkinson's work at https://github.com/Invoke-IR/ACE/blob/master/ACE-Management/PS-ACE/Scripts/ACE_Get-KerberosTicketCache.ps1 - - // Console.WriteLine("\r\n\r\n=== Kerberos Ticket Data (Current User) ===\r\n"); - - // //try - // //{ - // string name = "kerberos"; - // LSA_STRING_IN LSAString; - // LSAString.Length = (ushort)name.Length; - // LSAString.MaximumLength = (ushort)(name.Length + 1); - // LSAString.Buffer = name; - - // IntPtr ticketPointer = IntPtr.Zero; - // IntPtr ticketsPointer = IntPtr.Zero; - // int authPack; - // int returnBufferLength = 0; - // int protocalStatus = 0; - // IntPtr lsaHandle; - // int retCode; - - // // If we want to look at tickets from a session other than our own - // // then we need to use LsaRegisterLogonProcess instead of LsaConnectUntrusted - // retCode = LsaConnectUntrusted(out lsaHandle); - - // // obtains the unique identifier for the kerberos authentication package. - // retCode = LsaLookupAuthenticationPackage(lsaHandle, ref LSAString, out authPack); - - // UNICODE_STRING targetName = new UNICODE_STRING("krbtgt/TESTLAB.LOCAL"); - // UNICODE_STRING target = new UNICODE_STRING(); - - // KERB_RETRIEVE_TKT_RESPONSE CacheResponse = new KERB_RETRIEVE_TKT_RESPONSE(); - - // // LMEM_ZEROINIT -> 0x0040 - // IntPtr temp = LocalAlloc(0x0040, (uint)(targetName.Length + Marshal.SizeOf(typeof(KERB_RETRIEVE_TKT_REQUEST)))); - - // IntPtr unmanagedAddr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(KERB_RETRIEVE_TKT_REQUEST))); - // //Marshal.StructureToPtr(managedObj, unmanagedAddr, true); - // KERB_RETRIEVE_TKT_REQUEST_UNI CacheRequest = (KERB_RETRIEVE_TKT_REQUEST_UNI)Marshal.PtrToStructure(temp, typeof(KERB_RETRIEVE_TKT_REQUEST_UNI)); - // CacheRequest.MessageType = KERB_PROTOCOL_MESSAGE_TYPE.KerbRetrieveEncodedTicketMessage; - - // // KERB_RETRIEVE_TKT_REQUEST_UNI - // IntPtr CacheRequestPtr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(KERB_RETRIEVE_TKT_REQUEST))); - // Marshal.StructureToPtr(CacheRequest, CacheRequestPtr, false); - // target.buffer = (IntPtr)(CacheRequestPtr.ToInt64() + 1); - // target.Length = targetName.Length; - // target.MaximumLength = targetName.MaximumLength; - - // CopyMemory(target.buffer, targetName.buffer, targetName.Length); - - // CacheRequest.TargetName = target; - - // IntPtr responsePointer = IntPtr.Zero; - // int returnBufferLength2 = 0; - // // query LSA, specifying we want the the specified ticket data - // retCode = LsaCallAuthenticationPackage_KERB_RETRIEVE_TKT_UNI(lsaHandle, authPack, ref CacheRequest, Marshal.SizeOf(CacheRequest) + targetName.Length, out responsePointer, out returnBufferLength2, out protocalStatus); - // Console.WriteLine("LsaCallAuthenticationPackage_KERB_RETRIEVE_TKT_UNI retCode: {0}", retCode); - // Console.WriteLine("returnBufferLength: {0}", returnBufferLength2); - // Console.WriteLine("responsePointer: {0}\r\n", responsePointer); - // Console.WriteLine("protocalStatus: {0}\r\n", (uint)protocalStatus); - // Console.Out.Flush(); - - - // //string clientName = Marshal.PtrToStringUni(CacheResponse.Ticket.ClientName, CacheResponse.Ticket.ClientName.L / 2); - // DateTime startTime = DateTime.FromFileTime(CacheResponse.Ticket.StartTime); - // DateTime endTime = DateTime.FromFileTime(CacheResponse.Ticket.EndTime); - // Console.WriteLine("startTime: {0}", startTime); - // Console.WriteLine("endTime: {0}", endTime); - - // //// query LSA, specifying we want the ticket cache - // //retCode = LsaCallAuthenticationPackage(lsaHandle, authPack, ref tQuery, Marshal.SizeOf(tQuery), out ticketPointer, out returnBufferLength, out protocalStatus); - - // //// parse the returned pointer into our initial KERB_QUERY_TKT_CACHE_RESPONSE structure - // //tickets = (KERB_QUERY_TKT_CACHE_EX_RESPONSE)Marshal.PtrToStructure((System.IntPtr)ticketPointer, typeof(KERB_QUERY_TKT_CACHE_EX_RESPONSE)); - // //int count = tickets.CountOfTickets; - // //Console.WriteLine(" [*] Returned {0} tickets\r\n", count); - - // //// get the size of the structures we're iterating over - // //Int32 dataSize = Marshal.SizeOf(typeof(KERB_TICKET_CACHE_INFO_EX)); - - // //for (int i = 0; i < count; i++) - // //{ - // // // iterate through the structures - // // IntPtr currTicketPtr = (IntPtr)(long)((ticketPointer.ToInt64() + (int)(8 + i * dataSize))); - - // // // parse the new ptr to the appropriate structure - // // ticket = (KERB_TICKET_CACHE_INFO_EX)Marshal.PtrToStructure(currTicketPtr, typeof(KERB_TICKET_CACHE_INFO_EX)); - - // // // extract our fields - // // string clientName = Marshal.PtrToStringUni(ticket.ClientName.Buffer, ticket.ClientName.Length / 2); - // // string clientRealm = Marshal.PtrToStringUni(ticket.ClientRealm.Buffer, ticket.ClientRealm.Length / 2); - // // string serverName = Marshal.PtrToStringUni(ticket.ServerName.Buffer, ticket.ServerName.Length / 2); - // // string serverRealm = Marshal.PtrToStringUni(ticket.ServerRealm.Buffer, ticket.ServerRealm.Length / 2); - // // Console.WriteLine("clientName: {0}", clientName); - // // Console.WriteLine("clientRealm: {0}", clientRealm); - // // Console.WriteLine("serverName: {0}", serverName); - // // Console.WriteLine("serverRealm: {0}", serverRealm); - // // DateTime startTime = DateTime.FromFileTime(ticket.StartTime); - // // DateTime endTime = DateTime.FromFileTime(ticket.EndTime); - // // DateTime renewTime = DateTime.FromFileTime(ticket.RenewTime); - // // string encryptionType = ((KERB_ENCRYPTION_TYPE)ticket.EncryptionType).ToString(); - // // string ticketFlags = ((KERB_TICKET_FLAGS)ticket.TicketFlags).ToString(); - - // //KERB_RETRIEVE_TKT_REQUEST ticketQuery = new KERB_RETRIEVE_TKT_REQUEST(); - // //KERB_RETRIEVE_TKT_RESPONSE response = new KERB_RETRIEVE_TKT_RESPONSE(); - - // //// input object for querying the ticket cache - // ////ticketQuery.LogonId = new LUID(); - // //ticketQuery.MessageType = KERB_PROTOCOL_MESSAGE_TYPE.KerbRetrieveEncodedTicketMessage; - // //// indicate we want kerb creds yo' - // //ticketQuery.CacheOptions = KERB_CACHE_OPTIONS.KERB_RETRIEVE_TICKET_AS_KERB_CRED; - // //ticketQuery.TicketFlags = ticket.TicketFlags; - // ////ticketQuery.TargetName = ticket.ServerName; - - // //string targetName2 = "krbtgt/TESTLAB.LOCAL"; - // //LSA_STRING_IN LSAString2; - // //LSAString2.Length = (ushort)targetName2.Length; - // //LSAString2.MaximumLength = (ushort)(targetName2.Length + 1); - // //LSAString2.Buffer = targetName2; - // //ticketQuery.TargetName = LSAString2; - - // //Console.WriteLine("flags: {0}\r\n", ticket.TicketFlags.ToString("X2")); - - // //IntPtr responsePointer = IntPtr.Zero; - // //int returnBufferLength2 = 0; - // //// query LSA, specifying we want the the specified ticket data - // //retCode = LsaCallAuthenticationPackage_KERB_RETRIEVE_TKT(lsaHandle, authPack, ref ticketQuery, Marshal.SizeOf(ticketQuery), out responsePointer, out returnBufferLength2, out protocalStatus); - // //Console.WriteLine("LsaCallAuthenticationPackage_KERB_RETRIEVE_TKT retCode: {0}", retCode); - // //Console.WriteLine("returnBufferLength: {0}", returnBufferLength2); - // //Console.WriteLine("responsePointer: {0}\r\n", responsePointer); - // //// parse the returned pointer into our initial KERB_RETRIEVE_TKT_RESPONSE structure - - // //response = (KERB_RETRIEVE_TKT_RESPONSE)Marshal.PtrToStructure((System.IntPtr)responsePointer, typeof(KERB_RETRIEVE_TKT_RESPONSE)); - - // //KERB_EXTERNAL_NAME serviceNameStruct = (KERB_EXTERNAL_NAME)Marshal.PtrToStructure(response.Ticket.ServiceName, typeof(KERB_EXTERNAL_NAME)); - // //string serviceName = Marshal.PtrToStringUni(serviceNameStruct.Names.Buffer, serviceNameStruct.Names.Length / 2).Trim(); - - // //string targetName = ""; - // //if (response.Ticket.TargetName != IntPtr.Zero) - // //{ - // // KERB_EXTERNAL_NAME targetNameStruct = (KERB_EXTERNAL_NAME)Marshal.PtrToStructure(response.Ticket.TargetName, typeof(KERB_EXTERNAL_NAME)); - // // targetName = Marshal.PtrToStringUni(targetNameStruct.Names.Buffer, targetNameStruct.Names.Length / 2).Trim(); - // //} - - // //KERB_EXTERNAL_NAME clientNameStruct = (KERB_EXTERNAL_NAME)Marshal.PtrToStructure(response.Ticket.ClientName, typeof(KERB_EXTERNAL_NAME)); - // ////string clientName = Marshal.PtrToStringUni(clientNameStruct.Names.Buffer, clientNameStruct.Names.Length / 2).Trim(); - - // //string domainName = Marshal.PtrToStringUni(response.Ticket.DomainName.Buffer, response.Ticket.DomainName.Length / 2).Trim(); - // //string targetDomainName = Marshal.PtrToStringUni(response.Ticket.TargetDomainName.Buffer, response.Ticket.TargetDomainName.Length / 2).Trim(); - // //string altTargetDomainName = Marshal.PtrToStringUni(response.Ticket.AltTargetDomainName.Buffer, response.Ticket.AltTargetDomainName.Length / 2).Trim(); - - // //// extract the session key - // //KERB_ENCRYPTION_TYPE sessionKeyType = (KERB_ENCRYPTION_TYPE)response.Ticket.SessionKey.KeyType; - // //Int32 sessionKeyLength = response.Ticket.SessionKey.Length; - // //byte[] sessionKey = new byte[sessionKeyLength]; - // //Marshal.Copy(response.Ticket.SessionKey.Value, sessionKey, 0, sessionKeyLength); - // //string base64SessionKey = Convert.ToBase64String(sessionKey); - - // //DateTime keyExpirationTime = DateTime.FromFileTime(response.Ticket.KeyExpirationTime); - // //DateTime startTime2 = DateTime.FromFileTime(response.Ticket.StartTime); - // //DateTime endTime2 = DateTime.FromFileTime(response.Ticket.EndTime); - // //DateTime renewUntil = DateTime.FromFileTime(response.Ticket.RenewUntil); - // //Int64 timeSkew = response.Ticket.TimeSkew; - // //Int32 encodedTicketSize = response.Ticket.EncodedTicketSize; - - // //string ticketFlags2 = ((KERB_TICKET_FLAGS)response.Ticket.TicketFlags).ToString(); - - // //// extract the ticket and base64 encode it - // //byte[] encodedTicket = new byte[encodedTicketSize]; - // //Marshal.Copy(response.Ticket.EncodedTicket, encodedTicket, 0, encodedTicketSize); - // //string base64Ticket = Convert.ToBase64String(encodedTicket); - - // //Console.WriteLine(" ServiceName : {0}", serviceName); - // //Console.WriteLine(" TargetName : {0}", targetName); - // //Console.WriteLine(" ClientName : {0}", clientName); - // //Console.WriteLine(" DomainName : {0}", domainName); - // //Console.WriteLine(" TargetDomainName : {0}", targetDomainName); - // //Console.WriteLine(" AltTargetDomainName : {0}", altTargetDomainName); - // //Console.WriteLine(" SessionKeyType : {0}", sessionKeyType); - // //Console.WriteLine(" Base64SessionKey : {0}", base64SessionKey); - // //Console.WriteLine(" KeyExpirationTime : {0}", keyExpirationTime); - // //Console.WriteLine(" TicketFlags : {0}", ticketFlags2); - // //Console.WriteLine(" StartTime : {0}", startTime2); - // //Console.WriteLine(" EndTime : {0}", endTime2); - // //Console.WriteLine(" RenewUntil : {0}", renewUntil); - // //Console.WriteLine(" EncodedTicketSize : {0}", encodedTicketSize); - // //Console.WriteLine(" Base64EncodedTicket :\r\n"); - // //// display the TGT, columns of 80 chararacters - // //foreach (string line in Split(base64Ticket, 80)) - // //{ - // // Console.WriteLine(" {0}", line); - // //} - // //Console.WriteLine(); - // //} - - // // disconnect from LSA - // LsaDeregisterLogonProcess(lsaHandle); - // //} - // //catch (Exception ex) - // //{ - // // Console.WriteLine(" [X] Exception: {0}", ex.Message); - // //} - //} - - - public static void ListLogonSessions() - { - if (!IsHighIntegrity()) - { - // https://www.pinvoke.net/default.aspx/secur32.lsalogonuser - - // list user logons combined with logon session data via WMI - - Regex userDomainRegex = new Regex(@"Domain=""(.*)"",Name=""(.*)"""); - Regex logonIdRegex = new Regex(@"LogonId=""(\d+)"""); - - Console.WriteLine("\r\n\r\n=== Logon Sessions (via WMI) ===\r\n\r\n"); - - Dictionary logonMap = new Dictionary(); - - try - { - ManagementObjectSearcher wmiData = new ManagementObjectSearcher(@"root\cimv2", "SELECT * FROM Win32_LoggedOnUser"); - ManagementObjectCollection data = wmiData.Get(); - - foreach (ManagementObject result in data) - { - Match m = logonIdRegex.Match(result["Dependent"].ToString()); - if (m.Success) - { - string logonId = m.Groups[1].ToString(); - Match m2 = userDomainRegex.Match(result["Antecedent"].ToString()); - if (m2.Success) - { - string domain = m2.Groups[1].ToString(); - string user = m2.Groups[2].ToString(); - logonMap.Add(logonId, new string[] { domain, user }); - } - } - } - - ManagementObjectSearcher wmiData2 = new ManagementObjectSearcher(@"root\cimv2", "SELECT * FROM Win32_LogonSession"); - ManagementObjectCollection data2 = wmiData2.Get(); - - foreach (ManagementObject result2 in data2) - { - string[] userDomain = logonMap[result2["LogonId"].ToString()]; - string domain = userDomain[0]; - string userName = userDomain[1]; - System.DateTime startTime = System.Management.ManagementDateTimeConverter.ToDateTime(result2["StartTime"].ToString()); - - string logonType = String.Format("{0}", ((SECURITY_LOGON_TYPE)(Int32.Parse(result2["LogonType"].ToString())))); - - Console.WriteLine(" UserName : {0}", userName); - Console.WriteLine(" Domain : {0}", domain); - Console.WriteLine(" LogonId : {0}", result2["LogonId"].ToString()); - Console.WriteLine(" LogonType : {0}", logonType); - Console.WriteLine(" AuthenticationPackage : {0}", result2["AuthenticationPackage"].ToString()); - Console.WriteLine(" StartTime : {0}\r\n", startTime); - } - } - catch (Exception ex) - { - Console.WriteLine(" [X] Exception: {0}", ex.Message); - } - } - else - { - // heavily adapted from from Jared Hill: - // https://www.codeproject.com/Articles/18179/Using-the-Local-Security-Authority-to-Enumerate-Us - - Console.WriteLine("\r\n\r\n=== Logon Sessions (via LSA) ===\r\n\r\n"); - - try - { - DateTime systime = new DateTime(1601, 1, 1, 0, 0, 0, 0); //win32 systemdate - UInt64 count; - IntPtr luidPtr = IntPtr.Zero; - IntPtr iter = luidPtr; - - uint ret = LsaEnumerateLogonSessions(out count, out luidPtr); // get an array of pointers to LUIDs - - for (ulong i = 0; i < count; i++) - { - IntPtr sessionData; - - ret = LsaGetLogonSessionData(luidPtr, out sessionData); - SECURITY_LOGON_SESSION_DATA data = (SECURITY_LOGON_SESSION_DATA)Marshal.PtrToStructure(sessionData, typeof(SECURITY_LOGON_SESSION_DATA)); - - // if we have a valid logon - if (data.PSiD != IntPtr.Zero) - { - // get the account username - string username = Marshal.PtrToStringUni(data.Username.Buffer).Trim(); - - // convert the security identifier of the user - System.Security.Principal.SecurityIdentifier sid = new System.Security.Principal.SecurityIdentifier(data.PSiD); - - // domain for this account - string domain = Marshal.PtrToStringUni(data.LoginDomain.Buffer).Trim(); - - // authentication package - string authpackage = Marshal.PtrToStringUni(data.AuthenticationPackage.Buffer).Trim(); - - // logon type - SECURITY_LOGON_TYPE logonType = (SECURITY_LOGON_TYPE)data.LogonType; - - // datetime the session was logged in - DateTime logonTime = systime.AddTicks((long)data.LoginTime); - - // user's logon server - string logonServer = Marshal.PtrToStringUni(data.LogonServer.Buffer).Trim(); - - // logon server's DNS domain - string dnsDomainName = Marshal.PtrToStringUni(data.DnsDomainName.Buffer).Trim(); - - // user principalname - string upn = Marshal.PtrToStringUni(data.Upn.Buffer).Trim(); - - Console.WriteLine(" UserName : {0}", username); - Console.WriteLine(" Domain : {0}", domain); - Console.WriteLine(" LogonId : {0}", data.LoginID.LowPart); - Console.WriteLine(" UserSID : {0}", sid.AccountDomainSid); - Console.WriteLine(" AuthenticationPackage : {0}", authpackage); - Console.WriteLine(" LogonType : {0}", logonType); - Console.WriteLine(" LogonType : {0}", logonTime); - Console.WriteLine(" LogonServer : {0}", logonServer); - Console.WriteLine(" LogonServerDNSDomain : {0}", dnsDomainName); - Console.WriteLine(" UserPrincipalName : {0}\r\n", upn); - } - // move the pointer forward - luidPtr = (IntPtr)((long)luidPtr.ToInt64() + Marshal.SizeOf(typeof(LUID))); - LsaFreeReturnBuffer(sessionData); - } - LsaFreeReturnBuffer(luidPtr); - } - catch (Exception ex) - { - Console.WriteLine(" [X] Exception: {0}", ex); - } - } - } - - public static void ListAuditSettings() - { - Console.WriteLine("\r\n\r\n=== Audit Settings ===\r\n"); - Dictionary settings = GetRegValues("HKLM", "Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\System\\Audit"); - if ((settings != null) && (settings.Count != 0)) - { - foreach (KeyValuePair kvp in settings) - { - if (kvp.Value.GetType().IsArray && (kvp.Value.GetType().GetElementType().ToString() == "System.String")) - { - string result = string.Join(",", (string[])kvp.Value); - Console.WriteLine(" {0,-30} : {1}", kvp.Key, result); - } - else - { - Console.WriteLine(" {0,-30} : {1}", kvp.Key, kvp.Value); - } - } - } - } - - public static void ListWEFSettings() - { - Console.WriteLine("\r\n\r\n=== WEF Settings ===\r\n"); - Dictionary settings = GetRegValues("HKLM", "Software\\Policies\\Microsoft\\Windows\\EventLog\\EventForwarding\\SubscriptionManager"); - if ((settings != null) && (settings.Count != 0)) - { - foreach (KeyValuePair kvp in settings) - { - if (kvp.Value.GetType().IsArray && (kvp.Value.GetType().GetElementType().ToString() == "System.String")) - { - string result = string.Join(",", (string[])kvp.Value); - Console.WriteLine(" {0,-30} : {1}", kvp.Key, result); - } - else - { - Console.WriteLine(" {0,-30} : {1}", kvp.Key, kvp.Value); - } - } - } - } - - public static void ListLapsSettings() - { - Console.WriteLine("\r\n\r\n=== LAPS Settings ===\r\n"); - - string AdmPwdEnabled = GetRegValue("HKLM", "Software\\Policies\\Microsoft Services\\AdmPwd", "AdmPwdEnabled"); - - if (AdmPwdEnabled != "") - { - Console.WriteLine(" {0,-37} : {1}", "LAPS Enabled", AdmPwdEnabled); - - string LAPSAdminAccountName = GetRegValue("HKLM", "Software\\Policies\\Microsoft Services\\AdmPwd", "AdminAccountName"); - Console.WriteLine(" {0,-37} : {1}", "LAPS Admin Account Name", LAPSAdminAccountName); - - string LAPSPasswordComplexity = GetRegValue("HKLM", "Software\\Policies\\Microsoft Services\\AdmPwd", "PasswordComplexity"); - Console.WriteLine(" {0,-37} : {1}", "LAPS Password Complexity", LAPSPasswordComplexity); - - string LAPSPasswordLength = GetRegValue("HKLM", "Software\\Policies\\Microsoft Services\\AdmPwd", "PasswordLength"); - Console.WriteLine(" {0,-37} : {1}", "LAPS Password Length", LAPSPasswordLength); - - string LASPwdExpirationProtectionEnabled = GetRegValue("HKLM", "Software\\Policies\\Microsoft Services\\AdmPwd", "PwdExpirationProtectionEnabled"); - Console.WriteLine(" {0,-37} : {1}", "LAPS Expiration Protection Enabled", LASPwdExpirationProtectionEnabled); - } - else - { - Console.WriteLine(" [*] LAPS not installed"); - } - } - - public static void ListLocalGroupMembers() - { - // adapted from https://stackoverflow.com/questions/33935825/pinvoke-netlocalgroupgetmembers-runs-into-fatalexecutionengineerror/33939889#33939889 - - try - { - Console.WriteLine("\r\n\r\n=== Local Group Memberships ===\r\n"); - - // localization for @cnotin ;) - string[] groupsSIDs = { - "S-1-5-32-544", // Administrators - "S-1-5-32-555", // RDP - "S-1-5-32-562", // COM - "S-1-5-32-580" // Remote Management - }; - - foreach (string sid in groupsSIDs) - { - string groupNameFull = TranslateSid(sid); - if (string.IsNullOrEmpty(groupNameFull)) - { - // e.g. "S-1-5-32-580" for "Remote Management Users" can be missing on older versions of Windows - Console.WriteLine(" [X] Cannot find SID translation for '{0}'", sid); - continue; - } - - string groupName = groupNameFull.Substring(groupNameFull.IndexOf('\\') + 1); - Console.WriteLine(" * {0} *\r\n", groupName); - string[] members = GetLocalGroupMembers(groupName); - if (members != null) - { - foreach (string member in members) - { - Console.WriteLine(" {0}", member); - } - } - - Console.WriteLine(""); - } - } - catch (Exception ex) - { - Console.WriteLine(" [X] Exception: {0}", ex.Message); - } - } - - public static void ListMappedDrives() - { - try - { - Console.WriteLine("\r\n\r\n=== Drive Information (via .NET) ===\r\n"); - - // grab all drive letters - DriveInfo[] driveInfos = DriveInfo.GetDrives(); - - Console.WriteLine(" {0,-10} {1}", "Drive", "Mapped Location"); - - foreach (DriveInfo driveInfo in driveInfos) - { - // try to resolve each drive to a UNC mapped location - string path = GetUNCPath(driveInfo.Name); - - Console.WriteLine(" {0,-10} : {1}", driveInfo.Name, path); - } - } - catch (Exception ex) - { - Console.WriteLine(" [X] Exception: {0}", ex.Message); - } - } - - public static void ListWMIMappedDrives() - { - try - { - ManagementObjectSearcher wmiData = new ManagementObjectSearcher(@"root\cimv2", "SELECT * FROM win32_networkconnection"); - ManagementObjectCollection data = wmiData.Get(); - - Console.WriteLine("\r\n\r\n=== Mapped Drives (via WMI) ===\r\n"); - - foreach (ManagementObject result in data) - { - Console.WriteLine(" LocalName : {0}", result["LocalName"]); - Console.WriteLine(" RemoteName : {0}", result["RemoteName"]); - Console.WriteLine(" RemotePath : {0}", result["RemotePath"]); - Console.WriteLine(" Status : {0}", result["Status"]); - Console.WriteLine(" ConnectionState : {0}", result["ConnectionState"]); - Console.WriteLine(" Persistent : {0}", result["Persistent"]); - Console.WriteLine(" UserName : {0}", result["UserName"]); - Console.WriteLine(" Description : {0}\r\n", result["Description"]); - } - } - catch (Exception ex) - { - Console.WriteLine(" [X] Exception: {0}", ex.Message); - } - } - - public static void ListNetworkShares() - { - // lists current network shares for this system via WMI - - try - { - ManagementObjectSearcher wmiData = new ManagementObjectSearcher(@"root\cimv2", "SELECT * FROM Win32_Share"); - ManagementObjectCollection data = wmiData.Get(); - - Console.WriteLine("\r\n\r\n=== Network Shares (via WMI) ===\r\n"); - - foreach (ManagementObject result in data) - { - Console.WriteLine(" Name : {0}", result["Name"]); - Console.WriteLine(" Path : {0}", result["Path"]); - Console.WriteLine(" Description : {0}\r\n", result["Description"]); - } - } - catch (Exception ex) - { - Console.WriteLine(" [X] Exception: {0}", ex.Message); - } - } - - public static void ListAntiVirusWMI() - { - // lists installed VA products via WMI (the AntiVirusProduct class) - - try - { - ManagementObjectSearcher wmiData = new ManagementObjectSearcher(@"root\SecurityCenter2", "SELECT * FROM AntiVirusProduct"); - ManagementObjectCollection data = wmiData.Get(); - - Console.WriteLine("\r\n\r\n=== Registered Antivirus (via WMI) ===\r\n"); - - foreach (ManagementObject virusChecker in data) - { - Console.WriteLine(" Engine : {0}", virusChecker["displayName"]); - Console.WriteLine(" ProductEXE : {0}", virusChecker["pathToSignedProductExe"]); - Console.WriteLine(" ReportingEXE : {0}\r\n", virusChecker["pathToSignedReportingExe"]); - } - } - catch (Exception ex) - { - Console.WriteLine(" [X] Exception: {0}", ex.Message); - } - } - - public static void ListInterestingProcesses() - { - // TODO: check out https://github.com/harleyQu1nn/AggressorScripts/blob/master/ProcessColor.cna#L10 - - // from https://github.com/threatexpress/red-team-scripts/blob/master/HostEnum.ps1#L985-L1033 - Hashtable defensiveProcesses = new Hashtable() - { - {"mcshield.exe" , "McAfee AV"}, - {"windefend.exe" , "Windows Defender AV"}, - {"MSASCui.exe" , "Windows Defender AV"}, - {"MSASCuiL.exe" , "Windows Defender AV"}, - {"msmpeng.exe" , "Windows Defender AV"}, - {"msmpsvc.exe" , "Windows Defender AV"}, - {"WRSA.exe" , "WebRoot AV"}, - {"savservice.exe" , "Sophos AV"}, - {"TMCCSF.exe" , "Trend Micro AV"}, - {"symantec antivirus.exe" , "Symantec AV"}, - {"mbae.exe" , "MalwareBytes Anti-Exploit"}, - {"parity.exe" , "Bit9 application whitelisting"}, - {"cb.exe" , "Carbon Black behavioral analysis"}, - {"bds-vision.exe" , "BDS Vision behavioral analysis"}, - {"Triumfant.exe" , "Triumfant behavioral analysis"}, - {"CSFalcon.exe" , "CrowdStrike Falcon EDR"}, - {"ossec.exe" , "OSSEC intrusion detection"}, - {"TmPfw.exe" , "Trend Micro firewall"}, - {"dgagent.exe" , "Verdasys Digital Guardian DLP"}, - {"kvoop.exe" , "Unknown DLP process" }, - {"AAWTray.exe" , "UNKNOWN"}, - {"ackwin32.exe" , "UNKNOWN"}, - {"Ad-Aware.exe" , "UNKNOWN"}, - {"adaware.exe" , "UNKNOWN"}, - {"advxdwin.exe" , "UNKNOWN"}, - {"agentsvr.exe" , "UNKNOWN"}, - {"agentw.exe" , "UNKNOWN"}, - {"alertsvc.exe" , "UNKNOWN"}, - {"alevir.exe" , "UNKNOWN"}, - {"alogserv.exe" , "UNKNOWN"}, - {"amon9x.exe" , "UNKNOWN"}, - {"anti-trojan.exe" , "UNKNOWN"}, - {"antivirus.exe" , "UNKNOWN"}, - {"ants.exe" , "UNKNOWN"}, - {"apimonitor.exe" , "UNKNOWN"}, - {"aplica32.exe" , "UNKNOWN"}, - {"apvxdwin.exe" , "UNKNOWN"}, - {"arr.exe" , "UNKNOWN"}, - {"atcon.exe" , "UNKNOWN"}, - {"atguard.exe" , "UNKNOWN"}, - {"atro55en.exe" , "UNKNOWN"}, - {"atupdater.exe" , "UNKNOWN"}, - {"atwatch.exe" , "UNKNOWN"}, - {"au.exe" , "UNKNOWN"}, - {"aupdate.exe" , "UNKNOWN"}, - {"auto-protect.nav80try.exe", "UNKNOWN"}, - {"autodown.exe" , "UNKNOWN"}, - {"autoruns.exe" , "UNKNOWN"}, - {"autorunsc.exe" , "UNKNOWN"}, - {"autotrace.exe" , "UNKNOWN"}, - {"autoupdate.exe" , "UNKNOWN"}, - {"avconsol.exe" , "UNKNOWN"}, - {"ave32.exe" , "UNKNOWN"}, - {"avgcc32.exe" , "UNKNOWN"}, - {"avgctrl.exe" , "UNKNOWN"}, - {"avgemc.exe" , "UNKNOWN"}, - {"avgnt.exe" , "UNKNOWN"}, - {"avgrsx.exe" , "UNKNOWN"}, - {"avgserv.exe" , "UNKNOWN"}, - {"avgserv9.exe" , "UNKNOWN"}, - {"avguard.exe" , "UNKNOWN"}, - {"avgwdsvc.exe" , "UNKNOWN"}, - {"avgui.exe" , "UNKNOWN"}, - {"avgw.exe" , "UNKNOWN"}, - {"avkpop.exe" , "UNKNOWN"}, - {"avkserv.exe" , "UNKNOWN"}, - {"avkservice.exe" , "UNKNOWN"}, - {"avkwctl9.exe" , "UNKNOWN"}, - {"avltmain.exe" , "UNKNOWN"}, - {"avnt.exe" , "UNKNOWN"}, - {"avp.exe" , "UNKNOWN"}, - {"avp32.exe" , "UNKNOWN"}, - {"avpcc.exe" , "UNKNOWN"}, - {"avpdos32.exe" , "UNKNOWN"}, - {"avpm.exe" , "UNKNOWN"}, - {"avptc32.exe" , "UNKNOWN"}, - {"avpupd.exe" , "UNKNOWN"}, - {"avsched32.exe" , "UNKNOWN"}, - {"avsynmgr.exe" , "UNKNOWN"}, - {"avwin.exe" , "UNKNOWN"}, - {"avwin95.exe" , "UNKNOWN"}, - {"avwinnt.exe" , "UNKNOWN"}, - {"avwupd.exe" , "UNKNOWN"}, - {"avwupd32.exe" , "UNKNOWN"}, - {"avwupsrv.exe" , "UNKNOWN"}, - {"avxmonitor9x.exe" , "UNKNOWN"}, - {"avxmonitornt.exe" , "UNKNOWN"}, - {"avxquar.exe" , "UNKNOWN"}, - {"backweb.exe" , "UNKNOWN"}, - {"bargains.exe" , "UNKNOWN"}, - {"bd_professional.exe" , "UNKNOWN"}, - {"beagle.exe" , "UNKNOWN"}, - {"belt.exe" , "UNKNOWN"}, - {"bidef.exe" , "UNKNOWN"}, - {"bidserver.exe" , "UNKNOWN"}, - {"bipcp.exe" , "UNKNOWN"}, - {"bipcpevalsetup.exe" , "UNKNOWN"}, - {"bisp.exe" , "UNKNOWN"}, - {"blackd.exe" , "UNKNOWN"}, - {"blackice.exe" , "UNKNOWN"}, - {"blink.exe" , "UNKNOWN"}, - {"blss.exe" , "UNKNOWN"}, - {"bootconf.exe" , "UNKNOWN"}, - {"bootwarn.exe" , "UNKNOWN"}, - {"borg2.exe" , "UNKNOWN"}, - {"bpc.exe" , "UNKNOWN"}, - {"brasil.exe" , "UNKNOWN"}, - {"bs120.exe" , "UNKNOWN"}, - {"bundle.exe" , "UNKNOWN"}, - {"bvt.exe" , "UNKNOWN"}, - {"ccapp.exe" , "UNKNOWN"}, - {"ccevtmgr.exe" , "UNKNOWN"}, - {"ccpxysvc.exe" , "UNKNOWN"}, - {"ccSvcHst.exe" , "UNKNOWN"}, - {"cdp.exe" , "UNKNOWN"}, - {"cfd.exe" , "UNKNOWN"}, - {"cfgwiz.exe" , "UNKNOWN"}, - {"cfiadmin.exe" , "UNKNOWN"}, - {"cfiaudit.exe" , "UNKNOWN"}, - {"cfinet.exe" , "UNKNOWN"}, - {"cfinet32.exe" , "UNKNOWN"}, - {"claw95.exe" , "UNKNOWN"}, - {"claw95cf.exe" , "UNKNOWN"}, - {"clean.exe" , "UNKNOWN"}, - {"cleaner.exe" , "UNKNOWN"}, - {"cleaner3.exe" , "UNKNOWN"}, - {"cleanpc.exe" , "UNKNOWN"}, - {"cleanup.exe" , "UNKNOWN"}, - {"click.exe" , "UNKNOWN"}, - {"cmdagent.exe" , "UNKNOWN"}, - {"cmesys.exe" , "UNKNOWN"}, - {"cmgrdian.exe" , "UNKNOWN"}, - {"cmon016.exe" , "UNKNOWN"}, - {"connectionmonitor.exe" , "UNKNOWN"}, - {"cpd.exe" , "UNKNOWN"}, - {"cpf9x206.exe" , "UNKNOWN"}, - {"cpfnt206.exe" , "UNKNOWN"}, - {"ctrl.exe" , "UNKNOWN"}, - {"cv.exe" , "UNKNOWN"}, - {"cwnb181.exe" , "UNKNOWN"}, - {"cwntdwmo.exe" , "UNKNOWN"}, - {"CylanceUI.exe" , "UNKNOWN"}, - {"CyProtect.exe" , "UNKNOWN"}, - {"CyUpdate.exe" , "UNKNOWN"}, - {"cyserver.exe" , "UNKNOWN"}, - {"cytray.exe" , "UNKNOWN"}, - {"CyveraService.exe" , "UNKNOWN"}, - {"datemanager.exe" , "UNKNOWN"}, - {"dcomx.exe" , "UNKNOWN"}, - {"defalert.exe" , "UNKNOWN"}, - {"defscangui.exe" , "UNKNOWN"}, - {"defwatch.exe" , "UNKNOWN"}, - {"deputy.exe" , "UNKNOWN"}, - {"divx.exe" , "UNKNOWN"}, - {"dgprompt.exe" , "UNKNOWN"}, - {"DgService.exe" , "UNKNOWN"}, - {"dllcache.exe" , "UNKNOWN"}, - {"dllreg.exe" , "UNKNOWN"}, - {"doors.exe" , "UNKNOWN"}, - {"dpf.exe" , "UNKNOWN"}, - {"dpfsetup.exe" , "UNKNOWN"}, - {"dpps2.exe" , "UNKNOWN"}, - {"drwatson.exe" , "UNKNOWN"}, - {"drweb32.exe" , "UNKNOWN"}, - {"drwebupw.exe" , "UNKNOWN"}, - {"dssagent.exe" , "UNKNOWN"}, - {"dumpcap.exe" , "UNKNOWN"}, - {"dvp95.exe" , "UNKNOWN"}, - {"dvp95_0.exe" , "UNKNOWN"}, - {"ecengine.exe" , "UNKNOWN"}, - {"efpeadm.exe" , "UNKNOWN"}, - {"egui.exe" , "UNKNOWN"}, - {"ekrn.exe" , "UNKNOWN"}, - {"emet_agent.exe" , "UNKNOWN"}, - {"emet_service.exe" , "UNKNOWN"}, - {"emsw.exe" , "UNKNOWN"}, - {"engineserver.exe" , "UNKNOWN"}, - {"ent.exe" , "UNKNOWN"}, - {"esafe.exe" , "UNKNOWN"}, - {"escanhnt.exe" , "UNKNOWN"}, - {"escanv95.exe" , "UNKNOWN"}, - {"espwatch.exe" , "UNKNOWN"}, - {"ethereal.exe" , "UNKNOWN"}, - {"etrustcipe.exe" , "UNKNOWN"}, - {"evpn.exe" , "UNKNOWN"}, - {"exantivirus-cnet.exe" , "UNKNOWN"}, - {"exe.avxw.exe" , "UNKNOWN"}, - {"expert.exe" , "UNKNOWN"}, - {"explore.exe" , "UNKNOWN"}, - {"f-agnt95.exe" , "UNKNOWN"}, - {"f-prot.exe" , "UNKNOWN"}, - {"f-prot95.exe" , "UNKNOWN"}, - {"f-stopw.exe" , "UNKNOWN"}, - {"fameh32.exe" , "UNKNOWN"}, - {"fast.exe" , "UNKNOWN"}, - {"fch32.exe" , "UNKNOWN"}, - {"fcagswd.exe" , "McAfee DLP Agent"}, - {"fcags.exe" , "McAfee DLP Agent"}, - {"fih32.exe" , "UNKNOWN"}, - {"findviru.exe" , "UNKNOWN"}, - {"firesvc.exe" , "McAfee Host Intrusion Prevention"}, - {"firetray.exe" , "UNKNOWN"}, - {"firewall.exe" , "UNKNOWN"}, - {"fnrb32.exe" , "UNKNOWN"}, - {"fp-win.exe" , "UNKNOWN"}, - {"fp-win_trial.exe" , "UNKNOWN"}, - {"fprot.exe" , "UNKNOWN"}, - {"frameworkservice.exe" , "UNKNOWN"}, - {"frminst.exe" , "UNKNOWN"}, - {"frw.exe" , "UNKNOWN"}, - {"fsaa.exe" , "UNKNOWN"}, - {"fsav.exe" , "UNKNOWN"}, - {"fsav32.exe" , "UNKNOWN"}, - {"fsav530stbyb.exe" , "UNKNOWN"}, - {"fsav530wtbyb.exe" , "UNKNOWN"}, - {"fsav95.exe" , "UNKNOWN"}, - {"fsgk32.exe" , "UNKNOWN"}, - {"fsm32.exe" , "UNKNOWN"}, - {"fsma32.exe" , "UNKNOWN"}, - {"fsmb32.exe" , "UNKNOWN"}, - {"gator.exe" , "UNKNOWN"}, - {"gbmenu.exe" , "UNKNOWN"}, - {"gbpoll.exe" , "UNKNOWN"}, - {"generics.exe" , "UNKNOWN"}, - {"gmt.exe" , "UNKNOWN"}, - {"guard.exe" , "UNKNOWN"}, - {"guarddog.exe" , "UNKNOWN"}, - {"hacktracersetup.exe" , "UNKNOWN"}, - {"hbinst.exe" , "UNKNOWN"}, - {"hbsrv.exe" , "UNKNOWN"}, - {"HijackThis.exe" , "UNKNOWN"}, - {"hipsvc.exe" , "UNKNOWN"}, - {"HipMgmt.exe" , "McAfee Host Intrusion Protection"}, - {"hotactio.exe" , "UNKNOWN"}, - {"hotpatch.exe" , "UNKNOWN"}, - {"htlog.exe" , "UNKNOWN"}, - {"htpatch.exe" , "UNKNOWN"}, - {"hwpe.exe" , "UNKNOWN"}, - {"hxdl.exe" , "UNKNOWN"}, - {"hxiul.exe" , "UNKNOWN"}, - {"iamapp.exe" , "UNKNOWN"}, - {"iamserv.exe" , "UNKNOWN"}, - {"iamstats.exe" , "UNKNOWN"}, - {"ibmasn.exe" , "UNKNOWN"}, - {"ibmavsp.exe" , "UNKNOWN"}, - {"icload95.exe" , "UNKNOWN"}, - {"icloadnt.exe" , "UNKNOWN"}, - {"icmon.exe" , "UNKNOWN"}, - {"icsupp95.exe" , "UNKNOWN"}, - {"icsuppnt.exe" , "UNKNOWN"}, - {"idle.exe" , "UNKNOWN"}, - {"iedll.exe" , "UNKNOWN"}, - {"iedriver.exe" , "UNKNOWN"}, - {"iface.exe" , "UNKNOWN"}, - {"ifw2000.exe" , "UNKNOWN"}, - {"inetlnfo.exe" , "UNKNOWN"}, - {"infus.exe" , "UNKNOWN"}, - {"infwin.exe" , "UNKNOWN"}, - {"init.exe" , "UNKNOWN"}, - {"intdel.exe" , "UNKNOWN"}, - {"intren.exe" , "UNKNOWN"}, - {"iomon98.exe" , "UNKNOWN"}, - {"istsvc.exe" , "UNKNOWN"}, - {"jammer.exe" , "UNKNOWN"}, - {"jdbgmrg.exe" , "UNKNOWN"}, - {"jedi.exe" , "UNKNOWN"}, - {"kavlite40eng.exe" , "UNKNOWN"}, - {"kavpers40eng.exe" , "UNKNOWN"}, - {"kavpf.exe" , "UNKNOWN"}, - {"kazza.exe" , "UNKNOWN"}, - {"keenvalue.exe" , "UNKNOWN"}, - {"kerio-pf-213-en-win.exe" , "UNKNOWN"}, - {"kerio-wrl-421-en-win.exe" , "UNKNOWN"}, - {"kerio-wrp-421-en-win.exe" , "UNKNOWN"}, - {"kernel32.exe" , "UNKNOWN"}, - {"KeyPass.exe" , "UNKNOWN"}, - {"killprocesssetup161.exe" , "UNKNOWN"}, - {"launcher.exe" , "UNKNOWN"}, - {"ldnetmon.exe" , "UNKNOWN"}, - {"ldpro.exe" , "UNKNOWN"}, - {"ldpromenu.exe" , "UNKNOWN"}, - {"ldscan.exe" , "UNKNOWN"}, - {"lnetinfo.exe" , "UNKNOWN"}, - {"loader.exe" , "UNKNOWN"}, - {"localnet.exe" , "UNKNOWN"}, - {"lockdown.exe" , "UNKNOWN"}, - {"lockdown2000.exe" , "UNKNOWN"}, - {"lookout.exe" , "UNKNOWN"}, - {"lordpe.exe" , "UNKNOWN"}, - {"lsetup.exe" , "UNKNOWN"}, - {"luall.exe" , "UNKNOWN"}, - {"luau.exe" , "UNKNOWN"}, - {"lucomserver.exe" , "UNKNOWN"}, - {"luinit.exe" , "UNKNOWN"}, - {"luspt.exe" , "UNKNOWN"}, - {"mapisvc32.exe" , "UNKNOWN"}, - {"masvc.exe" , "McAfee Agent"}, - {"mbamservice.exe" , "UNKNOWN"}, - {"mcafeefire.exe" , "UNKNOWN"}, - {"mcagent.exe" , "UNKNOWN"}, - {"mcmnhdlr.exe" , "UNKNOWN"}, - {"mcscript.exe" , "UNKNOWN"}, - {"mcscript_inuse.exe" , "UNKNOWN"}, - {"mctool.exe" , "UNKNOWN"}, - {"mctray.exe" , "UNKNOWN"}, - {"mcupdate.exe" , "UNKNOWN"}, - {"mcvsrte.exe" , "UNKNOWN"}, - {"mcvsshld.exe" , "UNKNOWN"}, - {"md.exe" , "UNKNOWN"}, - {"mfeann.exe" , "McAfee VirusScan Enterprise"}, - {"mfemactl.exe" , "McAfee VirusScan Enterprise"}, - {"mfevtps.exe" , "UNKNOWN"}, - {"mfin32.exe" , "UNKNOWN"}, - {"mfw2en.exe" , "UNKNOWN"}, - {"mfweng3.02d30.exe" , "UNKNOWN"}, - {"mgavrtcl.exe" , "UNKNOWN"}, - {"mgavrte.exe" , "UNKNOWN"}, - {"mghtml.exe" , "UNKNOWN"}, - {"mgui.exe" , "UNKNOWN"}, - {"minilog.exe" , "UNKNOWN"}, - {"minionhost.exe" , "UNKNOWN"}, - {"mmod.exe" , "UNKNOWN"}, - {"monitor.exe" , "UNKNOWN"}, - {"moolive.exe" , "UNKNOWN"}, - {"mostat.exe" , "UNKNOWN"}, - {"mpfagent.exe" , "UNKNOWN"}, - {"mpfservice.exe" , "UNKNOWN"}, - {"mpftray.exe" , "UNKNOWN"}, - {"mrflux.exe" , "UNKNOWN"}, - {"msapp.exe" , "UNKNOWN"}, - {"msbb.exe" , "UNKNOWN"}, - {"msblast.exe" , "UNKNOWN"}, - {"mscache.exe" , "UNKNOWN"}, - {"msccn32.exe" , "UNKNOWN"}, - {"mscman.exe" , "UNKNOWN"}, - {"msconfig.exe" , "UNKNOWN"}, - {"msdm.exe" , "UNKNOWN"}, - {"msdos.exe" , "UNKNOWN"}, - {"msiexec16.exe" , "UNKNOWN"}, - {"msinfo32.exe" , "UNKNOWN"}, - {"mslaugh.exe" , "UNKNOWN"}, - {"msmgt.exe" , "UNKNOWN"}, - {"msmsgri32.exe" , "UNKNOWN"}, - {"MsSense.exe" , "Microsoft Defender ATP"}, - {"mssmmc32.exe" , "UNKNOWN"}, - {"mssys.exe" , "UNKNOWN"}, - {"msvxd.exe" , "UNKNOWN"}, - {"mu0311ad.exe" , "UNKNOWN"}, - {"mwatch.exe" , "UNKNOWN"}, - {"n32scanw.exe" , "UNKNOWN"}, - {"naprdmgr.exe" , "UNKNOWN"}, - {"nav.exe" , "UNKNOWN"}, - {"navap.navapsvc.exe" , "UNKNOWN"}, - {"navapsvc.exe" , "UNKNOWN"}, - {"navapw32.exe" , "UNKNOWN"}, - {"navdx.exe" , "UNKNOWN"}, - {"navlu32.exe" , "UNKNOWN"}, - {"navnt.exe" , "UNKNOWN"}, - {"navstub.exe" , "UNKNOWN"}, - {"navw32.exe" , "UNKNOWN"}, - {"navwnt.exe" , "UNKNOWN"}, - {"nc2000.exe" , "UNKNOWN"}, - {"ncinst4.exe" , "UNKNOWN"}, - {"ndd32.exe" , "UNKNOWN"}, - {"neomonitor.exe" , "UNKNOWN"}, - {"neowatchlog.exe" , "UNKNOWN"}, - {"netarmor.exe" , "UNKNOWN"}, - {"netd32.exe" , "UNKNOWN"}, - {"netinfo.exe" , "UNKNOWN"}, - {"netmon.exe" , "UNKNOWN"}, - {"netscanpro.exe" , "UNKNOWN"}, - {"netspyhunter-1.2.exe" , "UNKNOWN"}, - {"netstat.exe" , "UNKNOWN"}, - {"netutils.exe" , "UNKNOWN"}, - {"nisserv.exe" , "UNKNOWN"}, - {"nisum.exe" , "UNKNOWN"}, - {"nmain.exe" , "UNKNOWN"}, - {"nod32.exe" , "UNKNOWN"}, - {"normist.exe" , "UNKNOWN"}, - {"norton_internet_secu_3.0_407.exe" , "UNKNOWN"}, - {"notstart.exe" , "UNKNOWN"}, - {"npf40_tw_98_nt_me_2k.exe" , "UNKNOWN"}, - {"npfmessenger.exe" , "UNKNOWN"}, - {"nprotect.exe" , "UNKNOWN"}, - {"npscheck.exe" , "UNKNOWN"}, - {"npssvc.exe" , "UNKNOWN"}, - {"nsched32.exe" , "UNKNOWN"}, - {"nssys32.exe" , "UNKNOWN"}, - {"nstask32.exe" , "UNKNOWN"}, - {"nsupdate.exe" , "UNKNOWN"}, - {"nt.exe" , "UNKNOWN"}, - {"ntrtscan.exe" , "UNKNOWN"}, - {"ntvdm.exe" , "UNKNOWN"}, - {"ntxconfig.exe" , "UNKNOWN"}, - {"nui.exe" , "UNKNOWN"}, - {"nupgrade.exe" , "UNKNOWN"}, - {"nvarch16.exe" , "UNKNOWN"}, - {"nvc95.exe" , "UNKNOWN"}, - {"nvsvc32.exe" , "UNKNOWN"}, - {"nwinst4.exe" , "UNKNOWN"}, - {"nwservice.exe" , "UNKNOWN"}, - {"nwtool16.exe" , "UNKNOWN"}, - {"nxlog.exe" , "UNKNOWN"}, - {"ollydbg.exe" , "UNKNOWN"}, - {"onsrvr.exe" , "UNKNOWN"}, - {"optimize.exe" , "UNKNOWN"}, - {"ostronet.exe" , "UNKNOWN"}, - {"osqueryd.exe" , "UNKNOWN"}, - {"otfix.exe" , "UNKNOWN"}, - {"outpost.exe" , "UNKNOWN"}, - {"outpostinstall.exe" , "UNKNOWN"}, - {"outpostproinstall.exe" , "UNKNOWN"}, - {"padmin.exe" , "UNKNOWN"}, - {"panixk.exe" , "UNKNOWN"}, - {"patch.exe" , "UNKNOWN"}, - {"pavcl.exe" , "UNKNOWN"}, - {"pavproxy.exe" , "UNKNOWN"}, - {"pavsched.exe" , "UNKNOWN"}, - {"pavw.exe" , "UNKNOWN"}, - {"pccwin98.exe" , "UNKNOWN"}, - {"pcfwallicon.exe" , "UNKNOWN"}, - {"pcip10117_0.exe" , "UNKNOWN"}, - {"pcscan.exe" , "UNKNOWN"}, - {"pdsetup.exe" , "UNKNOWN"}, - {"periscope.exe" , "UNKNOWN"}, - {"persfw.exe" , "UNKNOWN"}, - {"perswf.exe" , "UNKNOWN"}, - {"pf2.exe" , "UNKNOWN"}, - {"pfwadmin.exe" , "UNKNOWN"}, - {"pgmonitr.exe" , "UNKNOWN"}, - {"pingscan.exe" , "UNKNOWN"}, - {"platin.exe" , "UNKNOWN"}, - {"pop3trap.exe" , "UNKNOWN"}, - {"poproxy.exe" , "UNKNOWN"}, - {"popscan.exe" , "UNKNOWN"}, - {"portdetective.exe" , "UNKNOWN"}, - {"portmonitor.exe" , "UNKNOWN"}, - {"powerscan.exe" , "UNKNOWN"}, - {"ppinupdt.exe" , "UNKNOWN"}, - {"pptbc.exe" , "UNKNOWN"}, - {"ppvstop.exe" , "UNKNOWN"}, - {"prizesurfer.exe" , "UNKNOWN"}, - {"prmt.exe" , "UNKNOWN"}, - {"prmvr.exe" , "UNKNOWN"}, - {"procdump.exe" , "UNKNOWN"}, - {"processmonitor.exe" , "UNKNOWN"}, - {"procexp.exe" , "UNKNOWN"}, - {"procexp64.exe" , "UNKNOWN"}, - {"procexplorerv1.0.exe" , "UNKNOWN"}, - {"procmon.exe" , "UNKNOWN"}, - {"programauditor.exe" , "UNKNOWN"}, - {"proport.exe" , "UNKNOWN"}, - {"protectx.exe" , "UNKNOWN"}, - {"pspf.exe" , "UNKNOWN"}, - {"purge.exe" , "UNKNOWN"}, - {"qconsole.exe" , "UNKNOWN"}, - {"qserver.exe" , "UNKNOWN"}, - {"rapapp.exe" , "UNKNOWN"}, - {"rav7.exe" , "UNKNOWN"}, - {"rav7win.exe" , "UNKNOWN"}, - {"rav8win32eng.exe" , "UNKNOWN"}, - {"ray.exe" , "UNKNOWN"}, - {"rb32.exe" , "UNKNOWN"}, - {"rcsync.exe" , "UNKNOWN"}, - {"realmon.exe" , "UNKNOWN"}, - {"reged.exe" , "UNKNOWN"}, - {"regedit.exe" , "UNKNOWN"}, - {"regedt32.exe" , "UNKNOWN"}, - {"rescue.exe" , "UNKNOWN"}, - {"rescue32.exe" , "UNKNOWN"}, - {"rrguard.exe" , "UNKNOWN"}, - {"rtvscan.exe" , "UNKNOWN"}, - {"rtvscn95.exe" , "UNKNOWN"}, - {"rulaunch.exe" , "UNKNOWN"}, - {"run32dll.exe" , "UNKNOWN"}, - {"rundll.exe" , "UNKNOWN"}, - {"rundll16.exe" , "UNKNOWN"}, - {"ruxdll32.exe" , "UNKNOWN"}, - {"safeweb.exe" , "UNKNOWN"}, - {"sahagent.exescan32.exe" , "UNKNOWN"}, - {"save.exe" , "UNKNOWN"}, - {"savenow.exe" , "UNKNOWN"}, - {"sbserv.exe" , "UNKNOWN"}, - {"scam32.exe" , "UNKNOWN"}, - {"scan32.exe" , "UNKNOWN"}, - {"scan95.exe" , "UNKNOWN"}, - {"scanpm.exe" , "UNKNOWN"}, - {"scrscan.exe" , "UNKNOWN"}, - {"SentinelOne.exe" , "UNKNOWN"}, - {"serv95.exe" , "UNKNOWN"}, - {"setupvameeval.exe" , "UNKNOWN"}, - {"setup_flowprotector_us.exe", "UNKNOWN"}, - {"sfc.exe" , "UNKNOWN"}, - {"sgssfw32.exe" , "UNKNOWN"}, - {"sh.exe" , "UNKNOWN"}, - {"shellspyinstall.exe" , "UNKNOWN"}, - {"shn.exe" , "UNKNOWN"}, - {"showbehind.exe" , "UNKNOWN"}, - {"shstat.exe" , "McAfee VirusScan Enterprise"}, - {"SISIDSService.exe" , "UNKNOWN"}, - {"SISIPSUtil.exe" , "UNKNOWN"}, - {"smc.exe" , "UNKNOWN"}, - {"sms.exe" , "UNKNOWN"}, - {"smss32.exe" , "UNKNOWN"}, - {"soap.exe" , "UNKNOWN"}, - {"sofi.exe" , "UNKNOWN"}, - {"sperm.exe" , "UNKNOWN"}, - {"splunk.exe" , "Splunk"}, - {"splunkd.exe" , "Splunk"}, - {"splunk-admon.exe" , "Splunk"}, - {"splunk-powershell.exe" , "Splunk"}, - {"splunk-winevtlog.exe" , "Splunk"}, - {"spf.exe" , "UNKNOWN"}, - {"sphinx.exe" , "UNKNOWN"}, - {"spoler.exe" , "UNKNOWN"}, - {"spoolcv.exe" , "UNKNOWN"}, - {"spoolsv32.exe" , "UNKNOWN"}, - {"spyxx.exe" , "UNKNOWN"}, - {"srexe.exe" , "UNKNOWN"}, - {"srng.exe" , "UNKNOWN"}, - {"ss3edit.exe" , "UNKNOWN"}, - {"ssgrate.exe" , "UNKNOWN"}, - {"ssg_4104.exe" , "UNKNOWN"}, - {"st2.exe" , "UNKNOWN"}, - {"start.exe" , "UNKNOWN"}, - {"stcloader.exe" , "UNKNOWN"}, - {"supftrl.exe" , "UNKNOWN"}, - {"support.exe" , "UNKNOWN"}, - {"supporter5.exe" , "UNKNOWN"}, - {"svchostc.exe" , "UNKNOWN"}, - {"svchosts.exe" , "UNKNOWN"}, - {"sweep95.exe" , "UNKNOWN"}, - {"sweepnet.sweepsrv.sys.swnetsup.exe", "UNKNOWN"}, - {"symproxysvc.exe" , "UNKNOWN"}, - {"symtray.exe" , "UNKNOWN"}, - {"sysedit.exe" , "UNKNOWN"}, - {"sysmon.exe" , "Sysinternals Sysmon"}, - {"sysupd.exe" , "UNKNOWN"}, - {"TaniumClient.exe" , "Tanium"}, - {"taskmg.exe" , "UNKNOWN"}, - {"taskmo.exe" , "UNKNOWN"}, - {"taumon.exe" , "UNKNOWN"}, - {"tbmon.exe" , "UNKNOWN"}, - {"tbscan.exe" , "UNKNOWN"}, - {"tc.exe" , "UNKNOWN"}, - {"tca.exe" , "UNKNOWN"}, - {"tcm.exe" , "UNKNOWN"}, - {"tcpview.exe" , "UNKNOWN"}, - {"tds-3.exe" , "UNKNOWN"}, - {"tds2-98.exe" , "UNKNOWN"}, - {"tds2-nt.exe" , "UNKNOWN"}, - {"teekids.exe" , "UNKNOWN"}, - {"tfak.exe" , "UNKNOWN"}, - {"tfak5.exe" , "UNKNOWN"}, - {"tgbob.exe" , "UNKNOWN"}, - {"titanin.exe" , "UNKNOWN"}, - {"titaninxp.exe" , "UNKNOWN"}, - {"tlaservice.exe" , "UNKNOWN"}, - {"tlaworker.exe" , "UNKNOWN"}, - {"tracert.exe" , "UNKNOWN"}, - {"trickler.exe" , "UNKNOWN"}, - {"trjscan.exe" , "UNKNOWN"}, - {"trjsetup.exe" , "UNKNOWN"}, - {"trojantrap3.exe" , "UNKNOWN"}, - {"tsadbot.exe" , "UNKNOWN"}, - {"tshark.exe" , "UNKNOWN"}, - {"tvmd.exe" , "UNKNOWN"}, - {"tvtmd.exe" , "UNKNOWN"}, - {"udaterui.exe" , "UNKNOWN"}, - {"undoboot.exe" , "UNKNOWN"}, - {"updat.exe" , "UNKNOWN"}, - {"update.exe" , "UNKNOWN"}, - {"updaterui.exe" , "UNKNOWN"}, - {"upgrad.exe" , "UNKNOWN"}, - {"utpost.exe" , "UNKNOWN"}, - {"vbcmserv.exe" , "UNKNOWN"}, - {"vbcons.exe" , "UNKNOWN"}, - {"vbust.exe" , "UNKNOWN"}, - {"vbwin9x.exe" , "UNKNOWN"}, - {"vbwinntw.exe" , "UNKNOWN"}, - {"vcsetup.exe" , "UNKNOWN"}, - {"vet32.exe" , "UNKNOWN"}, - {"vet95.exe" , "UNKNOWN"}, - {"vettray.exe" , "UNKNOWN"}, - {"vfsetup.exe" , "UNKNOWN"}, - {"vir-help.exe" , "UNKNOWN"}, - {"virusmdpersonalfirewall.exe", "UNKNOWN"}, - {"vnlan300.exe" , "UNKNOWN"}, - {"vnpc3000.exe" , "UNKNOWN"}, - {"vpc32.exe" , "UNKNOWN"}, - {"vpc42.exe" , "UNKNOWN"}, - {"vpfw30s.exe" , "UNKNOWN"}, - {"vptray.exe" , "UNKNOWN"}, - {"vscan40.exe" , "UNKNOWN"}, - {"vscenu6.02d30.exe" , "UNKNOWN"}, - {"vsched.exe" , "UNKNOWN"}, - {"vsecomr.exe" , "UNKNOWN"}, - {"vshwin32.exe" , "UNKNOWN"}, - {"vsisetup.exe" , "UNKNOWN"}, - {"vsmain.exe" , "UNKNOWN"}, - {"vsmon.exe" , "UNKNOWN"}, - {"vsstat.exe" , "UNKNOWN"}, - {"vstskmgr.exe" , "McAfee VirusScan Enterprise"}, - {"vswin9xe.exe" , "UNKNOWN"}, - {"vswinntse.exe" , "UNKNOWN"}, - {"vswinperse.exe" , "UNKNOWN"}, - {"w32dsm89.exe" , "UNKNOWN"}, - {"w9x.exe" , "UNKNOWN"}, - {"watchdog.exe" , "UNKNOWN"}, - {"webdav.exe" , "UNKNOWN"}, - {"webscanx.exe" , "UNKNOWN"}, - {"webtrap.exe" , "UNKNOWN"}, - {"wfindv32.exe" , "UNKNOWN"}, - {"whoswatchingme.exe" , "UNKNOWN"}, - {"wimmun32.exe" , "UNKNOWN"}, - {"win-bugsfix.exe" , "UNKNOWN"}, - {"win32.exe" , "UNKNOWN"}, - {"win32us.exe" , "UNKNOWN"}, - {"winactive.exe" , "UNKNOWN"}, - {"window.exe" , "UNKNOWN"}, - {"windows.exe" , "UNKNOWN"}, - {"wininetd.exe" , "UNKNOWN"}, - {"wininitx.exe" , "UNKNOWN"}, - {"winlogin.exe" , "UNKNOWN"}, - {"winmain.exe" , "UNKNOWN"}, - {"winnet.exe" , "UNKNOWN"}, - {"winppr32.exe" , "UNKNOWN"}, - {"winrecon.exe" , "UNKNOWN"}, - {"winservn.exe" , "UNKNOWN"}, - {"winssk32.exe" , "UNKNOWN"}, - {"winstart.exe" , "UNKNOWN"}, - {"winstart001.exe" , "UNKNOWN"}, - {"wintsk32.exe" , "UNKNOWN"}, - {"winupdate.exe" , "UNKNOWN"}, - {"wireshark.exe" , "UNKNOWN"}, - {"wkufind.exe" , "UNKNOWN"}, - {"wnad.exe" , "UNKNOWN"}, - {"wnt.exe" , "UNKNOWN"}, - {"wradmin.exe" , "UNKNOWN"}, - {"wrctrl.exe" , "UNKNOWN"}, - {"wsbgate.exe" , "UNKNOWN"}, - {"wupdater.exe" , "UNKNOWN"}, - {"wupdt.exe" , "UNKNOWN"}, - {"wyvernworksfirewall.exe" , "UNKNOWN"}, - {"xagt.exe" , "UNKNOWN"}, - {"xpf202en.exe" , "UNKNOWN"}, - {"zapro.exe" , "UNKNOWN"}, - {"zapsetup3001.exe" , "UNKNOWN"}, - {"zatutor.exe" , "UNKNOWN"}, - {"zonalm2601.exe" , "UNKNOWN"}, - {"zonealarm.exe" , "UNKNOWN"}, - {"_avp32.exe" , "UNKNOWN"}, - {"_avpcc.exe" , "UNKNOWN"}, - {"rshell.exe" , "UNKNOWN"}, - {"_avpm.exe" , "UNKNOWN"} - }; - - // TODO: cyberark? other password managers? - Hashtable interestingProcesses = new Hashtable() - { - {"CmRcService" , "Configuration Manager Remote Control Service"}, - {"ftp" , "Misc. FTP client"}, - {"LMIGuardian" , "LogMeIn Reporter"}, - {"LogMeInSystray" , "LogMeIn System Tray"}, - {"RaMaint" , "LogMeIn maintenance sevice"}, - {"mmc" , "Microsoft Management Console"}, - {"putty" , "Putty SSH client"}, - {"pscp" , "Putty SCP client"}, - {"psftp" , "Putty SFTP client"}, - {"puttytel" , "Putty Telnet client"}, - {"plink" , "Putty CLI client"}, - {"pageant" , "Putty SSH auth agent"}, - {"kitty" , "Kitty SSH client"}, - {"telnet" , "Misc. Telnet client"}, - {"SecureCRT" , "SecureCRT SSH/Telnet client"}, - {"TeamViewer" , "TeamViewer"}, - {"tv_x64" , "TeamViewer x64 remote control"}, - {"tv_w32" , "TeamViewer x86 remote control"}, - {"keepass" , "KeePass password vault"}, - {"mstsc" , "Microsoft RDP client"}, - {"vnc" , "Possible VNC client"}, - {"powershell" , "PowerShell host process"}, - {"cmd" , "Command Prompt"}, - }; - - Hashtable browserProcesses = new Hashtable() - { - {"chrome" , "Google Chrome"}, - {"iexplore" , "Microsoft Internet Explorer"}, - {"MicrosoftEdge" , "Microsoft Edge"}, - {"firefox" , "Mozilla Firefox"} - }; - - try - { - string wmiQuery = string.Format("SELECT * FROM Win32_Process"); - ManagementObjectSearcher searcher = new ManagementObjectSearcher(wmiQuery); - ManagementObjectCollection retObjectCollection = searcher.Get(); - - Console.WriteLine("\r\n\r\n=== Process Enumerations ===\r\n"); - - Console.WriteLine(" * Potential Defensive Processes *\r\n"); - - foreach (ManagementObject Process in retObjectCollection) - { - foreach (DictionaryEntry defensiveProcess in defensiveProcesses) - { - if (Process["Name"].ToString().ToLower() == defensiveProcess.Key.ToString().ToLower()) - { - string[] OwnerInfo = new string[2]; - Process.InvokeMethod("GetOwner", (object[])OwnerInfo); - - Console.WriteLine("\tName : {0}", Process["Name"]); - Console.WriteLine("\tProduct : {0}", defensiveProcess.Value); - Console.WriteLine("\tProcessID : {0}", Process["ProcessID"]); - if (OwnerInfo[0] != null) - { - Console.WriteLine("\tOwner : {0}\\{1}", OwnerInfo[1], OwnerInfo[0]); - } - else - { - Console.WriteLine("\tOwner : "); - } - Console.WriteLine("\tCommandLine : {0}\r\n", Process["CommandLine"]); - } - } - } - - Console.WriteLine("\r\n * Browser Processes *\r\n"); - - foreach (ManagementObject Process in retObjectCollection) - { - foreach (DictionaryEntry browserProcess in browserProcesses) - { - if (Regex.IsMatch(Process["Name"].ToString(), browserProcess.Key.ToString(), RegexOptions.IgnoreCase)) - { - string[] OwnerInfo = new string[2]; - Process.InvokeMethod("GetOwner", (object[])OwnerInfo); - - Console.WriteLine("\tName : {0}", Process["Name"]); - Console.WriteLine("\tProduct : {0}", browserProcess.Value); - Console.WriteLine("\tProcessID : {0}", Process["ProcessID"]); - if (OwnerInfo[0] != null) - { - Console.WriteLine("\tOwner : {0}\\{1}", OwnerInfo[1], OwnerInfo[0]); - } - else - { - Console.WriteLine("\tOwner : "); - } - Console.WriteLine("\tCommandLine : {0}\r\n", Process["CommandLine"]); - } - } - } - - Console.WriteLine("\r\n * Other Interesting Processes *\r\n"); - - foreach (ManagementObject Process in retObjectCollection) - { - foreach (DictionaryEntry interestingProcess in interestingProcesses) - { - if (Regex.IsMatch(Process["Name"].ToString(), interestingProcess.Key.ToString(), RegexOptions.IgnoreCase)) - { - string[] OwnerInfo = new string[2]; - Process.InvokeMethod("GetOwner", (object[])OwnerInfo); - - Console.WriteLine("\tName : {0}", Process["Name"]); - Console.WriteLine("\tProduct : {0}", interestingProcess.Value); - Console.WriteLine("\tProcessID : {0}", Process["ProcessID"]); - if (OwnerInfo[0] != null) - { - Console.WriteLine("\tOwner : {0}\\{1}", OwnerInfo[1], OwnerInfo[0]); - } - else - { - Console.WriteLine("\tOwner : "); - } - Console.WriteLine("\tCommandLine : {0}\r\n", Process["CommandLine"]); - } - } - } - } - catch (Exception ex) - { - Console.WriteLine(" [X] Exception: {0}", ex.Message); - } - } - - public static void ListRegistryAutoLogon() - { - Console.WriteLine("\r\n\r\n=== Registry Auto-logon Settings ===\r\n"); - - string DefaultDomainName = GetRegValue("HKLM", "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon", "DefaultDomainName"); - if (DefaultDomainName != "") - { - Console.WriteLine(" {0,-23} : {1}", "DefaultDomainName", DefaultDomainName); - } - - string DefaultUserName = GetRegValue("HKLM", "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon", "DefaultUserName"); - if (DefaultUserName != "") - { - Console.WriteLine(" {0,-23} : {1}", "DefaultUserName", DefaultUserName); - } - - string DefaultPassword = GetRegValue("HKLM", "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon", "DefaultPassword"); - if (DefaultPassword != "") - { - Console.WriteLine(" {0,-23} : {1}", "DefaultPassword", DefaultPassword); - } - - string AltDefaultDomainName = GetRegValue("HKLM", "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon", "AltDefaultDomainName"); - if (AltDefaultDomainName != "") - { - Console.WriteLine(" {0,-23} : {1}", "AltDefaultDomainName", AltDefaultDomainName); - } - - string AltDefaultUserName = GetRegValue("HKLM", "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon", "AltDefaultUserName"); - if (AltDefaultDomainName != "") - { - Console.WriteLine(" {0,-23} : {1}", "AltDefaultUserName", AltDefaultUserName); - } - - string AltDefaultPassword = GetRegValue("HKLM", "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon", "AltDefaultPassword"); - if (AltDefaultDomainName != "") - { - Console.WriteLine(" {0,-23} : {1}", "AltDefaultPassword", AltDefaultPassword); - } - } - - public static void ListRegistryAutoRuns() - { - Console.WriteLine("\r\n\r\n=== Registry Autoruns ==="); - - string[] autorunLocations = new string[] { - "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run", - "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\RunOnce", - "SOFTWARE\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\Run", - "SOFTWARE\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\RunOnce", - "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\RunService", - "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\RunOnceService", - "SOFTWARE\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\RunService", - "SOFTWARE\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\RunOnceService" - }; - - foreach (string autorunLocation in autorunLocations) - { - Dictionary settings = GetRegValues("HKLM", autorunLocation); - if ((settings != null) && (settings.Count != 0)) - { - Console.WriteLine("\r\n HKLM:\\{0} :", autorunLocation); - foreach (KeyValuePair kvp in settings) - { - Console.WriteLine(" {0}", kvp.Value); - } - } - } - } - - public static void ListRDPSessions() - { - // adapted from http://www.pinvoke.net/default.aspx/wtsapi32.wtsenumeratesessions - IntPtr server = IntPtr.Zero; - List ret = new List(); - server = OpenServer("localhost"); - - Console.WriteLine("\r\n\r\n=== Current Host RDP Sessions (qwinsta) ===\r\n"); - - try - { - IntPtr ppSessionInfo = IntPtr.Zero; - - Int32 count = 0; - Int32 level = 1; - Int32 retval = WTSEnumerateSessionsEx(server, ref level, 0, ref ppSessionInfo, ref count); - Int32 dataSize = Marshal.SizeOf(typeof(WTS_SESSION_INFO_1)); - Int64 current = (Int64)ppSessionInfo; - - if (retval != 0) - { - for (int i = 0; i < count; i++) - { - WTS_SESSION_INFO_1 si = (WTS_SESSION_INFO_1)Marshal.PtrToStructure((System.IntPtr)current, typeof(WTS_SESSION_INFO_1)); - current += dataSize; - - Console.WriteLine(" SessionID: {0}", si.SessionID); - Console.WriteLine(" SessionName: {0}", si.pSessionName); - Console.WriteLine(" UserName: {0}", si.pUserName); - Console.WriteLine(" DomainName: {0}", si.pDomainName); - Console.WriteLine(" State: {0}", si.State); - - // Now use WTSQuerySessionInformation to get the remote IP (if any) for the connection - IntPtr addressPtr = IntPtr.Zero; - uint bytes = 0; - - WTSQuerySessionInformation(server, (uint)si.SessionID, WTS_INFO_CLASS.WTSClientAddress, out addressPtr, out bytes); - WTS_CLIENT_ADDRESS address = (WTS_CLIENT_ADDRESS)Marshal.PtrToStructure((System.IntPtr)addressPtr, typeof(WTS_CLIENT_ADDRESS)); - - if (address.Address[2] != 0) - { - string sourceIP = String.Format("{0}.{1}.{2}.{3}", address.Address[2], address.Address[3], address.Address[4], address.Address[5]); - Console.WriteLine(" SourceIP: {0}\r\n", sourceIP); - } - else - { - Console.WriteLine(" SourceIP: \r\n"); - } - } - - WTSFreeMemory(ppSessionInfo); - } - } - catch (Exception ex) - { - Console.WriteLine(ex); - } - finally - { - CloseServer(server); - } - } - - public static void ListFirewallRules() - { - // lists local firewall policies and rules - // by default, only "deny" result are output unless "full" is passed - - if (FilterResults.filter) - { - Console.WriteLine("\r\n\r\n=== Firewall Rules (Deny) ===\r\n"); - } - else - { - Console.WriteLine("\r\n\r\n=== Firewall Rules (All) ===\r\n"); - } - - try - { - // GUID for HNetCfg.FwPolicy2 COM object - Type firewall = Type.GetTypeFromCLSID(new Guid("E2B3C97F-6AE1-41AC-817A-F6F92166D7DD")); - Object firewallObj = Activator.CreateInstance(firewall); - Object types = firewallObj.GetType().InvokeMember("CurrentProfileTypes", BindingFlags.GetProperty, null, firewallObj, null); - - Console.WriteLine(" Current Profile(s) : {0}\r\n", (FirewallProfiles)Int32.Parse(types.ToString())); - - // NET_FW_PROFILE2_DOMAIN = 1, NET_FW_PROFILE2_PRIVATE = 2, NET_FW_PROFILE2_PUBLIC = 4 - Object enabledDomain = firewallObj.GetType().InvokeMember("FirewallEnabled", BindingFlags.GetProperty, null, firewallObj, new object[] { 1 }); - Console.WriteLine(" FirewallEnabled (Domain) : {0}", enabledDomain); - Object enabledPrivate = firewallObj.GetType().InvokeMember("FirewallEnabled", BindingFlags.GetProperty, null, firewallObj, new object[] { 2 }); - Console.WriteLine(" FirewallEnabled (Private) : {0}", enabledPrivate); - Object enabledPublic = firewallObj.GetType().InvokeMember("FirewallEnabled", BindingFlags.GetProperty, null, firewallObj, new object[] { 4 }); - Console.WriteLine(" FirewallEnabled (Public) : {0}\r\n", enabledPublic); - - // now grab all the rules - Object rules = firewallObj.GetType().InvokeMember("Rules", BindingFlags.GetProperty, null, firewallObj, null); - - // manually get the enumerator() method - System.Collections.IEnumerator enumerator = (System.Collections.IEnumerator)rules.GetType().InvokeMember("GetEnumerator", BindingFlags.InvokeMethod, null, rules, null); - - // move to the first item - enumerator.MoveNext(); - Object currentItem = enumerator.Current; - - while (currentItem != null) - { - // only display enabled rules - Object Enabled = currentItem.GetType().InvokeMember("Enabled", BindingFlags.GetProperty, null, currentItem, null); - if (Enabled.ToString() == "True") - { - Object Action = currentItem.GetType().InvokeMember("Action", BindingFlags.GetProperty, null, currentItem, null); - if ((FilterResults.filter && (Action.ToString() == "0")) || !FilterResults.filter) - { - // extract all of our fields - Object Name = currentItem.GetType().InvokeMember("Name", BindingFlags.GetProperty, null, currentItem, null); - Object Description = currentItem.GetType().InvokeMember("Description", BindingFlags.GetProperty, null, currentItem, null); - Object Protocol = currentItem.GetType().InvokeMember("Protocol", BindingFlags.GetProperty, null, currentItem, null); - Object ApplicationName = currentItem.GetType().InvokeMember("ApplicationName", BindingFlags.GetProperty, null, currentItem, null); - Object LocalAddresses = currentItem.GetType().InvokeMember("LocalAddresses", BindingFlags.GetProperty, null, currentItem, null); - Object LocalPorts = currentItem.GetType().InvokeMember("LocalPorts", BindingFlags.GetProperty, null, currentItem, null); - Object RemoteAddresses = currentItem.GetType().InvokeMember("RemoteAddresses", BindingFlags.GetProperty, null, currentItem, null); - Object RemotePorts = currentItem.GetType().InvokeMember("RemotePorts", BindingFlags.GetProperty, null, currentItem, null); - Object Direction = currentItem.GetType().InvokeMember("Direction", BindingFlags.GetProperty, null, currentItem, null); - Object Profiles = currentItem.GetType().InvokeMember("Profiles", BindingFlags.GetProperty, null, currentItem, null); - - string ruleAction = "ALLOW"; - if (Action.ToString() != "1") - { - ruleAction = "DENY"; - } - - string ruleDirection = "IN"; - if (Direction.ToString() != "1") - { - ruleDirection = "OUT"; - } - - string ruleProtocol = "TCP"; - if (Protocol.ToString() != "6") - { - ruleProtocol = "UDP"; - } - // TODO: other protocols! - - Console.WriteLine(" Name : {0}", Name); - Console.WriteLine(" Description : {0}", Description); - Console.WriteLine(" ApplicationName : {0}", ApplicationName); - Console.WriteLine(" Protocol : {0}", ruleProtocol); - Console.WriteLine(" Action : {0}", ruleAction); - Console.WriteLine(" Direction : {0}", ruleDirection); - Console.WriteLine(" Profiles : {0}", (FirewallProfiles)Int32.Parse(Profiles.ToString())); - Console.WriteLine(" Local Addr:Port : {0}:{1}", LocalAddresses, LocalPorts); - Console.WriteLine(" Remote Addr:Port : {0}:{1}\r\n", RemoteAddresses, RemotePorts); - } - } - // manually move the enumerator - enumerator.MoveNext(); - currentItem = enumerator.Current; - } - Marshal.ReleaseComObject(firewallObj); - firewallObj = null; - } - catch (Exception ex) - { - Console.WriteLine(" [X] Exception: {0}", ex); - } - } - - public static void ListDNSCache() - { - Console.WriteLine("\r\n\r\n=== DNS Cache (via WMI) ===\r\n"); - - // lists the local DNS cache via WMI (MSFT_DNSClientCache class) - try - { - ManagementObjectSearcher wmiData = new ManagementObjectSearcher(@"root\standardcimv2", "SELECT * FROM MSFT_DNSClientCache"); - ManagementObjectCollection data = wmiData.Get(); - - foreach (ManagementObject result in data) - { - Console.WriteLine(" Entry : {0}", result["Entry"]); - Console.WriteLine(" Name : {0}", result["Name"]); - Console.WriteLine(" Data : {0}\r\n", result["Data"]); - } - } - catch (ManagementException ex) when (ex.ErrorCode == ManagementStatus.InvalidNamespace) - { - Console.WriteLine(" [X] 'MSFT_DNSClientCache' WMI class unavailable (minimum supported versions of Windows: 8/2012)", ex.Message); - } - catch (Exception ex) - { - Console.WriteLine(" [X] Exception: {0}", ex.Message); - } - } - - public static void ListARPTable() - { - // adapted from Fred's code at https://social.technet.microsoft.com/Forums/lync/en-US/e949b8d6-17ad-4afc-88cd-0019a3ac9df9/powershell-alternative-to-arp-a?forum=ITCG - - Console.WriteLine("\r\n\r\n=== Current ARP Table ==="); - - try - { - Dictionary adapters = new Dictionary(); - Dictionary hostNames = new Dictionary(); - - // build a mapping of index -> interface information - foreach (NetworkInterface ni in NetworkInterface.GetAllNetworkInterfaces()) - { - if (ni != null) - { - IPInterfaceProperties adapterProperties = ni.GetIPProperties(); - if (adapterProperties != null) - { - string dnsServers = ""; - List dnsServerList = new List(); - IPAddressCollection dnsServerCollection = adapterProperties.DnsAddresses; - if (dnsServerCollection.Count > 0) - { - foreach (IPAddress dns in dnsServerCollection) - { - dnsServerList.Add(dns.ToString()); - } - dnsServers = String.Join(", ", dnsServerList.ToArray()); - } - - try - { - IPv4InterfaceProperties p = adapterProperties.GetIPv4Properties(); - if (p != null) - { - ArrayList ips = new ArrayList(); - - foreach (UnicastIPAddressInformation info in adapterProperties.UnicastAddresses) - { - if (Regex.IsMatch(info.Address.ToString(), @"^(\d+)\.(\d+)\.(\d+)\.(\d+)$")) - { - // grab all the IPv4 addresses - ips.Add(info.Address.ToString()); - } - } - // build a "Ethernet1 (172.16.213.246) --- Index 8" type string for the index - string description = String.Format("{0} ({1}) --- Index {2}", ni.Name, string.Join(",", (string[])ips.ToArray(Type.GetType("System.String"))), p.Index); - if (!String.IsNullOrEmpty(dnsServers)) - { - description += String.Format("\r\n DNS Servers : {0}\r\n", dnsServers); - } - adapters.Add(p.Index, description); - } - } - catch { } - } - } - } - - int bytesNeeded = 0; - - int result = GetIpNetTable(IntPtr.Zero, ref bytesNeeded, false); - - // call the function, expecting an insufficient buffer. - if (result != ERROR_INSUFFICIENT_BUFFER) - { - Console.WriteLine(" [X] Exception: {0}", result); - } - - IntPtr buffer = IntPtr.Zero; - - // allocate sufficient memory for the result structure - buffer = Marshal.AllocCoTaskMem(bytesNeeded); - - result = GetIpNetTable(buffer, ref bytesNeeded, false); - - if (result != 0) - { - Console.WriteLine(" [X] Exception allocating buffer: {0}", result); - } - - // now we have the buffer, we have to marshal it. We can read the first 4 bytes to get the length of the buffer - int entries = Marshal.ReadInt32(buffer); - - // increment the memory pointer by the size of the int - IntPtr currentBuffer = new IntPtr(buffer.ToInt64() + Marshal.SizeOf(typeof(int))); - - // allocate a list of entries - List arpEntries = new List(); - - // cycle through the entries - for (int index = 0; index < entries; index++) - { - arpEntries.Add((MIB_IPNETROW)Marshal.PtrToStructure(new IntPtr(currentBuffer.ToInt64() + (index * Marshal.SizeOf(typeof(MIB_IPNETROW)))), typeof(MIB_IPNETROW))); - } - - // sort the list by interface index - List sortedARPEntries = arpEntries.OrderBy(o => o.dwIndex).ToList(); - int currentIndexAdaper = -1; - - foreach (MIB_IPNETROW arpEntry in sortedARPEntries) - { - int indexAdapter = arpEntry.dwIndex; - - if (currentIndexAdaper != indexAdapter) - { - if (adapters.ContainsKey(indexAdapter)) - { - Console.WriteLine("\r\n\r\n Interface : {0}", adapters[indexAdapter]); - } - else - { - Console.WriteLine("\r\n\r\n Interface : n/a --- Index {0}", indexAdapter); - } - Console.WriteLine(" Internet Address Physical Address Type"); - currentIndexAdaper = indexAdapter; - } - - IPAddress ipAddr = new IPAddress(BitConverter.GetBytes(arpEntry.dwAddr)); - byte[] macBytes = new byte[] { arpEntry.mac0, arpEntry.mac1, arpEntry.mac2, arpEntry.mac3, arpEntry.mac4, arpEntry.mac5 }; - string physAddr = BitConverter.ToString(macBytes); - ArpEntryType entryType = (ArpEntryType)arpEntry.dwType; - - Console.WriteLine(String.Format(" {0,-22}{1,-22}{2}", ipAddr, physAddr, entryType)); - } - - FreeMibTable(buffer); - } - catch (Exception ex) - { - Console.WriteLine(" [X] Exception: {0}", ex); - } - } - - // helper that gets a service name from a service tag - private static string GetServiceNameFromTag(uint ProcessId, uint ServiceTag) - { - SC_SERVICE_TAG_QUERY serviceTagQuery = new SC_SERVICE_TAG_QUERY - { - ProcessId = ProcessId, - ServiceTag = ServiceTag - }; - - uint res = I_QueryTagInformation(IntPtr.Zero, SC_SERVICE_TAG_QUERY_TYPE.ServiceNameFromTagInformation, ref serviceTagQuery); - if (res == ERROR_SUCCESS) - { - return Marshal.PtrToStringUni(serviceTagQuery.Buffer); - } - else - { - return null; - } - } - - public static void ListAllTcpConnections() - { - int AF_INET = 2; // IP_v4 - uint tableBufferSize = 0; - uint ret = 0; - IntPtr tableBuffer = IntPtr.Zero; - IntPtr rowPtr = IntPtr.Zero; - MIB_TCPTABLE_OWNER_MODULE ownerModuleTable; - MIB_TCPROW_OWNER_MODULE[] TcpRows; - Dictionary processes = new Dictionary(); - - Console.WriteLine("\r\n\r\n=== Active TCP Network Connections ===\r\n"); - - try - { - // Adapted from https://stackoverflow.com/questions/577433/which-pid-listens-on-a-given-port-in-c-sharp/577660#577660 - // Build a PID -> process name lookup table - ManagementObjectSearcher searcher = new ManagementObjectSearcher("SELECT * FROM Win32_Process"); - ManagementObjectCollection retObjectCollection = searcher.Get(); - - foreach (ManagementObject Process in retObjectCollection) - { - if (Process["CommandLine"] != null) - { - processes.Add(Process["ProcessId"].ToString(), Process["CommandLine"].ToString()); - } - else - { - processes.Add(Process["ProcessId"].ToString(), Process["Name"].ToString()); - } - } - - // Figure out how much memory we need for the result struct - ret = GetExtendedTcpTable(IntPtr.Zero, ref tableBufferSize, true, AF_INET, TCP_TABLE_CLASS.TCP_TABLE_OWNER_MODULE_ALL, 0); - if (ret != ERROR_SUCCESS && ret != ERROR_INSUFFICIENT_BUFFER) - { - // 122 == insufficient buffer size - Console.WriteLine(" [X] Bad check value from GetExtendedTcpTable : {0}", ret); - return; - } - - tableBuffer = Marshal.AllocHGlobal((int)tableBufferSize); - - ret = GetExtendedTcpTable(tableBuffer, ref tableBufferSize, true, AF_INET, TCP_TABLE_CLASS.TCP_TABLE_OWNER_MODULE_ALL, 0); - if (ret != ERROR_SUCCESS) - { - Console.WriteLine(" [X] Bad return value from GetExtendedTcpTable : {0}", ret); - return; - } - - // get the number of entries in the table - ownerModuleTable = (MIB_TCPTABLE_OWNER_MODULE)Marshal.PtrToStructure(tableBuffer, typeof(MIB_TCPTABLE_OWNER_MODULE)); - rowPtr = (IntPtr)(tableBuffer.ToInt64() + Marshal.OffsetOf(typeof(MIB_TCPTABLE_OWNER_MODULE), "Table").ToInt64()); - TcpRows = new MIB_TCPROW_OWNER_MODULE[ownerModuleTable.NumEntries]; - - for (int i = 0; i < ownerModuleTable.NumEntries; i++) - { - MIB_TCPROW_OWNER_MODULE tcpRow = - (MIB_TCPROW_OWNER_MODULE)Marshal.PtrToStructure(rowPtr, typeof(MIB_TCPROW_OWNER_MODULE)); - TcpRows[i] = tcpRow; - // next entry - rowPtr = (IntPtr)((long)rowPtr + Marshal.SizeOf(tcpRow)); - } - - Console.WriteLine(" Local Address Foreign Address State PID Service ProcessName"); - foreach (MIB_TCPROW_OWNER_MODULE entry in TcpRows) - { - string processName = ""; - try - { - processName = processes[entry.OwningPid.ToString()]; - } - catch { } - - string serviceName = GetServiceNameFromTag(entry.OwningPid, (uint)entry.OwningModuleInfo0); - - Console.WriteLine(String.Format(" {0,-23}{1,-23}{2,-11}{3,-6}{4,-15} {5}", entry.LocalAddress + ":" + entry.LocalPort, entry.RemoteAddress + ":" + entry.RemotePort, entry.State, entry.OwningPid, serviceName, processName)); - } - } - catch (Exception ex) - { - Console.WriteLine(" [X] Exception: {0}", ex.Message); - } - finally - { - if (tableBuffer != IntPtr.Zero) - { - Marshal.FreeHGlobal(tableBuffer); - } - } - } - - public static void ListAllUdpConnections() - { - int AF_INET = 2; // IP_v4 - uint tableBufferSize = 0; - uint ret = 0; - IntPtr tableBuffer = IntPtr.Zero; - IntPtr rowPtr = IntPtr.Zero; - MIB_UDPTABLE_OWNER_MODULE ownerModuleTable; - MIB_UDPROW_OWNER_MODULE[] UdpRows; - Dictionary processes = new Dictionary(); - - Console.WriteLine("\r\n\r\n=== Active UDP Network Connections ===\r\n"); - - try - { - // Adapted from https://stackoverflow.com/questions/577433/which-pid-listens-on-a-given-port-in-c-sharp/577660#577660 - // Build a PID -> process name lookup table - ManagementObjectSearcher searcher = new ManagementObjectSearcher("SELECT * FROM Win32_Process"); - ManagementObjectCollection retObjectCollection = searcher.Get(); - - foreach (ManagementObject Process in retObjectCollection) - { - if (Process["CommandLine"] != null) - { - processes.Add(Process["ProcessId"].ToString(), Process["CommandLine"].ToString()); - } - else - { - processes.Add(Process["ProcessId"].ToString(), Process["Name"].ToString()); - } - } - - // Figure out how much memory we need for the result struct - ret = GetExtendedUdpTable(IntPtr.Zero, ref tableBufferSize, true, AF_INET, UDP_TABLE_CLASS.UDP_TABLE_OWNER_MODULE, 0); - if (ret != ERROR_SUCCESS && ret != ERROR_INSUFFICIENT_BUFFER) - { - // 122 == insufficient buffer size - Console.WriteLine(" [X] Bad check value from GetExtendedUdpTable : {0}", ret); - return; - } - - tableBuffer = Marshal.AllocHGlobal((int)tableBufferSize); - - ret = GetExtendedUdpTable(tableBuffer, ref tableBufferSize, true, AF_INET, UDP_TABLE_CLASS.UDP_TABLE_OWNER_MODULE, 0); - if (ret != ERROR_SUCCESS) - { - Console.WriteLine(" [X] Bad return value from GetExtendedUdpTable : {0}", ret); - return; - } - - // get the number of entries in the table - ownerModuleTable = (MIB_UDPTABLE_OWNER_MODULE)Marshal.PtrToStructure(tableBuffer, typeof(MIB_UDPTABLE_OWNER_MODULE)); - rowPtr = (IntPtr)(tableBuffer.ToInt64() + Marshal.OffsetOf(typeof(MIB_UDPTABLE_OWNER_MODULE), "Table").ToInt64()); - UdpRows = new MIB_UDPROW_OWNER_MODULE[ownerModuleTable.NumEntries]; - - for (int i = 0; i < ownerModuleTable.NumEntries; i++) - { - MIB_UDPROW_OWNER_MODULE udpRow = - (MIB_UDPROW_OWNER_MODULE)Marshal.PtrToStructure(rowPtr, typeof(MIB_UDPROW_OWNER_MODULE)); - UdpRows[i] = udpRow; - // next entry - rowPtr = (IntPtr)((long)rowPtr + Marshal.SizeOf(udpRow)); - } - - Console.WriteLine(" Local Address PID Service ProcessName"); - foreach (MIB_UDPROW_OWNER_MODULE entry in UdpRows) - { - string processName = ""; - try - { - processName = processes[entry.OwningPid.ToString()]; - } - catch { } - - string serviceName = GetServiceNameFromTag(entry.OwningPid, (uint)entry.OwningModuleInfo0); - - Console.WriteLine(String.Format(" {0,-23}{1,-7}{2,-23} {3}", entry.LocalAddress + ":" + entry.LocalPort, entry.OwningPid, serviceName, processName)); - } - } - catch (Exception ex) - { - Console.WriteLine(" [X] Exception: {0}", ex.Message); - } - finally - { - if (tableBuffer != IntPtr.Zero) - { - Marshal.FreeHGlobal(tableBuffer); - } - } - } - - public static void ListNonstandardProcesses() - { - // lists currently running processes that don't have "Microsoft Corporation" as the company name in their file info - // or all processes if "full" is passed - - if (FilterResults.filter) - { - Console.WriteLine("\r\n\r\n=== Non Microsoft Processes (via WMI) ===\r\n"); - } - else - { - Console.WriteLine("\r\n\r\n=== All Processes (via WMI) ===\r\n"); - } - - try - { - var wmiQueryString = "SELECT ProcessId, ExecutablePath, CommandLine FROM Win32_Process"; - using (var searcher = new ManagementObjectSearcher(wmiQueryString)) - using (var results = searcher.Get()) - { - var query = from p in Process.GetProcesses() - join mo in results.Cast() - on p.Id equals (int)(uint)mo["ProcessId"] - select new - { - Process = p, - Path = (string)mo["ExecutablePath"], - CommandLine = (string)mo["CommandLine"], - }; - foreach (var item in query) - { - //OLD - if ((item.Path != null) && ((!FilterResults.filter) || (!Regex.IsMatch(item.Path, "C:\\\\WINDOWS\\\\", RegexOptions.IgnoreCase)))) - if ((item.Path != null)) { - FileVersionInfo myFileVersionInfo = FileVersionInfo.GetVersionInfo(item.Path); - string companyName = myFileVersionInfo.CompanyName; - if ((String.IsNullOrEmpty(companyName)) || (!FilterResults.filter) || (!Regex.IsMatch(companyName, @"^Microsoft.*", RegexOptions.IgnoreCase))) - { - bool isDotNet = false; - try - { - AssemblyName myAssemblyName = AssemblyName.GetAssemblyName(item.Path); - isDotNet = true; - } - catch (System.IO.FileNotFoundException) - { - // System.Console.WriteLine("The file cannot be found."); - } - catch (System.BadImageFormatException exception) - { - if (Regex.IsMatch(exception.Message, ".*This assembly is built by a runtime newer than the currently loaded runtime and cannot be loaded.*", RegexOptions.IgnoreCase)) - { - isDotNet = true; - } - } - catch - { - // System.Console.WriteLine("The assembly has already been loaded."); - } - - Console.WriteLine(" Name : {0}", item.Process.ProcessName); - Console.WriteLine(" Company Name : {0}", companyName); - Console.WriteLine(" PID : {0}", item.Process.Id); - Console.WriteLine(" Path : {0}", item.Path); - Console.WriteLine(" CommandLine : {0}", item.CommandLine); - Console.WriteLine(" IsDotNet : {0}\r\n", isDotNet); - } - } - } - } - } - catch (Exception ex) - { - Console.WriteLine(" [X] Exception: {0}", ex.Message); - } - } - - - // elevated system checks - public static void List4624Events() - { - var eventId = "4624"; - - // grab events from the last X days - 7 for default, 30 for "full" collection - int lastDays = 7; - - if (!FilterResults.filter) - { - lastDays = 30; - } - - var startTime = System.DateTime.Now.AddDays(-lastDays); - var endTime = System.DateTime.Now; - - Console.WriteLine("\r\n\r\n=== 4624 Account Logon Events (last {0} days) ===\r\n", lastDays); - - var query = string.Format(@"*[System/EventID={0}] and *[System[TimeCreated[@SystemTime >= '{1}']]] and *[System[TimeCreated[@SystemTime <= '{2}']]]", - eventId, - startTime.ToUniversalTime().ToString("o"), - endTime.ToUniversalTime().ToString("o")); - - EventLogQuery eventsQuery = new EventLogQuery("Security", PathType.LogName, query); - eventsQuery.ReverseDirection = true; - - try - { - EventLogReader logReader = new EventLogReader(eventsQuery); - - for (EventRecord eventdetail = logReader.ReadEvent(); eventdetail != null; eventdetail = logReader.ReadEvent()) - { - //string SubjectUserSid = eventdetail.Properties[0].Value.ToString(); - //string SubjectUserName = eventdetail.Properties[1].Value.ToString(); - //string SubjectDomainName = eventdetail.Properties[2].Value.ToString(); - //string SubjectLogonId = eventdetail.Properties[3].Value.ToString(); - string TargetUserSid = eventdetail.Properties[4].Value.ToString(); - string TargetUserName = eventdetail.Properties[5].Value.ToString(); - string TargetDomainName = eventdetail.Properties[6].Value.ToString(); - //string TargetLogonId = eventdetail.Properties[7].Value.ToString(); - //string LogonType = eventdetail.Properties[8].Value.ToString(); - string LogonType = String.Format("{0}", (SECURITY_LOGON_TYPE)(Int32.Parse(eventdetail.Properties[8].Value.ToString()))); - //string LogonProcessName = eventdetail.Properties[9].Value.ToString(); - string AuthenticationPackageName = eventdetail.Properties[10].Value.ToString(); - string WorkstationName = eventdetail.Properties[11].Value.ToString(); - //string LogonGuid = eventdetail.Properties[12].Value.ToString(); - //string TransmittedServices = eventdetail.Properties[13].Value.ToString(); - string LmPackageName = eventdetail.Properties[14].Value.ToString(); - //string KeyLength = eventdetail.Properties[15].Value.ToString(); - //string ProcessId = eventdetail.Properties[16].Value.ToString(); - string ProcessName = eventdetail.Properties[17].Value.ToString(); - //string IpAddress = eventdetail.Properties[18].Value.ToString(); - //string IpPort = eventdetail.Properties[19].Value.ToString(); - //string ImpersonationLevel = eventdetail.Properties[20].Value.ToString(); - //string RestrictedAdminMode = eventdetail.Properties[21].Value.ToString(); - //string TargetOutboundUserName = eventdetail.Properties[22].Value.ToString(); - //string TargetOutboundDomainName = eventdetail.Properties[23].Value.ToString(); - //string VirtualAccount = eventdetail.Properties[24].Value.ToString(); - //string TargetLinkedLogonId = eventdetail.Properties[25].Value.ToString(); - //string ElevatedToken = eventdetail.Properties[26].Value.ToString(); - - // filter out SYSTEM, computer accounts, local service accounts, UMFD-X accounts, and DWM-X accounts (for now) - Regex ignoreRegex = new Regex(@"SYSTEM|\$$|LOCAL SERVICE|NETWORK SERVICE|UMFD-[0-9]+|DWM-[0-9]+|ANONYMOUS LOGON"); - Match m = ignoreRegex.Match(TargetUserName); - if (!m.Success) - { - Console.WriteLine(" UserName : {0}", TargetUserName); - Console.WriteLine(" UserDomain : {0}", TargetDomainName); - Console.WriteLine(" UserSID : {0}", TargetUserSid); - Console.WriteLine(" ProcessName : {0}", ProcessName); - Console.WriteLine(" LogonType : {0}", LogonType); - Console.WriteLine(" AuthPKG : {0}", AuthenticationPackageName); - Console.WriteLine(" LmPackageName : {0}", LmPackageName); - Console.WriteLine(" WorkstationName : {0}", WorkstationName); - Console.WriteLine(" TimeCreated : {0}\r\n", eventdetail.TimeCreated.ToString()); - - //Console.WriteLine(eventdetail.FormatDescription()); - //break; - } - } - } - catch (Exception ex) - { - Console.WriteLine(" [X] Exception: {0}", ex.Message); - } - } - - public static void List4648Events() - { - var eventId = "4648"; - - // grab events from the last X days - 7 for default, 30 for "full" collection - int lastDays = 7; - - if (!FilterResults.filter) - { - lastDays = 30; - } - - var startTime = System.DateTime.Now.AddDays(-lastDays); - var endTime = System.DateTime.Now; - - Console.WriteLine("\r\n\r\n=== 4624 Explicit Credential Events (last {0} days) - Runas or Outbound RDP ===\r\n", lastDays); - - - var query = string.Format(@"*[System/EventID={0}] and *[System[TimeCreated[@SystemTime >= '{1}']]] and *[System[TimeCreated[@SystemTime <= '{2}']]]", - eventId, - startTime.ToUniversalTime().ToString("o"), - endTime.ToUniversalTime().ToString("o")); - - EventLogQuery eventsQuery = new EventLogQuery("Security", PathType.LogName, query); - eventsQuery.ReverseDirection = true; - - try - { - EventLogReader logReader = new EventLogReader(eventsQuery); - - for (EventRecord eventdetail = logReader.ReadEvent(); eventdetail != null; eventdetail = logReader.ReadEvent()) - { - string SubjectUserSid = eventdetail.Properties[0].Value.ToString(); - string SubjectUserName = eventdetail.Properties[1].Value.ToString(); - string SubjectDomainName = eventdetail.Properties[2].Value.ToString(); - //string SubjectLogonId = eventdetail.Properties[3].Value.ToString(); - //string LogonGuid = eventdetail.Properties[4].Value.ToString(); - string TargetUserName = eventdetail.Properties[5].Value.ToString(); - string TargetDomainName = eventdetail.Properties[6].Value.ToString(); - //string TargetLogonGuid = eventdetail.Properties[7].Value.ToString(); - string TargetServerName = eventdetail.Properties[8].Value.ToString(); - //string TargetInfo = eventdetail.Properties[9].Value.ToString(); - //string ProcessId = eventdetail.Properties[10].Value.ToString(); - string ProcessName = eventdetail.Properties[11].Value.ToString(); - //string IpAddress = eventdetail.Properties[12].Value.ToString(); - //string IpPort = eventdetail.Properties[13].Value.ToString(); - - // filter out accounts (for now) - Regex ignoreRegex = new Regex(@"\$$"); - Match m = ignoreRegex.Match(SubjectUserName); - if (!m.Success) - { - Console.WriteLine(" SubjectUserName : {0}", SubjectUserName); - Console.WriteLine(" SubjectDomainName : {0}", SubjectDomainName); - Console.WriteLine(" SubjectUserSid : {0}", SubjectUserSid); - Console.WriteLine(" TargetUserName : {0}", TargetUserName); - Console.WriteLine(" TargetDomainName : {0}", TargetDomainName); - Console.WriteLine(" TargetServerName : {0}", TargetServerName); - Console.WriteLine(" ProcessName : {0}", ProcessName); - Console.WriteLine(" TimeCreated : {0}\r\n", eventdetail.TimeCreated.ToString()); - } - } - } - catch (Exception ex) - { - Console.WriteLine(" [X] Exception: {0}", ex.Message); - } - } - - public static void ListSysmonConfig() - { - Console.WriteLine("\r\n\r\n=== Sysmon Configuration ===\r\n"); - - string hashing = GetRegValue("HKLM", "SYSTEM\\CurrentControlSet\\Services\\SysmonDrv\\Parameters", "HashingAlgorithm"); - if (!String.IsNullOrEmpty(hashing)) - { - Console.WriteLine(" Hashing algorithm: {0}", hashing); - } - - string options = GetRegValue("HKLM", "SYSTEM\\CurrentControlSet\\Services\\SysmonDrv\\Parameters", "Options"); - if (!String.IsNullOrEmpty(options)) - { - Console.WriteLine(" Options: {0}", options); - } - - byte[] sysmonRules = GetRegValueBytes("HKLM", "SYSTEM\\CurrentControlSet\\Services\\SysmonDrv\\Parameters", "Rules"); - if (sysmonRules != null) - { - Console.WriteLine(" Sysmon rules: " + Convert.ToBase64String(sysmonRules)); - } - } - - - // user-focused checks - public static void ListCurrentDomainGroups() - { - try - { - Console.WriteLine("\r\n\r\n=== Current User's Groups ===\r\n"); - - WindowsIdentity wi = WindowsIdentity.GetCurrent(); - List groups = new List(); - - foreach (IdentityReference group in wi.Groups) - { - try - { - groups.Add(group.Translate(typeof(NTAccount)).ToString()); - } - catch { } - } - groups.Sort(); - foreach (string group in groups) - { - Console.WriteLine(" {0}", group); - } - } - catch (Exception ex) - { - Console.WriteLine(" [X] Exception: {0}", ex.Message); - } - } - - public static void ListSavedRDPConnections() - { - //shows saved RDP connections, including username hints (if present) - - if (IsHighIntegrity()) - { - string[] SIDs = Registry.Users.GetSubKeyNames(); - foreach (string SID in SIDs) - { - if (SID.StartsWith("S-1-5") && !SID.EndsWith("_Classes")) - { - string[] subkeys = GetRegSubkeys("HKU", String.Format("{0}\\Software\\Microsoft\\Terminal Server Client\\Servers", SID)); - if (subkeys != null) - { - Console.WriteLine("\r\n\r\n=== Saved RDP Connection Information ({0}) ===", SID); - foreach (string host in subkeys) - { - string usernameHint = GetRegValue("HKCU", String.Format("Software\\Microsoft\\Terminal Server Client\\Servers\\{0}", host), "UsernameHint"); - Console.WriteLine("\r\n Host : {0}", host); - if (usernameHint != "") - { - Console.WriteLine(" UsernameHint : {0}", usernameHint); - } - } - } - } - } - } - else - { - Console.WriteLine("\r\n\r\n=== Saved RDP Connection Information (Current User) ==="); - string[] subkeys = GetRegSubkeys("HKCU", "Software\\Microsoft\\Terminal Server Client\\Servers"); - if (subkeys != null) - { - foreach (string host in subkeys) - { - string usernameHint = GetRegValue("HKCU", String.Format("Software\\Microsoft\\Terminal Server Client\\Servers\\{0}", host), "UsernameHint"); - Console.WriteLine("\r\n Host : {0}", host); - if (usernameHint != "") - { - Console.WriteLine(" UsernameHint : {0}", usernameHint); - } - } - } - } - } - - public static void ListMasterKeys() - { - // lists any found DPAPI master keys - try - { - if (IsHighIntegrity()) - { - Console.WriteLine("\r\n\r\n=== Checking for DPAPI Master Keys (All Users) ===\r\n"); - - string userFolder = String.Format("{0}\\Users\\", Environment.GetEnvironmentVariable("SystemDrive")); - string[] dirs = Directory.GetDirectories(userFolder); - foreach (string dir in dirs) - { - string[] parts = dir.Split('\\'); - string userName = parts[parts.Length - 1]; - if (!(dir.EndsWith("Public") || dir.EndsWith("Default") || dir.EndsWith("Default User") || dir.EndsWith("All Users"))) - { - string userDPAPIBasePath = String.Format("{0}\\AppData\\Roaming\\Microsoft\\Protect\\", dir); - if (System.IO.Directory.Exists(userDPAPIBasePath)) - { - string[] directories = Directory.GetDirectories(userDPAPIBasePath); - foreach (string directory in directories) - { - string[] files = Directory.GetFiles(directory); - - Console.WriteLine(" Folder : {0}\r\n", directory); - - foreach (string file in files) - { - if (Regex.IsMatch(file, @"[0-9A-Fa-f]{8}[-][0-9A-Fa-f]{4}[-][0-9A-Fa-f]{4}[-][0-9A-Fa-f]{4}[-][0-9A-Fa-f]{12}")) - { - DateTime lastAccessed = System.IO.File.GetLastAccessTime(file); - DateTime lastModified = System.IO.File.GetLastWriteTime(file); - string fileName = System.IO.Path.GetFileName(file); - Console.WriteLine(" MasterKey : {0}", fileName); - Console.WriteLine(" Accessed : {0}", lastAccessed); - Console.WriteLine(" Modified : {0}\r\n", lastModified); - } - } - Console.WriteLine(); - } - } - } - } - Console.WriteLine(" [*] Use the Mimikatz \"dpapi::masterkey\" module with appropriate arguments (/pvk or /rpc) to decrypt"); - Console.WriteLine(" [*] You can also extract many DPAPI masterkeys from memory with the Mimikatz \"sekurlsa::dpapi\" module"); - } - else - { - Console.WriteLine("\r\n\r\n=== Checking for DPAPI Master Keys (Current User) ===\r\n"); - string userName = Environment.GetEnvironmentVariable("USERNAME"); - string userDPAPIBasePath = String.Format("{0}\\AppData\\Roaming\\Microsoft\\Protect\\", System.Environment.GetEnvironmentVariable("USERPROFILE")); - - if (System.IO.Directory.Exists(userDPAPIBasePath)) - { - string[] directories = Directory.GetDirectories(userDPAPIBasePath); - foreach (string directory in directories) - { - string[] files = Directory.GetFiles(directory); - - Console.WriteLine(" Folder : {0}\r\n", directory); - - foreach (string file in files) - { - if (Regex.IsMatch(file, @"[0-9A-Fa-f]{8}[-][0-9A-Fa-f]{4}[-][0-9A-Fa-f]{4}[-][0-9A-Fa-f]{4}[-][0-9A-Fa-f]{12}")) - { - DateTime lastAccessed = System.IO.File.GetLastAccessTime(file); - DateTime lastModified = System.IO.File.GetLastWriteTime(file); - string fileName = System.IO.Path.GetFileName(file); - Console.WriteLine(" MasterKey : {0}", fileName); - Console.WriteLine(" Accessed : {0}", lastAccessed); - Console.WriteLine(" Modified : {0}\r\n", lastModified); - } - } - } - } - Console.WriteLine(" [*] Use the Mimikatz \"dpapi::masterkey\" module with appropriate arguments (/rpc) to decrypt"); - } - } - catch (Exception ex) - { - Console.WriteLine(" [X] Exception: {0}", ex.Message); - } - } - - public static void ListCredFiles() - { - // lists any found files in Local\Microsoft\Credentials\* - try - { - if (IsHighIntegrity()) - { - Console.WriteLine("\r\n\r\n=== Checking for Credential Files (All Users) ===\r\n"); - - string userFolder = String.Format("{0}\\Users\\", Environment.GetEnvironmentVariable("SystemDrive")); - string[] dirs = Directory.GetDirectories(userFolder); - bool found = false; - - foreach (string dir in dirs) - { - string[] parts = dir.Split('\\'); - string userName = parts[parts.Length - 1]; - if (!(dir.EndsWith("Public") || dir.EndsWith("Default") || dir.EndsWith("Default User") || dir.EndsWith("All Users"))) - { - string userCredFilePath = String.Format("{0}\\AppData\\Local\\Microsoft\\Credentials\\", dir); - if (System.IO.Directory.Exists(userCredFilePath)) - { - string[] systemFiles = Directory.GetFiles(userCredFilePath); - if ((systemFiles != null) && (systemFiles.Length != 0)) - { - Console.WriteLine("\r\n Folder : {0}\r\n", userCredFilePath); - - foreach (string file in systemFiles) - { - DateTime lastAccessed = System.IO.File.GetLastAccessTime(file); - DateTime lastModified = System.IO.File.GetLastWriteTime(file); - long size = new System.IO.FileInfo(file).Length; - string fileName = System.IO.Path.GetFileName(file); - found = true; - Console.WriteLine(" CredFile : {0}", fileName); - - // jankily parse the bytes to extract the credential type and master key GUID - // reference- https://github.com/gentilkiwi/mimikatz/blob/3d8be22fff9f7222f9590aa007629e18300cf643/modules/kull_m_dpapi.h#L24-L54 - byte[] credentialArray = File.ReadAllBytes(file); - byte[] guidMasterKeyArray = new byte[16]; - Array.Copy(credentialArray, 36, guidMasterKeyArray, 0, 16); - Guid guidMasterKey = new Guid(guidMasterKeyArray); - - byte[] stringLenArray = new byte[16]; - Array.Copy(credentialArray, 56, stringLenArray, 0, 4); - int descLen = BitConverter.ToInt32(stringLenArray, 0); - - byte[] descBytes = new byte[descLen]; - Array.Copy(credentialArray, 60, descBytes, 0, descLen - 4); - - string desc = Encoding.Unicode.GetString(descBytes); - Console.WriteLine(" Description : {0}", desc); - Console.WriteLine(" MasterKey : {0}", guidMasterKey.ToString()); - Console.WriteLine(" Accessed : {0}", lastAccessed); - Console.WriteLine(" Modified : {0}", lastModified); - Console.WriteLine(" Size : {0}\r\n", size); - } - } - } - } - } - - string systemFolder = String.Format("{0}\\System32\\config\\systemprofile\\AppData\\Local\\Microsoft\\Credentials", Environment.GetEnvironmentVariable("SystemRoot")); - string[] files = Directory.GetFiles(systemFolder); - if ((files != null) && (files.Length != 0)) - { - Console.WriteLine("\r\n Folder : {0}\r\n", systemFolder); - - foreach (string file in files) - { - DateTime lastAccessed = System.IO.File.GetLastAccessTime(file); - DateTime lastModified = System.IO.File.GetLastWriteTime(file); - long size = new System.IO.FileInfo(file).Length; - string fileName = System.IO.Path.GetFileName(file); - found = true; - Console.WriteLine(" CredFile : {0}", fileName); - - // jankily parse the bytes to extract the credential type and master key GUID - // reference- https://github.com/gentilkiwi/mimikatz/blob/3d8be22fff9f7222f9590aa007629e18300cf643/modules/kull_m_dpapi.h#L24-L54 - byte[] credentialArray = File.ReadAllBytes(file); - byte[] guidMasterKeyArray = new byte[16]; - Array.Copy(credentialArray, 36, guidMasterKeyArray, 0, 16); - Guid guidMasterKey = new Guid(guidMasterKeyArray); - - byte[] stringLenArray = new byte[16]; - Array.Copy(credentialArray, 56, stringLenArray, 0, 4); - int descLen = BitConverter.ToInt32(stringLenArray, 0); - - byte[] descBytes = new byte[descLen]; - Array.Copy(credentialArray, 60, descBytes, 0, descLen - 4); - - string desc = Encoding.Unicode.GetString(descBytes); - Console.WriteLine(" Description : {0}", desc); - Console.WriteLine(" MasterKey : {0}", guidMasterKey.ToString()); - Console.WriteLine(" Accessed : {0}", lastAccessed); - Console.WriteLine(" Modified : {0}", lastModified); - Console.WriteLine(" Size : {0}\r\n", size); - } - } - - if (found) - { - Console.WriteLine(" [*] Use the Mimikatz \"dpapi::cred\" module with appropriate /masterkey to decrypt"); - Console.WriteLine(" [*] You can extract many DPAPI masterkeys from memory with the Mimikatz \"sekurlsa::dpapi\" module"); - } - } - else - { - Console.WriteLine("\r\n\r\n=== Checking for Credential Files (Current User) ===\r\n"); - string userName = Environment.GetEnvironmentVariable("USERNAME"); - string userCredFilePath = String.Format("{0}\\AppData\\Local\\Microsoft\\Credentials\\", System.Environment.GetEnvironmentVariable("USERPROFILE")); - bool found = false; - - if (System.IO.Directory.Exists(userCredFilePath)) - { - string[] files = Directory.GetFiles(userCredFilePath); - Console.WriteLine(" Folder : {0}\r\n", userCredFilePath); - - foreach (string file in files) - { - DateTime lastAccessed = System.IO.File.GetLastAccessTime(file); - DateTime lastModified = System.IO.File.GetLastWriteTime(file); - long size = new System.IO.FileInfo(file).Length; - string fileName = System.IO.Path.GetFileName(file); - found = true; - Console.WriteLine(" CredFile : {0}", fileName); - - // jankily parse the bytes to extract the credential type and master key GUID - // reference- https://github.com/gentilkiwi/mimikatz/blob/3d8be22fff9f7222f9590aa007629e18300cf643/modules/kull_m_dpapi.h#L24-L54 - byte[] credentialArray = File.ReadAllBytes(file); - byte[] guidMasterKeyArray = new byte[16]; - Array.Copy(credentialArray, 36, guidMasterKeyArray, 0, 16); - Guid guidMasterKey = new Guid(guidMasterKeyArray); - - byte[] stringLenArray = new byte[16]; - Array.Copy(credentialArray, 56, stringLenArray, 0, 4); - int descLen = BitConverter.ToInt32(stringLenArray, 0); - - byte[] descBytes = new byte[descLen]; - Array.Copy(credentialArray, 60, descBytes, 0, descLen - 4); - - string desc = Encoding.Unicode.GetString(descBytes); - Console.WriteLine(" Description : {0}", desc); - Console.WriteLine(" MasterKey : {0}", guidMasterKey.ToString()); - Console.WriteLine(" Accessed : {0}", lastAccessed); - Console.WriteLine(" Modified : {0}", lastModified); - Console.WriteLine(" Size : {0}\r\n", size); - } - } - if (found) - { - Console.WriteLine(" [*] Use the Mimikatz \"dpapi::cred\" module with appropriate /masterkey to decrypt"); - } - } - } - catch (Exception ex) - { - Console.WriteLine(" [X] Exception: {0}", ex.Message); - } - } - - public static void ListRDCManFiles() - { - // lists any found files in Local\Microsoft\Credentials\* - try - { - if (IsHighIntegrity()) - { - Console.WriteLine("\r\n\r\n=== Checking for RDCMan Settings Files (All Users) ===\r\n"); - - string userFolder = String.Format("{0}\\Users\\", Environment.GetEnvironmentVariable("SystemDrive")); - string[] dirs = Directory.GetDirectories(userFolder); - bool found = false; - - foreach (string dir in dirs) - { - string[] parts = dir.Split('\\'); - string userName = parts[parts.Length - 1]; - if (!(dir.EndsWith("Public") || dir.EndsWith("Default") || dir.EndsWith("Default User") || dir.EndsWith("All Users"))) - { - string userRDManFile = String.Format("{0}\\AppData\\Local\\Microsoft\\Remote Desktop Connection Manager\\RDCMan.settings", dir); - if (System.IO.File.Exists(userRDManFile)) - { - XmlDocument xmlDoc = new XmlDocument(); - xmlDoc.Load(userRDManFile); - - // grab the recent RDG files - XmlNodeList filesToOpen = xmlDoc.GetElementsByTagName("FilesToOpen"); - XmlNodeList items = filesToOpen[0].ChildNodes; - XmlNode node = items[0]; - - DateTime lastAccessed = System.IO.File.GetLastAccessTime(userRDManFile); - DateTime lastModified = System.IO.File.GetLastWriteTime(userRDManFile); - Console.WriteLine(" RDCManFile : {0}", userRDManFile); - Console.WriteLine(" Accessed : {0}", lastAccessed); - Console.WriteLine(" Modified : {0}", lastModified); - - foreach (XmlNode rdgFile in items) - { - found = true; - Console.WriteLine(" .RDG File : {0}", rdgFile.InnerText); - } - Console.WriteLine(); - } - } - } - - if (found) - { - Console.WriteLine(" [*] Use the Mimikatz \"dpapi::rdg\" module with appropriate /masterkey to decrypt any .rdg files"); - Console.WriteLine(" [*] You can extract many DPAPI masterkeys from memory with the Mimikatz \"sekurlsa::dpapi\" module"); - } - } - else - { - Console.WriteLine("\r\n\r\n=== Checking for RDCMan Settings Files (Current User) ===\r\n"); - bool found = false; - string userName = Environment.GetEnvironmentVariable("USERNAME"); - string userRDManFile = String.Format("{0}\\AppData\\Local\\Microsoft\\Remote Desktop Connection Manager\\RDCMan.settings", System.Environment.GetEnvironmentVariable("USERPROFILE")); - - if (System.IO.File.Exists(userRDManFile)) - { - XmlDocument xmlDoc = new XmlDocument(); - xmlDoc.Load(userRDManFile); - - // grab the recent RDG files - XmlNodeList filesToOpen = xmlDoc.GetElementsByTagName("FilesToOpen"); - XmlNodeList items = filesToOpen[0].ChildNodes; - XmlNode node = items[0]; - - DateTime lastAccessed = System.IO.File.GetLastAccessTime(userRDManFile); - DateTime lastModified = System.IO.File.GetLastWriteTime(userRDManFile); - Console.WriteLine(" RDCManFile : {0}", userRDManFile); - Console.WriteLine(" Accessed : {0}", lastAccessed); - Console.WriteLine(" Modified : {0}", lastModified); - - foreach (XmlNode rdgFile in items) - { - found = true; - Console.WriteLine(" .RDG File : {0}", rdgFile.InnerText); - } - Console.WriteLine(); - } - if (found) - { - Console.WriteLine(" [*] Use the Mimikatz \"dpapi::rdg\" module with appropriate /masterkey to decrypt any .rdg files"); - Console.WriteLine(" [*] You can extract many DPAPI masterkeys from memory with the Mimikatz \"sekurlsa::dpapi\" module"); - } - } - } - catch (Exception ex) - { - Console.WriteLine(" [X] Exception: {0}", ex.Message); - } - } - - public static void ListIETabs() - { - // Lists currently open Internet Explorer tabs, via COM - // Notes: - // https://searchcode.com/codesearch/view/9859954/ - // https://gist.github.com/yizhang82/a1268d3ea7295a8a1496e01d60ada816 - - Console.WriteLine("\r\n\r\n=== Internet Explorer Open Tabs ===\r\n"); - - try - { - // Shell.Application COM GUID - Type shell = Type.GetTypeFromCLSID(new Guid("13709620-C279-11CE-A49E-444553540000")); - - // actually instantiate the Shell.Application COM object - Object shellObj = Activator.CreateInstance(shell); - - // grab all the current windows - Object windows = shellObj.GetType().InvokeMember("Windows", BindingFlags.InvokeMethod, null, shellObj, null); - - // grab the open tab count - Object openTabs = windows.GetType().InvokeMember("Count", BindingFlags.GetProperty, null, windows, null); - int openTabsCount = Int32.Parse(openTabs.ToString()); - - for (int i = 0; i < openTabsCount; i++) - { - // grab the acutal tab - Object item = windows.GetType().InvokeMember("Item", BindingFlags.InvokeMethod, null, windows, new object[] { i }); - try - { - // extract the tab properties - Object locationName = item.GetType().InvokeMember("LocationName", BindingFlags.GetProperty, null, item, null); - Object locationURL = item.GetType().InvokeMember("LocationUrl", BindingFlags.GetProperty, null, item, null); - - // ensure we have a site address - if (Regex.IsMatch(locationURL.ToString(), @"(^https?://.+)|(^ftp://)")) - { - Console.WriteLine(" Location Name : {0}", locationName); - Console.WriteLine(" Location URL : {0}\r\n", locationURL); - } - Marshal.ReleaseComObject(item); - item = null; - } - catch - { - // - } - } - Marshal.ReleaseComObject(windows); - windows = null; - Marshal.ReleaseComObject(shellObj); - shellObj = null; - } - catch (Exception ex2) - { - Console.WriteLine(" [X] Exception: {0}", ex2); - } - } - public static void TriageIE() - { - // lists Internt explorer history (last 7 days by default) and favorites - - int lastDays = 7; - - if (!FilterResults.filter) - { - lastDays = 90; - } - - DateTime startTime = System.DateTime.Now.AddDays(-lastDays); - - try - { - if (IsHighIntegrity()) - { - Console.WriteLine("\r\n\r\n=== Internet Explorer (All Users) Last {0} Days ===", lastDays); - - string[] SIDs = Registry.Users.GetSubKeyNames(); - foreach (string SID in SIDs) - { - if (SID.StartsWith("S-1-5") && !SID.EndsWith("_Classes")) - { - Dictionary settings = GetRegValues("HKU", String.Format("{0}\\SOFTWARE\\Microsoft\\Internet Explorer\\TypedURLs", SID)); - if ((settings != null) && (settings.Count > 1)) - { - Console.WriteLine("\r\n History ({0}):", SID); - foreach (KeyValuePair kvp in settings) - { - byte[] timeBytes = GetRegValueBytes("HKU", String.Format("{0}\\SOFTWARE\\Microsoft\\Internet Explorer\\TypedURLsTime", SID), kvp.Key.ToString().Trim()); - if (timeBytes != null) - { - long timeLong = (long)(BitConverter.ToInt64(timeBytes, 0)); - DateTime urlTime = DateTime.FromFileTime(timeLong); - if (urlTime > startTime) - { - Console.WriteLine(" {0,-23} : {1}", urlTime, kvp.Value.ToString().Trim()); - } - } - } - } - } - } - - string userFolder = String.Format("{0}\\Users\\", Environment.GetEnvironmentVariable("SystemDrive")); - string[] dirs = Directory.GetDirectories(userFolder); - foreach (string dir in dirs) - { - string[] parts = dir.Split('\\'); - string userName = parts[parts.Length - 1]; - if (!(dir.EndsWith("Public") || dir.EndsWith("Default") || dir.EndsWith("Default User") || dir.EndsWith("All Users"))) - { - string userIEBookmarkPath = String.Format("{0}\\Favorites\\", dir); - - if (Directory.Exists(userIEBookmarkPath)) - { - string[] bookmarkPaths = Directory.GetFiles(userIEBookmarkPath, "*.url", SearchOption.AllDirectories); - if (bookmarkPaths.Length != 0) - { - Console.WriteLine("\r\n Favorites ({0}):", userName); - - foreach (string bookmarkPath in bookmarkPaths) - { - using (StreamReader rdr = new StreamReader(bookmarkPath)) - { - string line; - string url = ""; - while ((line = rdr.ReadLine()) != null) - { - if (line.StartsWith("URL=", StringComparison.InvariantCultureIgnoreCase)) - { - if (line.Length > 4) - url = line.Substring(4); - break; - } - } - Console.WriteLine(" {0}", url.ToString().Trim()); - } - } - } - } - } - } - } - else - { - Console.WriteLine("\r\n\r\n=== Internet Explorer (Current User) Last {0} Days ===", lastDays); - - Console.WriteLine("\r\n History:"); - Dictionary settings = GetRegValues("HKCU", "SOFTWARE\\Microsoft\\Internet Explorer\\TypedURLs"); - if ((settings != null) && (settings.Count != 0)) - { - foreach (KeyValuePair kvp in settings) - { - byte[] timeBytes = GetRegValueBytes("HKCU", "SOFTWARE\\Microsoft\\Internet Explorer\\TypedURLsTime", kvp.Key.ToString().Trim()); - if (timeBytes != null) - { - long timeLong = (long)(BitConverter.ToInt64(timeBytes, 0)); - DateTime urlTime = DateTime.FromFileTime(timeLong); - if (urlTime > startTime) - { - Console.WriteLine(" {0,-23} : {1}", urlTime, kvp.Value.ToString().Trim()); - } - } - } - } - - - Console.WriteLine("\r\n Favorites:"); - string userIEBookmarkPath = String.Format("{0}\\Favorites\\", System.Environment.GetEnvironmentVariable("USERPROFILE")); - - string[] bookmarkPaths = Directory.GetFiles(userIEBookmarkPath, "*.url", SearchOption.AllDirectories); - - foreach (string bookmarkPath in bookmarkPaths) - { - using (StreamReader rdr = new StreamReader(bookmarkPath)) - { - string line; - string url = ""; - while ((line = rdr.ReadLine()) != null) - { - if (line.StartsWith("URL=", StringComparison.InvariantCultureIgnoreCase)) - { - if (line.Length > 4) - url = line.Substring(4); - break; - } - } - Console.WriteLine(" {0}", url.ToString().Trim()); - } - } - } - } - catch (Exception ex) - { - Console.WriteLine(" [X] Exception: {0}", ex); - } - } - - - public static object GetVaultElementValue(IntPtr vaultElementPtr) - { - // Helper function to extract the ItemValue field from a VAULT_ITEM_ELEMENT struct - // pulled directly from @djhohnstein's SharpWeb project: https://github.com/djhohnstein/SharpWeb/blob/master/Edge/SharpEdge.cs - object results; - object partialElement = System.Runtime.InteropServices.Marshal.PtrToStructure(vaultElementPtr, typeof(VaultCli.VAULT_ITEM_ELEMENT)); - FieldInfo partialElementInfo = partialElement.GetType().GetField("Type"); - var partialElementType = partialElementInfo.GetValue(partialElement); - - IntPtr elementPtr = (IntPtr)(vaultElementPtr.ToInt64() + 16); - switch ((int)partialElementType) - { - case 7: // VAULT_ELEMENT_TYPE == String; These are the plaintext passwords! - IntPtr StringPtr = System.Runtime.InteropServices.Marshal.ReadIntPtr(elementPtr); - results = System.Runtime.InteropServices.Marshal.PtrToStringUni(StringPtr); - break; - case 0: // VAULT_ELEMENT_TYPE == bool - results = System.Runtime.InteropServices.Marshal.ReadByte(elementPtr); - results = (bool)results; - break; - case 1: // VAULT_ELEMENT_TYPE == Short - results = System.Runtime.InteropServices.Marshal.ReadInt16(elementPtr); - break; - case 2: // VAULT_ELEMENT_TYPE == Unsigned Short - results = System.Runtime.InteropServices.Marshal.ReadInt16(elementPtr); - break; - case 3: // VAULT_ELEMENT_TYPE == Int - results = System.Runtime.InteropServices.Marshal.ReadInt32(elementPtr); - break; - case 4: // VAULT_ELEMENT_TYPE == Unsigned Int - results = System.Runtime.InteropServices.Marshal.ReadInt32(elementPtr); - break; - case 5: // VAULT_ELEMENT_TYPE == Double - results = System.Runtime.InteropServices.Marshal.PtrToStructure(elementPtr, typeof(Double)); - break; - case 6: // VAULT_ELEMENT_TYPE == GUID - results = System.Runtime.InteropServices.Marshal.PtrToStructure(elementPtr, typeof(Guid)); - break; - case 12: // VAULT_ELEMENT_TYPE == Sid - IntPtr sidPtr = System.Runtime.InteropServices.Marshal.ReadIntPtr(elementPtr); - var sidObject = new System.Security.Principal.SecurityIdentifier(sidPtr); - results = sidObject.Value; - break; - default: - /* Several VAULT_ELEMENT_TYPES are currently unimplemented according to - * Lord Graeber. Thus we do not implement them. */ - results = null; - break; - } - return results; - } - public static void DumpVault() - { - // pulled directly from @djhohnstein's SharpWeb project: https://github.com/djhohnstein/SharpWeb/blob/master/Edge/SharpEdge.cs - Console.WriteLine("\r\n\r\n=== Checking Windows Vaults ==="); - var OSVersion = Environment.OSVersion.Version; - var OSMajor = OSVersion.Major; - var OSMinor = OSVersion.Minor; - - Type VAULT_ITEM; - - if (OSMajor >= 6 && OSMinor >= 2) - { - VAULT_ITEM = typeof(VaultCli.VAULT_ITEM_WIN8); - } - else - { - VAULT_ITEM = typeof(VaultCli.VAULT_ITEM_WIN7); - } - - Int32 vaultCount = 0; - IntPtr vaultGuidPtr = IntPtr.Zero; - var result = VaultCli.VaultEnumerateVaults(0, ref vaultCount, ref vaultGuidPtr); - - //var result = CallVaultEnumerateVaults(VaultEnum, 0, ref vaultCount, ref vaultGuidPtr); - - if ((int)result != 0) - { - Console.WriteLine(" [ERROR] Unable to enumerate vaults. Error (0x" + result.ToString() + ")"); - return; - } - - // Create dictionary to translate Guids to human readable elements - IntPtr guidAddress = vaultGuidPtr; - Dictionary vaultSchema = new Dictionary(); - vaultSchema.Add(new Guid("2F1A6504-0641-44CF-8BB5-3612D865F2E5"), "Windows Secure Note"); - vaultSchema.Add(new Guid("3CCD5499-87A8-4B10-A215-608888DD3B55"), "Windows Web Password Credential"); - vaultSchema.Add(new Guid("154E23D0-C644-4E6F-8CE6-5069272F999F"), "Windows Credential Picker Protector"); - vaultSchema.Add(new Guid("4BF4C442-9B8A-41A0-B380-DD4A704DDB28"), "Web Credentials"); - vaultSchema.Add(new Guid("77BC582B-F0A6-4E15-4E80-61736B6F3B29"), "Windows Credentials"); - vaultSchema.Add(new Guid("E69D7838-91B5-4FC9-89D5-230D4D4CC2BC"), "Windows Domain Certificate Credential"); - vaultSchema.Add(new Guid("3E0E35BE-1B77-43E7-B873-AED901B6275B"), "Windows Domain Password Credential"); - vaultSchema.Add(new Guid("3C886FF3-2669-4AA2-A8FB-3F6759A77548"), "Windows Extended Credential"); - vaultSchema.Add(new Guid("00000000-0000-0000-0000-000000000000"), null); - - for (int i = 0; i < vaultCount; i++) - { - // Open vault block - object vaultGuidString = System.Runtime.InteropServices.Marshal.PtrToStructure(guidAddress, typeof(Guid)); - Guid vaultGuid = new Guid(vaultGuidString.ToString()); - guidAddress = (IntPtr)(guidAddress.ToInt64() + System.Runtime.InteropServices.Marshal.SizeOf(typeof(Guid))); - IntPtr vaultHandle = IntPtr.Zero; - string vaultType; - if (vaultSchema.ContainsKey(vaultGuid)) - { - vaultType = vaultSchema[vaultGuid]; - } - else - { - vaultType = vaultGuid.ToString(); - } - result = VaultCli.VaultOpenVault(ref vaultGuid, (UInt32)0, ref vaultHandle); - if (result != 0) - { - Console.WriteLine(" [ERROR] Unable to open the following vault: " + vaultType + ". Error: 0x" + result.ToString()); - return; - } - // Vault opened successfully! Continue. - - - Console.WriteLine("\r\n Vault GUID : {0}", vaultGuid); - Console.WriteLine(" Vault Type : {0}\r\n", vaultType); - - // Fetch all items within Vault - int vaultItemCount = 0; - IntPtr vaultItemPtr = IntPtr.Zero; - result = VaultCli.VaultEnumerateItems(vaultHandle, 512, ref vaultItemCount, ref vaultItemPtr); - if (result != 0) - { - Console.WriteLine(" [ERROR] Unable to enumerate vault items from the following vault: " + vaultType + ". Error 0x" + result.ToString()); - return; - } - var structAddress = vaultItemPtr; - if (vaultItemCount > 0) - { - // For each vault item... - for (int j = 1; j <= vaultItemCount; j++) - { - // Begin fetching vault item... - var currentItem = System.Runtime.InteropServices.Marshal.PtrToStructure(structAddress, VAULT_ITEM); - structAddress = (IntPtr)(structAddress.ToInt64() + System.Runtime.InteropServices.Marshal.SizeOf(VAULT_ITEM)); - - IntPtr passwordVaultItem = IntPtr.Zero; - // Field Info retrieval - FieldInfo schemaIdInfo = currentItem.GetType().GetField("SchemaId"); - Guid schemaId = new Guid(schemaIdInfo.GetValue(currentItem).ToString()); - FieldInfo pResourceElementInfo = currentItem.GetType().GetField("pResourceElement"); - IntPtr pResourceElement = (IntPtr)pResourceElementInfo.GetValue(currentItem); - FieldInfo pIdentityElementInfo = currentItem.GetType().GetField("pIdentityElement"); - IntPtr pIdentityElement = (IntPtr)pIdentityElementInfo.GetValue(currentItem); - FieldInfo dateTimeInfo = currentItem.GetType().GetField("LastModified"); - UInt64 lastModified = (UInt64)dateTimeInfo.GetValue(currentItem); - - IntPtr pPackageSid = IntPtr.Zero; - if (OSMajor >= 6 && OSMinor >= 2) - { - // Newer versions have package sid - FieldInfo pPackageSidInfo = currentItem.GetType().GetField("pPackageSid"); - pPackageSid = (IntPtr)pPackageSidInfo.GetValue(currentItem); - result = VaultCli.VaultGetItem_WIN8(vaultHandle, ref schemaId, pResourceElement, pIdentityElement, pPackageSid, IntPtr.Zero, 0, ref passwordVaultItem); - } - else - { - result = VaultCli.VaultGetItem_WIN7(vaultHandle, ref schemaId, pResourceElement, pIdentityElement, IntPtr.Zero, 0, ref passwordVaultItem); - } - - if (result != 0) - { - Console.WriteLine(" [ERROR] occured while retrieving vault item. Error: 0x" + result.ToString()); - return; - } - object passwordItem = System.Runtime.InteropServices.Marshal.PtrToStructure(passwordVaultItem, VAULT_ITEM); - FieldInfo pAuthenticatorElementInfo = passwordItem.GetType().GetField("pAuthenticatorElement"); - IntPtr pAuthenticatorElement = (IntPtr)pAuthenticatorElementInfo.GetValue(passwordItem); - // Fetch the credential from the authenticator element - object cred = GetVaultElementValue(pAuthenticatorElement); - object packageSid = null; - if (pPackageSid != IntPtr.Zero && pPackageSid != null) - { - packageSid = GetVaultElementValue(pPackageSid); - } - if (cred != null) // Indicates successful fetch - { - // Console.WriteLine(" --- IE/Edge Credential ---"); - // Console.WriteLine(" Vault Type : {0}", vaultType); - object resource = GetVaultElementValue(pResourceElement); - if (resource != null) - { - Console.WriteLine(" Resource : {0}", resource); - } - object identity = GetVaultElementValue(pIdentityElement); - if (identity != null) - { - Console.WriteLine(" Identity : {0}", identity); - } - if (packageSid != null) - { - Console.WriteLine(" PacakgeSid : {0}", packageSid); - } - Console.WriteLine(" Credential : {0}", cred); - // Stupid datetime - Console.WriteLine(" LastModified : {0}", System.DateTime.FromFileTimeUtc((long)lastModified)); - Console.WriteLine(); - } - } - } - } - } - - public static void CheckChrome() - { - // checks if Chrome has a history database - try - { - if (IsHighIntegrity()) - { - Console.WriteLine("\r\n\r\n=== Checking for Chrome (All Users) ===\r\n"); - - string userFolder = String.Format("{0}\\Users\\", Environment.GetEnvironmentVariable("SystemDrive")); - string[] dirs = Directory.GetDirectories(userFolder); - foreach (string dir in dirs) - { - bool found = false; - string[] parts = dir.Split('\\'); - string userName = parts[parts.Length - 1]; - if (!(dir.EndsWith("Public") || dir.EndsWith("Default") || dir.EndsWith("Default User") || dir.EndsWith("All Users"))) - { - string userChromeHistoryPath = String.Format("{0}\\AppData\\Local\\Google\\Chrome\\User Data\\Default\\History", dir); - if (System.IO.File.Exists(userChromeHistoryPath)) - { - Console.WriteLine(" [*] Chrome history file exists at {0}", userChromeHistoryPath); - Console.WriteLine(" Run the 'TriageChrome' command\r\n"); - found = true; - } - string userChromeCookiesPath = String.Format("{0}\\AppData\\Local\\Google\\Chrome\\User Data\\Default\\Cookies", dir); - if (System.IO.File.Exists(userChromeCookiesPath)) - { - Console.WriteLine(" [*] Chrome cookies database exists at {0}", userChromeCookiesPath); - Console.WriteLine(" Run the Mimikatz \"dpapi::chrome\" module\r\n"); - found = true; - } - string userChromeLoginDataPath = String.Format("{0}\\AppData\\Local\\Google\\Chrome\\User Data\\Default\\Login Data", dir); - if (System.IO.File.Exists(userChromeLoginDataPath)) - { - Console.WriteLine(" [*] Chrome saved login database exists at {0}", userChromeLoginDataPath); - Console.WriteLine(" Run the Mimikatz \"dpapi::chrome\" module or SharpWeb (https://github.com/djhohnstein/SharpWeb)\r\n"); - found = true; - } - if (found) - { - Console.WriteLine(); - } - } - } - } - else - { - Console.WriteLine("\r\n\r\n=== Checking for Chrome (Current User) ===\r\n"); - string userChromeHistoryPath = String.Format("{0}\\AppData\\Local\\Google\\Chrome\\User Data\\Default\\History", System.Environment.GetEnvironmentVariable("USERPROFILE")); - if (System.IO.File.Exists(userChromeHistoryPath)) - { - Console.WriteLine(" [*] Chrome history file exists at {0}", userChromeHistoryPath); - Console.WriteLine(" Run the 'TriageChrome' command\r\n"); - } - string userChromeCookiesPath = String.Format("{0}\\AppData\\Local\\Google\\Chrome\\User Data\\Default\\Cookies", System.Environment.GetEnvironmentVariable("USERPROFILE")); - if (System.IO.File.Exists(userChromeCookiesPath)) - { - Console.WriteLine(" [*] Chrome cookies database exists at {0}", userChromeCookiesPath); - Console.WriteLine(" Run the Mimikatz \"dpapi::chrome\" module\r\n"); - } - string userChromeLoginDataPath = String.Format("{0}\\AppData\\Local\\Google\\Chrome\\User Data\\Default\\Login Data", System.Environment.GetEnvironmentVariable("USERPROFILE")); - if (System.IO.File.Exists(userChromeLoginDataPath)) - { - Console.WriteLine(" [*] Chrome saved login database exists at {0}", userChromeLoginDataPath); - Console.WriteLine(" Run the Mimikatz \"dpapi::chrome\" module or SharpWeb (https://github.com/djhohnstein/SharpWeb)"); - } - } - } - catch (Exception ex) - { - Console.WriteLine(" [X] Exception: {0}", ex.Message); - } - } - public static void ParseChromeHistory(string path, string user) - { - // parses a Chrome history file via regex - if (System.IO.File.Exists(path)) - { - Console.WriteLine("\r\n History ({0}):\r\n", user); - Regex historyRegex = new Regex(@"(http|ftp|https|file)://([\w_-]+(?:(?:\.[\w_-]+)+))([\w.,@?^=%&:/~+#-]*[\w@?^=%&/~+#-])?"); - - try - { - using (StreamReader r = new StreamReader(path)) - { - string line; - while ((line = r.ReadLine()) != null) - { - Match m = historyRegex.Match(line); - if (m.Success) - { - Console.WriteLine(" {0}", m.Groups[0].ToString().Trim()); - } - } - } - } - catch (System.IO.IOException exception) - { - Console.WriteLine("\r\n [x] IO exception, history file likely in use (i.e. Browser is likely running): ", exception.Message); - } - catch (Exception exception) - { - Console.WriteLine("\r\n [x] Exception: {0}", exception.Message); - } - } - } - public static void ParseChromeBookmarks(string path, string user) - { - // parses a Chrome bookmarks - if (System.IO.File.Exists(path)) - { - Console.WriteLine("\r\n Bookmarks ({0}):\r\n", user); - - try - { - string contents = System.IO.File.ReadAllText(path); - - // reference: http://www.tomasvera.com/programming/using-javascriptserializer-to-parse-json-objects/ - JavaScriptSerializer json = new JavaScriptSerializer(); - Dictionary deserialized = json.Deserialize>(contents); - Dictionary roots = (Dictionary)deserialized["roots"]; - Dictionary bookmark_bar = (Dictionary)roots["bookmark_bar"]; - System.Collections.ArrayList children = (System.Collections.ArrayList)bookmark_bar["children"]; - - foreach (Dictionary entry in children) - { - Console.WriteLine(" Name: {0}", entry["name"].ToString().Trim()); - Console.WriteLine(" Url: {0}\r\n", entry["url"].ToString().Trim()); - } - } - catch (System.IO.IOException exception) - { - Console.WriteLine("\r\n [x] IO exception, Bookmarks file likely in use (i.e. Chrome is likely running).", exception.Message); - } - catch (Exception exception) - { - Console.WriteLine("\r\n [x] Exception: {0}", exception.Message); - } - } - } - public static void TriageChrome() - { - try - { - if (IsHighIntegrity()) - { - Console.WriteLine("\r\n\r\n=== Chrome (All Users) ==="); - - string userFolder = String.Format("{0}\\Users\\", Environment.GetEnvironmentVariable("SystemDrive")); - string[] dirs = Directory.GetDirectories(userFolder); - foreach (string dir in dirs) - { - string[] parts = dir.Split('\\'); - string userName = parts[parts.Length - 1]; - if (!(dir.EndsWith("Public") || dir.EndsWith("Default") || dir.EndsWith("Default User") || dir.EndsWith("All Users"))) - { - string userChromeHistoryPath = String.Format("{0}\\AppData\\Local\\Google\\Chrome\\User Data\\Default\\History", dir); - ParseChromeHistory(userChromeHistoryPath, userName); - - string userChromeBookmarkPath = String.Format("{0}\\AppData\\Local\\Google\\Chrome\\User Data\\Default\\Bookmarks", dir); - ParseChromeBookmarks(userChromeBookmarkPath, userName); - } - } - } - else - { - Console.WriteLine("\r\n\r\n=== Chrome (Current User) ==="); - - string userChromeHistoryPath = String.Format("{0}\\AppData\\Local\\Google\\Chrome\\User Data\\Default\\History", System.Environment.GetEnvironmentVariable("USERPROFILE")); - ParseChromeHistory(userChromeHistoryPath, System.Environment.GetEnvironmentVariable("USERNAME")); - - string userChromeBookmarkPath = String.Format("{0}\\AppData\\Local\\Google\\Chrome\\User Data\\Default\\Bookmarks", System.Environment.GetEnvironmentVariable("USERPROFILE")); - - ParseChromeBookmarks(userChromeBookmarkPath, System.Environment.GetEnvironmentVariable("USERNAME")); - } - } - catch (Exception ex) - { - Console.WriteLine(" [X] Exception: {0}", ex.Message); - } - } - - public static void CheckFirefox() - { - // checks if Firefox has a history database - try - { - if (IsHighIntegrity()) - { - Console.WriteLine("\r\n\r\n=== Checking for Firefox (All Users) ===\r\n"); - - string userFolder = String.Format("{0}\\Users\\", Environment.GetEnvironmentVariable("SystemDrive")); - string[] dirs = Directory.GetDirectories(userFolder); - foreach (string dir in dirs) - { - string[] parts = dir.Split('\\'); - string userName = parts[parts.Length - 1]; - if (!(dir.EndsWith("Public") || dir.EndsWith("Default") || dir.EndsWith("Default User") || dir.EndsWith("All Users"))) - { - bool found = false; - string userFirefoxBasePath = String.Format("{0}\\AppData\\Roaming\\Mozilla\\Firefox\\Profiles\\", dir); - if (System.IO.Directory.Exists(userFirefoxBasePath)) - { - string[] directories = Directory.GetDirectories(userFirefoxBasePath); - foreach (string directory in directories) - { - string firefoxHistoryFile = String.Format("{0}\\{1}", directory, "places.sqlite"); - if (System.IO.File.Exists(firefoxHistoryFile)) - { - Console.WriteLine(" [*] Firefox history file exists at {0}", firefoxHistoryFile); - Console.WriteLine(" Run the 'TriageFirefox' command\r\n"); - found = true; - } - string firefoxCredentialFile3 = String.Format("{0}\\{1}", directory, "key3.db"); - if (System.IO.File.Exists(firefoxCredentialFile3)) - { - Console.WriteLine(" [*] Firefox credential file exists at {0}", firefoxCredentialFile3); - Console.WriteLine(" Run SharpWeb (https://github.com/djhohnstein/SharpWeb) \r\n"); - found = true; - } - string firefoxCredentialFile4 = String.Format("{0}\\{1}", directory, "key4.db"); - if (System.IO.File.Exists(firefoxCredentialFile4)) - { - Console.WriteLine(" [*] Firefox credential file exists at {0}", firefoxCredentialFile4); - Console.WriteLine(" Run SharpWeb (https://github.com/djhohnstein/SharpWeb) \r\n"); - found = true; - } - } - if (found) - { - Console.WriteLine(); - } - } - } - } - } - else - { - Console.WriteLine("\r\n\r\n=== Checking for Firefox (Current User) ===\r\n"); - string userName = Environment.GetEnvironmentVariable("USERNAME"); - string userFirefoxBasePath = String.Format("{0}\\AppData\\Roaming\\Mozilla\\Firefox\\Profiles\\", System.Environment.GetEnvironmentVariable("USERPROFILE")); - - if (System.IO.Directory.Exists(userFirefoxBasePath)) - { - string[] directories = Directory.GetDirectories(userFirefoxBasePath); - foreach (string directory in directories) - { - string firefoxHistoryFile = String.Format("{0}\\{1}", directory, "places.sqlite"); - if (System.IO.File.Exists(firefoxHistoryFile)) - { - Console.WriteLine(" [*] Firefox history file exists at {0}", firefoxHistoryFile); - Console.WriteLine(" Run the 'TriageFirefox' command\r\n"); - } - string firefoxCredentialFile3 = String.Format("{0}\\{1}", directory, "key3.db"); - if (System.IO.File.Exists(firefoxCredentialFile3)) - { - Console.WriteLine(" [*] Firefox credential file exists at {0}", firefoxCredentialFile3); - Console.WriteLine(" Run SharpWeb (https://github.com/djhohnstein/SharpWeb)\r\n"); - } - string firefoxCredentialFile4 = String.Format("{0}\\{1}", directory, "key4.db"); - if (System.IO.File.Exists(firefoxCredentialFile4)) - { - Console.WriteLine(" [*] Firefox credential file exists at {0}", firefoxCredentialFile4); - Console.WriteLine(" Run SharpWeb (https://github.com/djhohnstein/SharpWeb)\r\n"); - } - } - } - } - } - catch (Exception ex) - { - Console.WriteLine(" [X] Exception: {0}", ex.Message); - } - } - public static void ParseFirefoxHistory(string path, string user) - { - // parses a Firefox history file via regex - if (System.IO.Directory.Exists(path)) - { - string[] directories = Directory.GetDirectories(path); - foreach (string directory in directories) - { - string firefoxHistoryFile = String.Format("{0}\\{1}", directory, "places.sqlite"); - - Console.WriteLine("\r\n History ({0}):\r\n", user); - Regex historyRegex = new Regex(@"(http|ftp|https|file)://([\w_-]+(?:(?:\.[\w_-]+)+))([\w.,@?^=%&:/~+#-]*[\w@?^=%&/~+#-])?"); - - try - { - using (StreamReader r = new StreamReader(firefoxHistoryFile)) - { - string line; - while ((line = r.ReadLine()) != null) - { - Match m = historyRegex.Match(line); - if (m.Success) - { - Console.WriteLine(" {0}", m.Groups[0].ToString().Trim()); - } - } - } - } - catch (System.IO.IOException exception) - { - Console.WriteLine("\r\n [x] IO exception, places.sqlite file likely in use (i.e. Firefox is likely running).", exception.Message); - } - catch (Exception exception) - { - Console.WriteLine("\r\n [x] Exception: {0}", exception.Message); - } - } - } - } - public static void TriageFirefox() - { - try - { - if (IsHighIntegrity()) - { - Console.WriteLine("\r\n\r\n=== Firefox (All Users) ==="); - - string userFolder = String.Format("{0}\\Users\\", Environment.GetEnvironmentVariable("SystemDrive")); - string[] dirs = Directory.GetDirectories(userFolder); - foreach (string dir in dirs) - { - string[] parts = dir.Split('\\'); - string userName = parts[parts.Length - 1]; - if (!(dir.EndsWith("Public") || dir.EndsWith("Default") || dir.EndsWith("Default User") || dir.EndsWith("All Users"))) - { - string userFirefoxBasePath = String.Format("{0}\\AppData\\Roaming\\Mozilla\\Firefox\\Profiles\\", dir); - ParseFirefoxHistory(userFirefoxBasePath, userName); - } - } - } - else - { - Console.WriteLine("\r\n\r\n=== Firefox (Current User) ==="); - string userName = Environment.GetEnvironmentVariable("USERNAME"); - - string userFirefoxBasePath = String.Format("{0}\\AppData\\Roaming\\Mozilla\\Firefox\\Profiles\\", System.Environment.GetEnvironmentVariable("USERPROFILE")); - ParseFirefoxHistory(userFirefoxBasePath, userName); - } - } - catch (Exception ex) - { - Console.WriteLine(" [X] Exception: {0}", ex.Message); - } - } - - public static void ListRecentRunCommands() - { - // lists recently run commands via the RunMRU registry key - if (IsHighIntegrity()) - { - Console.WriteLine("\r\n\r\n=== Recent Typed RUN Commands (All Users) ==="); - - string[] SIDs = Registry.Users.GetSubKeyNames(); - foreach (string SID in SIDs) - { - if (SID.StartsWith("S-1-5") && !SID.EndsWith("_Classes")) - { - Dictionary recentCommands = GetRegValues("HKU", String.Format("{0}\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\RunMRU", SID)); - if ((recentCommands != null) && (recentCommands.Count != 0)) - { - Console.WriteLine("\r\n {0} :", SID); - foreach (KeyValuePair kvp in recentCommands) - { - Console.WriteLine(" {0,-10} : {1}", kvp.Key, kvp.Value); - } - } - } - } - } - else - { - Console.WriteLine("\r\n\r\n=== Recent Typed RUN Commands (Current User) ===\r\n"); - - Dictionary recentCommands = GetRegValues("HKCU", "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\RunMRU"); - if ((recentCommands != null) && (recentCommands.Count != 0)) - { - foreach (KeyValuePair kvp in recentCommands) - { - Console.WriteLine(" {0,-10} : {1}", kvp.Key, kvp.Value); - } - } - } - } - - public static void ListPuttySessions() - { - // extracts saved putty sessions and basic configs (via the registry) - if (IsHighIntegrity()) - { - Console.WriteLine("\r\n\r\n=== Putty Saved Session Information (All Users) ===\r\n"); - - string[] SIDs = Registry.Users.GetSubKeyNames(); - foreach (string SID in SIDs) - { - if (SID.StartsWith("S-1-5") && !SID.EndsWith("_Classes")) - { - string[] subKeys = GetRegSubkeys("HKU", String.Format("{0}\\Software\\SimonTatham\\PuTTY\\Sessions\\", SID)); - - foreach (string sessionName in subKeys) - { - Console.WriteLine(" {0,-20} : {1}", "User SID", SID); - Console.WriteLine(" {0,-20} : {1}", "SessionName", sessionName); - - string[] keys = - { - "HostName", - "UserName", - "PublicKeyFile", - "PortForwardings", - "ConnectionSharing" - }; - - foreach (string key in keys) - { - string result = GetRegValue("HKU", String.Format("{0}\\Software\\SimonTatham\\PuTTY\\Sessions\\{1}", SID, sessionName), key); - if (!String.IsNullOrEmpty(result)) - { - Console.WriteLine(" {0,-20} : {1}", key, result); - } - } - Console.WriteLine(); - } - } - } - } - else - { - Console.WriteLine("\r\n\r\n=== Putty Saved Session Information (Current User) ===\r\n"); - - string[] subKeys = GetRegSubkeys("HKCU", "Software\\SimonTatham\\PuTTY\\Sessions\\"); - foreach (string sessionName in subKeys) - { - Console.WriteLine(" {0,-20} : {1}", "SessionName", sessionName); - - string[] keys = - { - "HostName", - "UserName", - "PublicKeyFile", - "PortForwardings", - "ConnectionSharing" - }; - - foreach (string key in keys) - { - string result = GetRegValue("HKCU", String.Format("Software\\SimonTatham\\PuTTY\\Sessions\\{0}", sessionName), key); - if (!String.IsNullOrEmpty(result)) - { - Console.WriteLine(" {0,-20} : {1}", key, result); - } - } - Console.WriteLine(); - } - } - } - - public static void ListPuttySSHHostKeys() - { - // extracts saved putty host keys (via the registry) - if (IsHighIntegrity()) - { - Console.WriteLine("\r\n\r\n=== Putty SSH Host Hosts (All Users) ===\r\n"); - - string[] SIDs = Registry.Users.GetSubKeyNames(); - foreach (string SID in SIDs) - { - if (SID.StartsWith("S-1-5") && !SID.EndsWith("_Classes")) - { - Dictionary hostKeys = GetRegValues("HKU", String.Format("{0}\\Software\\SimonTatham\\PuTTY\\SshHostKeys\\", SID)); - if ((hostKeys != null) && (hostKeys.Count != 0)) - { - Console.WriteLine(" {0} :", SID); - foreach (KeyValuePair kvp in hostKeys) - { - Console.WriteLine(" {0,-10}", kvp.Key); - } - } - } - } - } - else - { - Console.WriteLine("\r\n\r\n=== Putty SSH Host Key Recent Hosts (Current User) ===\r\n"); - - Dictionary hostKeys = GetRegValues("HKCU", "Software\\SimonTatham\\PuTTY\\SshHostKeys\\"); - if ((hostKeys != null) && (hostKeys.Count != 0)) - { - foreach (KeyValuePair kvp in hostKeys) - { - Console.WriteLine(" {0,-10}", kvp.Key); - } - } - } - - //Console.WriteLine("\r\n\r\n=== Putty SSH Host Key Recent Hosts ===\r\n"); - - //Dictionary sessions = GetRegValues("HKCU", "Software\\SimonTatham\\PuTTY\\SshHostKeys\\"); - //if (sessions != null) - //{ - // foreach (KeyValuePair kvp in sessions) - // { - // Console.WriteLine(" {0,-10}", kvp.Key); - // } - //} - } - - public static void ListCloudCreds() - { - // checks for various cloud credential files (AWS, Microsoft Azure, and Google Compute) - // adapted from https://twitter.com/cmaddalena's SharpCloud project (https://github.com/chrismaddalena/SharpCloud/) - try - { - if (IsHighIntegrity()) - { - Console.WriteLine("\r\n\r\n=== Checking for Cloud Credentials (All Users) ===\r\n"); - - string userFolder = String.Format("{0}\\Users\\", Environment.GetEnvironmentVariable("SystemDrive")); - string[] dirs = Directory.GetDirectories(userFolder); - foreach (string dir in dirs) - { - bool found = false; - string[] parts = dir.Split('\\'); - string userName = parts[parts.Length - 1]; - if (!(dir.EndsWith("Public") || dir.EndsWith("Default") || dir.EndsWith("Default User") || dir.EndsWith("All Users"))) - { - string awsKeyFile = String.Format("{0}\\.aws\\credentials", dir); - if (System.IO.File.Exists(awsKeyFile)) - { - DateTime lastAccessed = System.IO.File.GetLastAccessTime(awsKeyFile); - DateTime lastModified = System.IO.File.GetLastWriteTime(awsKeyFile); - long size = new System.IO.FileInfo(awsKeyFile).Length; - Console.WriteLine(" [*] AWS key file exists at : {0}", awsKeyFile); - Console.WriteLine(" Accessed : {0}", lastAccessed); - Console.WriteLine(" Modified : {0}", lastModified); - Console.WriteLine(" Size : {0}\r\n", size); - found = true; - } - string computeCredsDb = String.Format("{0}\\AppData\\Roaming\\gcloud\\credentials.db", dir); - if (System.IO.File.Exists(computeCredsDb)) - { - DateTime lastAccessed = System.IO.File.GetLastAccessTime(computeCredsDb); - DateTime lastModified = System.IO.File.GetLastWriteTime(computeCredsDb); - long size = new System.IO.FileInfo(computeCredsDb).Length; - Console.WriteLine(" [*] Compute creds at : {0}", computeCredsDb); - Console.WriteLine(" Accessed : {0}", lastAccessed); - Console.WriteLine(" Modified : {0}", lastModified); - Console.WriteLine(" Size : {0}\r\n", size); - found = true; - } - string computeLegacyCreds = String.Format("{0}\\AppData\\Roaming\\gcloud\\legacy_credentials", dir); - if (System.IO.File.Exists(computeLegacyCreds)) - { - DateTime lastAccessed = System.IO.File.GetLastAccessTime(computeLegacyCreds); - DateTime lastModified = System.IO.File.GetLastWriteTime(computeLegacyCreds); - long size = new System.IO.FileInfo(computeLegacyCreds).Length; - Console.WriteLine(" [*] Compute legacy creds at : {0}", computeLegacyCreds); - Console.WriteLine(" Accessed : {0}", lastAccessed); - Console.WriteLine(" Modified : {0}", lastModified); - Console.WriteLine(" Size : {0}\r\n", size); - found = true; - } - string computeAccessTokensDb = String.Format("{0}\\AppData\\Roaming\\gcloud\\access_tokens.db", dir); - if (System.IO.File.Exists(computeAccessTokensDb)) - { - DateTime lastAccessed = System.IO.File.GetLastAccessTime(computeAccessTokensDb); - DateTime lastModified = System.IO.File.GetLastWriteTime(computeAccessTokensDb); - long size = new System.IO.FileInfo(computeAccessTokensDb).Length; - Console.WriteLine(" [*] Compute access tokens at : {0}", computeAccessTokensDb); - Console.WriteLine(" Accessed : {0}", lastAccessed); - Console.WriteLine(" Modified : {0}", lastModified); - Console.WriteLine(" Size : {0}\r\n", size); - found = true; - } - string azureTokens = String.Format("{0}\\.azure\\accessTokens.json", dir); - if (System.IO.File.Exists(azureTokens)) - { - DateTime lastAccessed = System.IO.File.GetLastAccessTime(azureTokens); - DateTime lastModified = System.IO.File.GetLastWriteTime(azureTokens); - long size = new System.IO.FileInfo(azureTokens).Length; - Console.WriteLine(" [*] Azure access tokens at : {0}", azureTokens); - Console.WriteLine(" Accessed : {0}", lastAccessed); - Console.WriteLine(" Modified : {0}", lastModified); - Console.WriteLine(" Size : {0}\r\n", size); - found = true; - } - string azureProfile = String.Format("{0}\\.azure\\azureProfile.json", dir); - if (System.IO.File.Exists(azureProfile)) - { - DateTime lastAccessed = System.IO.File.GetLastAccessTime(azureProfile); - DateTime lastModified = System.IO.File.GetLastWriteTime(azureProfile); - long size = new System.IO.FileInfo(azureProfile).Length; - Console.WriteLine(" [*] Azure profile at : {0}", azureProfile); - Console.WriteLine(" Accessed : {0}", lastAccessed); - Console.WriteLine(" Modified : {0}", lastModified); - Console.WriteLine(" Size : {0}\r\n", size); - found = true; - } - if (found) - { - System.Console.WriteLine(); - } - } - } - } - else - { - Console.WriteLine("\r\n\r\n=== Checking for Cloud Credentials (Current User) ===\r\n"); - - string awsKeyFile = String.Format("{0}\\.aws\\credentials", System.Environment.GetEnvironmentVariable("USERPROFILE")); - if (System.IO.File.Exists(awsKeyFile)) - { - DateTime lastAccessed = System.IO.File.GetLastAccessTime(awsKeyFile); - DateTime lastModified = System.IO.File.GetLastWriteTime(awsKeyFile); - long size = new System.IO.FileInfo(awsKeyFile).Length; - Console.WriteLine(" [*] AWS key file exists at : {0}", awsKeyFile); - Console.WriteLine(" Accessed : {0}", lastAccessed); - Console.WriteLine(" Modified : {0}", lastModified); - Console.WriteLine(" Size : {0}\r\n", size); - } - string computeCredsDb = String.Format("{0}\\AppData\\Roaming\\gcloud\\credentials.db", System.Environment.GetEnvironmentVariable("USERPROFILE")); - if (System.IO.File.Exists(computeCredsDb)) - { - DateTime lastAccessed = System.IO.File.GetLastAccessTime(computeCredsDb); - DateTime lastModified = System.IO.File.GetLastWriteTime(computeCredsDb); - long size = new System.IO.FileInfo(computeCredsDb).Length; - Console.WriteLine(" [*] Compute creds at : {0}", computeCredsDb); - Console.WriteLine(" Accessed : {0}", lastAccessed); - Console.WriteLine(" Modified : {0}", lastModified); - Console.WriteLine(" Size : {0}\r\n", size); - } - string computeLegacyCreds = String.Format("{0}\\AppData\\Roaming\\gcloud\\legacy_credentials", System.Environment.GetEnvironmentVariable("USERPROFILE")); - if (System.IO.File.Exists(computeLegacyCreds)) - { - DateTime lastAccessed = System.IO.File.GetLastAccessTime(computeLegacyCreds); - DateTime lastModified = System.IO.File.GetLastWriteTime(computeLegacyCreds); - long size = new System.IO.FileInfo(computeLegacyCreds).Length; - Console.WriteLine(" [*] Compute legacy creds at : {0}", computeLegacyCreds); - Console.WriteLine(" Accessed : {0}", lastAccessed); - Console.WriteLine(" Modified : {0}", lastModified); - Console.WriteLine(" Size : {0}\r\n", size); - } - string computeAccessTokensDb = String.Format("{0}\\AppData\\Roaming\\gcloud\\access_tokens.db", System.Environment.GetEnvironmentVariable("USERPROFILE")); - if (System.IO.File.Exists(computeAccessTokensDb)) - { - DateTime lastAccessed = System.IO.File.GetLastAccessTime(computeAccessTokensDb); - DateTime lastModified = System.IO.File.GetLastWriteTime(computeAccessTokensDb); - long size = new System.IO.FileInfo(computeAccessTokensDb).Length; - Console.WriteLine(" [*] Compute access tokens at : {0}", computeAccessTokensDb); - Console.WriteLine(" Accessed : {0}", lastAccessed); - Console.WriteLine(" Modified : {0}", lastModified); - Console.WriteLine(" Size : {0}\r\n", size); - } - string azureTokens = String.Format("{0}\\.azure\\accessTokens.json", System.Environment.GetEnvironmentVariable("USERPROFILE")); - if (System.IO.File.Exists(azureTokens)) - { - DateTime lastAccessed = System.IO.File.GetLastAccessTime(azureTokens); - DateTime lastModified = System.IO.File.GetLastWriteTime(azureTokens); - long size = new System.IO.FileInfo(azureTokens).Length; - Console.WriteLine(" [*] Azure access tokens at : {0}", azureTokens); - Console.WriteLine(" Accessed : {0}", lastAccessed); - Console.WriteLine(" Modified : {0}", lastModified); - Console.WriteLine(" Size : {0}\r\n", size); - } - string azureProfile = String.Format("{0}\\.azure\\azureProfile.json", System.Environment.GetEnvironmentVariable("USERPROFILE")); - if (System.IO.File.Exists(azureProfile)) - { - DateTime lastAccessed = System.IO.File.GetLastAccessTime(azureProfile); - DateTime lastModified = System.IO.File.GetLastWriteTime(azureProfile); - long size = new System.IO.FileInfo(azureProfile).Length; - Console.WriteLine(" [*] Azure profile at : {0}", azureProfile); - Console.WriteLine(" Accessed : {0}", lastAccessed); - Console.WriteLine(" Modified : {0}", lastModified); - Console.WriteLine(" Size : {0}\r\n", size); - } - } - } - catch (Exception ex) - { - Console.WriteLine(" [X] Exception: {0}", ex.Message); - } - } - - public static void ListRecentFiles() - { - // parses recent file shortcuts via COM - - int lastDays = 7; - - if (!FilterResults.filter) - { - lastDays = 30; - } - - DateTime startTime = System.DateTime.Now.AddDays(-lastDays); - - try - { - // WshShell COM object GUID - Type shell = Type.GetTypeFromCLSID(new Guid("F935DC22-1CF0-11d0-ADB9-00C04FD58A0B")); - Object shellObj = Activator.CreateInstance(shell); - - if (IsHighIntegrity()) - { - Console.WriteLine("\r\n\r\n=== Recently Accessed Files (All Users) Last {0} Days ===\r\n", lastDays); - - string userFolder = String.Format("{0}\\Users\\", Environment.GetEnvironmentVariable("SystemDrive")); - string[] dirs = Directory.GetDirectories(userFolder); - foreach (string dir in dirs) - { - string[] parts = dir.Split('\\'); - string userName = parts[parts.Length - 1]; - - if (!(dir.EndsWith("Public") || dir.EndsWith("Default") || dir.EndsWith("Default User") || dir.EndsWith("All Users"))) - { - string recentPath = String.Format("{0}\\AppData\\Roaming\\Microsoft\\Windows\\Recent\\", dir); - try - { - string[] recentFiles = Directory.GetFiles(recentPath, "*.lnk", SearchOption.AllDirectories); - - if (recentFiles.Length != 0) - { - Console.WriteLine(" {0} :\r\n", userName); - foreach (string recentFile in recentFiles) - { - DateTime lastAccessed = System.IO.File.GetLastAccessTime(recentFile); - - if (lastAccessed > startTime) - { - // invoke the WshShell com object, creating a shortcut to then extract the TargetPath from - Object shortcut = shellObj.GetType().InvokeMember("CreateShortcut", BindingFlags.InvokeMethod, null, shellObj, new object[] { recentFile }); - Object TargetPath = shortcut.GetType().InvokeMember("TargetPath", BindingFlags.GetProperty, null, shortcut, new object[] { }); - - if (TargetPath.ToString().Trim() != "") - { - Console.WriteLine(" Target: {0,-10}", TargetPath.ToString()); - Console.WriteLine(" Accessed: {0}\r\n", lastAccessed); - } - Marshal.ReleaseComObject(shortcut); - shortcut = null; - } - } - } - } - catch { } - } - } - } - else - { - Console.WriteLine("\r\n\r\n=== Recently Accessed Files (Current User) Last {0} Days ===\r\n", lastDays); - - string recentPath = String.Format("{0}\\Microsoft\\Windows\\Recent\\", System.Environment.GetEnvironmentVariable("APPDATA")); - - string[] recentFiles = Directory.GetFiles(recentPath, "*.lnk", SearchOption.AllDirectories); - - foreach (string recentFile in recentFiles) - { - // old method (needed interop dll) - //WshShell shell = new WshShell(); - //IWshShortcut shortcut = (IWshShortcut)shell.CreateShortcut(recentFile); - - DateTime lastAccessed = System.IO.File.GetLastAccessTime(recentFile); - - if (lastAccessed > startTime) - { - // invoke the WshShell com object, creating a shortcut to then extract the TargetPath from - Object shortcut = shellObj.GetType().InvokeMember("CreateShortcut", BindingFlags.InvokeMethod, null, shellObj, new object[] { recentFile }); - Object TargetPath = shortcut.GetType().InvokeMember("TargetPath", BindingFlags.GetProperty, null, shortcut, new object[] { }); - if (TargetPath.ToString().Trim() != "") - { - Console.WriteLine(" Target: {0,-10}", TargetPath.ToString()); - Console.WriteLine(" Accessed: {0}\r\n", lastAccessed); - } - Marshal.ReleaseComObject(shortcut); - shortcut = null; - } - } - } - // release the WshShell COM object - Marshal.ReleaseComObject(shellObj); - shellObj = null; - } - catch (Exception ex) - { - Console.WriteLine(" [X] Exception: {0}", ex.Message); - } - } - - public static void ListInterestingFiles() - { - // returns files (w/ modification dates) that match the given pattern below - string patterns = "*pass *;*diagram*;*.pdf;*.vsd;*.doc;*docx;*.xls;*.xlsx;*.kdbx;*.key;KeePass.config"; - - if (IsHighIntegrity()) - { - Console.WriteLine("\r\n\r\n=== Interesting Files (All Users) ===\r\n"); - - string searchPath = String.Format("{0}\\Users\\", Environment.GetEnvironmentVariable("SystemDrive")); - - List files = FindFiles(searchPath, patterns); - - foreach (string file in files) - { - DateTime lastAccessed = System.IO.File.GetLastAccessTime(file); - DateTime lastModified = System.IO.File.GetLastWriteTime(file); - Console.WriteLine(" File: {0}", file); - Console.WriteLine(" Accessed: {0}", lastAccessed); - Console.WriteLine(" Modified: {0}", lastModified); - } - } - - else - { - Console.WriteLine("\r\n\r\n=== Interesting Files (Current User) ===\r\n"); - - string searchPath = Environment.GetEnvironmentVariable("USERPROFILE"); - - List files = FindFiles(searchPath, patterns); - - foreach (string file in files) - { - DateTime lastAccessed = System.IO.File.GetLastAccessTime(file); - DateTime lastModified = System.IO.File.GetLastWriteTime(file); - Console.WriteLine(" File: {0}", file); - Console.WriteLine(" Accessed: {0}", lastAccessed); - Console.WriteLine(" Modified: {0}", lastModified); - } - } - } - - - // misc checks - public static void ListPatches() - { - // lists current patches via WMI (win32_quickfixengineering) - try - { - ManagementObjectSearcher wmiData = new ManagementObjectSearcher(@"root\cimv2", "SELECT * FROM win32_quickfixengineering"); - ManagementObjectCollection data = wmiData.Get(); - - Console.WriteLine("\r\n\r\n=== Installed Patches (via WMI) ===\r\n"); - Console.WriteLine(" HotFixID InstalledOn Description"); - - foreach (ManagementObject result in data) - { - Console.WriteLine(String.Format(" {0,-11}{1,-15}{2}", result["HotFixID"], result["InstalledOn"], result["Description"])); - } - } - catch (Exception ex) - { - Console.WriteLine(" [X] Exception: {0}", ex.Message); - } - } - - public static void ListRecycleBin() - { - // lists recently deleted files (needs to be run from a user context!) - - // Reference: https://stackoverflow.com/questions/18071412/list-filenames-in-the-recyclebin-with-c-sharp-without-using-any-external-files - Console.WriteLine("\r\n\r\n=== Recycle Bin Files Within the last 30 Days ===\r\n"); - - int lastDays = 30; - - var startTime = System.DateTime.Now.AddDays(-lastDays); - - // Shell COM object GUID - Type shell = Type.GetTypeFromCLSID(new Guid("13709620-C279-11CE-A49E-444553540000")); - Object shellObj = Activator.CreateInstance(shell); - - // namespace for recycle bin == 10 - https://msdn.microsoft.com/en-us/library/windows/desktop/bb762494(v=vs.85).aspx - Object recycle = shellObj.GetType().InvokeMember("Namespace", BindingFlags.InvokeMethod, null, shellObj, new object[] { 10 }); - // grab all the deletes items - Object items = recycle.GetType().InvokeMember("Items", BindingFlags.InvokeMethod, null, recycle, null); - // grab the number of deleted items - Object count = items.GetType().InvokeMember("Count", BindingFlags.GetProperty, null, items, null); - int deletedCount = Int32.Parse(count.ToString()); - - // iterate through each item - for (int i = 0; i < deletedCount; i++) - { - // grab the specific deleted item - Object item = items.GetType().InvokeMember("Item", BindingFlags.InvokeMethod, null, items, new object[] { i }); - Object DateDeleted = item.GetType().InvokeMember("ExtendedProperty", BindingFlags.InvokeMethod, null, item, new object[] { "System.Recycle.DateDeleted" }); - DateTime modifiedDate = DateTime.Parse(DateDeleted.ToString()); - if (modifiedDate > startTime) - { - // additional extended properties from https://blogs.msdn.microsoft.com/oldnewthing/20140421-00/?p=1183 - Object Name = item.GetType().InvokeMember("Name", BindingFlags.GetProperty, null, item, null); - Object Path = item.GetType().InvokeMember("Path", BindingFlags.GetProperty, null, item, null); - Object Size = item.GetType().InvokeMember("Size", BindingFlags.GetProperty, null, item, null); - Object DeletedFrom = item.GetType().InvokeMember("ExtendedProperty", BindingFlags.InvokeMethod, null, item, new object[] { "System.Recycle.DeletedFrom" }); - Console.WriteLine(" Name : {0}", Name); - Console.WriteLine(" Path : {0}", Path); - Console.WriteLine(" Size : {0}", Size); - Console.WriteLine(" Deleted From : {0}", DeletedFrom); - Console.WriteLine(" Date Deleted : {0}\r\n", DateDeleted); - } - Marshal.ReleaseComObject(item); - item = null; - } - Marshal.ReleaseComObject(recycle); - recycle = null; - Marshal.ReleaseComObject(shellObj); - shellObj = null; - } - - - // meta-functions for running various checks - public static void SystemChecks() - { - Console.WriteLine("\r\n=== Running System Triage Checks ===\r\n"); - ListBasicOSInfo(); - ListRebootSchedule(); - ListTokenGroupPrivs(); - ListUACSystemPolicies(); - ListPowerShellSettings(); - ListAuditSettings(); - ListWEFSettings(); - ListLSASettings(); - ListUserEnvVariables(); - ListSystemEnvVariables(); - ListUserFolders(); - ListNonstandardServices(); - ListInternetSettings(); - ListLapsSettings(); - ListLocalGroupMembers(); - ListMappedDrives(); - ListRDPSessions(); - ListWMIMappedDrives(); - ListNetworkShares(); - ListFirewallRules(); - ListAntiVirusWMI(); - ListInterestingProcesses(); - ListRegistryAutoLogon(); - ListRegistryAutoRuns(); - ListDNSCache(); - ListARPTable(); - ListAllTcpConnections(); - ListAllUdpConnections(); - ListNonstandardProcesses(); - - // list patches and List4624Events/List4648Events if we're doing "full" collection - if (!FilterResults.filter) - { - ListPatches(); - List4624Events(); - List4648Events(); - } - - if (IsHighIntegrity()) - { - Console.WriteLine("\r\n\r\n [*] In high integrity, performing elevated collection options."); - ListSysmonConfig(); - } - } - - public static void UserChecks() - { - Console.WriteLine("\r\n=== Running User Triage Checks ===\r\n"); - - if (IsHighIntegrity()) - { - Console.WriteLine("\r\n [*] In high integrity, attempting triage for all users on the machine."); - Console.WriteLine("\r\n Current user : {0} - {1} ", WindowsIdentity.GetCurrent().Name, WindowsIdentity.GetCurrent().User); - } - else - { - Console.WriteLine("\r\n [*] In medium integrity, attempting triage of current user."); - Console.WriteLine("\r\n Current user : {0} - {1} ", WindowsIdentity.GetCurrent().Name, WindowsIdentity.GetCurrent().User); - } - - CheckFirefox(); - CheckChrome(); - TriageIE(); - DumpVault(); - ListSavedRDPConnections(); - ListRecentRunCommands(); - ListPuttySessions(); - ListPuttySSHHostKeys(); - ListCloudCreds(); - ListRecentFiles(); - ListMasterKeys(); - ListCredFiles(); - ListRDCManFiles(); - - if (!FilterResults.filter) - { - TriageChrome(); - TriageFirefox(); - ListInterestingFiles(); - } - } - - static void Usage() - { - Console.WriteLine(" \"SeatBelt.exe system\" collects the following system data:\r\n"); - Console.WriteLine("\tBasicOSInfo - Basic OS info (i.e. architecture, OS version, etc.)"); - Console.WriteLine("\tRebootSchedule - Reboot schedule (last 15 days) based on event IDs 12 and 13"); - Console.WriteLine("\tTokenGroupPrivs - Current process/token privileges (e.g. SeDebugPrivilege/etc.)"); - Console.WriteLine("\tUACSystemPolicies - UAC system policies via the registry"); - Console.WriteLine("\tPowerShellSettings - PowerShell versions and security settings"); - Console.WriteLine("\tAuditSettings - Audit settings via the registry"); - Console.WriteLine("\tWEFSettings - Windows Event Forwarding (WEF) settings via the registry"); - Console.WriteLine("\tLSASettings - LSA settings (including auth packages)"); - Console.WriteLine("\tUserEnvVariables - Current user environment variables"); - Console.WriteLine("\tSystemEnvVariables - Current system environment variables"); - Console.WriteLine("\tUserFolders - Folders in C:\\Users\\"); - Console.WriteLine("\tNonstandardServices - Services with file info company names that don't contain 'Microsoft'"); - Console.WriteLine("\tInternetSettings - Internet settings including proxy configs"); - Console.WriteLine("\tLapsSettings - LAPS settings, if installed"); - Console.WriteLine("\tLocalGroupMembers - Members of local admins, RDP, and DCOM"); - Console.WriteLine("\tMappedDrives - Mapped drives"); - Console.WriteLine("\tRDPSessions - Current incoming RDP sessions"); - Console.WriteLine("\tWMIMappedDrives - Mapped drives via WMI"); - Console.WriteLine("\tNetworkShares - Network shares"); - Console.WriteLine("\tFirewallRules - Deny firewall rules, \"full\" dumps all"); - Console.WriteLine("\tAntiVirusWMI - Registered antivirus (via WMI)"); - Console.WriteLine("\tInterestingProcesses - \"Interesting\" processes- defensive products and admin tools"); - Console.WriteLine("\tRegistryAutoRuns - Registry autoruns"); - Console.WriteLine("\tRegistryAutoLogon - Registry autologon information"); - Console.WriteLine("\tDNSCache - DNS cache entries (via WMI)"); - Console.WriteLine("\tARPTable - Lists the current ARP table and adapter information (equivalent to arp -a)"); - Console.WriteLine("\tAllTcpConnections - Lists current TCP connections and associated processes"); - Console.WriteLine("\tAllUdpConnections - Lists current UDP connections and associated processes"); - Console.WriteLine("\tNonstandardProcesses - Running processeswith file info company names that don't contain 'Microsoft'"); - Console.WriteLine("\t * If the user is in high integrity, the following additional actions are run:"); - Console.WriteLine("\tSysmonConfig - Sysmon configuration from the registry"); - - Console.WriteLine("\r\n\r\n \"SeatBelt.exe user\" collects the following user data:\r\n"); - Console.WriteLine("\tSavedRDPConnections - Saved RDP connections"); - Console.WriteLine("\tTriageIE - Internet Explorer bookmarks and history (last 7 days)"); - Console.WriteLine("\tDumpVault - Dump saved credentials in Windows Vault (i.e. logins from Internet Explorer and Edge), from SharpWeb"); - Console.WriteLine("\tRecentRunCommands - Recent \"run\" commands"); - Console.WriteLine("\tPuttySessions - Interesting settings from any saved Putty configurations"); - Console.WriteLine("\tPuttySSHHostKeys - Saved putty SSH host keys"); - Console.WriteLine("\tCloudCreds - AWS/Google/Azure cloud credential files"); - Console.WriteLine("\tRecentFiles - Parsed \"recent files\" shortcuts (last 7 days)"); - Console.WriteLine("\tMasterKeys - List DPAPI master keys"); - Console.WriteLine("\tCredFiles - List Windows credential DPAPI blobs"); - Console.WriteLine("\tRDCManFiles - List Windows Remote Desktop Connection Manager settings files"); - Console.WriteLine("\t * If the user is in high integrity, this data is collected for ALL users instead of just the current user"); - - Console.WriteLine("\r\n\r\n Non-default options:\r\n"); - Console.WriteLine("\tCurrentDomainGroups - The current user's local and domain groups"); - Console.WriteLine("\tPatches - Installed patches via WMI (takes a bit on some systems)"); - Console.WriteLine("\tLogonSessions - User logon session data"); - Console.WriteLine("\tKerberosTGTData - ALL TEH TGTZ!"); - Console.WriteLine("\tInterestingFiles - \"Interesting\" files matching various patterns in the user's folder"); - Console.WriteLine("\tIETabs - Open Internet Explorer tabs"); - Console.WriteLine("\tTriageChrome - Chrome bookmarks and history"); - Console.WriteLine("\tTriageFirefox - Firefox history (no bookmarks)"); - Console.WriteLine("\tRecycleBin - Items in the Recycle Bin deleted in the last 30 days - only works from a user context!"); - Console.WriteLine("\t4624Events - 4624 logon events from the security event log"); - Console.WriteLine("\t4648Events - 4648 explicit logon events from the security event log (runas or outbound RDP)"); - Console.WriteLine("\tKerberosTickets - List Kerberos tickets. If elevated, grouped by all logon sessions."); - - Console.WriteLine("\r\n\r\n \"SeatBelt.exe all\" will run ALL enumeration checks, can be combined with \"full\".\r\n"); - Console.WriteLine("\r\n \"SeatBelt.exe [CheckName] full\" will prevent any filtering and will return complete results.\r\n"); - Console.WriteLine("\r\n \"SeatBelt.exe [CheckName] [CheckName2] ...\" will run one or more specified checks only (case-sensitive naming!)\r\n"); - } - - static void Main(string[] args) - { - PrintLogo(); - - var watch = System.Diagnostics.Stopwatch.StartNew(); - - if (args.Length != 0) - { - foreach (string arg in args) - { - if (string.Equals(arg, "full", StringComparison.CurrentCultureIgnoreCase)) - { - FilterResults.filter = false; - } - } - - foreach (string arg in args) - { - if (string.Equals(arg, "full", StringComparison.CurrentCultureIgnoreCase)) - { - FilterResults.filter = false; - if (args.Length == 1) - { - // if "full" is the only argument, run System and User triage - SystemChecks(); - ListKerberosTickets(); - UserChecks(); - ListIETabs(); - ListPatches(); - ListRecycleBin(); - - watch.Stop(); - Console.WriteLine("\r\n\r\n[*] Completed All Safety Checks with no filtering in {0} seconds\r\n", (watch.ElapsedMilliseconds / 1000)); - return; - } - } - if (string.Equals(arg, "all", StringComparison.CurrentCultureIgnoreCase)) - { - SystemChecks(); - ListKerberosTickets(); - UserChecks(); - ListIETabs(); - ListPatches(); - TriageChrome(); - TriageFirefox(); - ListRecycleBin(); - ListInterestingFiles(); - - watch.Stop(); - Console.WriteLine("\r\n\r\n[*] Completed All Safety Checks in {0} seconds\r\n", (watch.ElapsedMilliseconds / 1000)); - return; - } - } - - foreach (string arg in args) - { - if (string.Equals(arg, "full", StringComparison.CurrentCultureIgnoreCase)) { } - else if (string.Equals(arg, "system", StringComparison.CurrentCultureIgnoreCase)) - { - SystemChecks(); - } - else if (string.Equals(arg, "user", StringComparison.CurrentCultureIgnoreCase)) - { - UserChecks(); - } - else - { - Type type = typeof(Program); - - MethodInfo info = null; - - // try to grab the function name via reflection - if (Regex.IsMatch(arg, @"^Triage.*")) - { - // if TriageX(), all good - info = type.GetMethod(arg); - } - else if (Regex.IsMatch(arg, @"^Dump.*")) - { - // if DumpX, all good - info = type.GetMethod(arg); - } - else - { - // build List() - info = type.GetMethod(String.Format("List{0}", arg)); - } - - if (info == null) - { - Console.WriteLine("[X] Check \"{0}\" not found!", arg); - } - else - { - info.Invoke(null, new object[] { }); - } - } - } - } - else - { - Usage(); - return; - } - - watch.Stop(); - Console.WriteLine("\r\n\r\n[*] Completed Safety Checks in {0} seconds\r\n", (watch.ElapsedMilliseconds / 1000)); + (new Seatbelt(args)).Start(); } } } diff --git a/Seatbelt/Properties/AssemblyInfo.cs b/Seatbelt/Properties/AssemblyInfo.cs index c0ec8d8..151328c 100755 --- a/Seatbelt/Properties/AssemblyInfo.cs +++ b/Seatbelt/Properties/AssemblyInfo.cs @@ -1,5 +1,4 @@ using System.Reflection; -using System.Runtime.CompilerServices; using System.Runtime.InteropServices; // General Information about an assembly is controlled through the following diff --git a/Seatbelt/Runtime.cs b/Seatbelt/Runtime.cs new file mode 100644 index 0000000..6f823dc --- /dev/null +++ b/Seatbelt/Runtime.cs @@ -0,0 +1,331 @@ +#nullable disable +using Seatbelt.Commands; +using Seatbelt.Interop; +using Seatbelt.Util; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using Seatbelt.Output.Sinks; +using System.Management; +using Microsoft.Win32; + +namespace Seatbelt +{ + internal class Runtime + { + public List AllCommands { get; private set; } = new List(); + public readonly IOutputSink OutputSink; + private IEnumerable Commands { get; set; } + private IEnumerable CommandGroups { get; set; } + public bool FilterResults { get; } + public string ComputerName { get; } // for remote connections + private string UserName { get; } // for remote connections + private string Password { get; } // for remote connections + private ManagementClass wmiRegProv { get; } + + public Runtime(IOutputSink outputSink, IEnumerable commands, IEnumerable commandGroups, bool filter) + : this(outputSink, commands, commandGroups, filter, "", "", "") + { + } + + public Runtime(IOutputSink outputSink, IEnumerable commands, IEnumerable commandGroups, bool filter, string computerName) + : this(outputSink, commands, commandGroups, filter, computerName, "", "") + { + } + + public Runtime(IOutputSink outputSink, IEnumerable commands, IEnumerable commandGroups, bool filter, string computerName, string userName, string password) + { + OutputSink = outputSink; + Commands = commands; + CommandGroups = commandGroups; + FilterResults = filter; + ComputerName = computerName; + UserName = userName; + Password = password; + + // test a remote connection first if a remote system is specified + if (!string.IsNullOrEmpty(computerName)) + { + try + { + if (!string.IsNullOrEmpty(userName) && !string.IsNullOrEmpty(password)) + { + OutputSink.WriteHost($"[*] Running commands remotely against the host '{computerName}' with credentials -> user:{UserName} , password:{Password}\r\n"); + + var options = new ConnectionOptions(); + options.Username = UserName; + options.Password = Password; + options.Impersonation = ImpersonationLevel.Impersonate; + options.EnablePrivileges = true; + + var scope = new ManagementScope($"\\\\{computerName}\\root\\cimv2", options); + scope.Connect(); + } + else + { + OutputSink.WriteHost($"[*] Running commands remotely against the host '{computerName}' with current user credentials\r\n"); + + var scope = new ManagementScope($"\\\\{computerName}\\root\\cimv2"); + scope.Connect(); + } + InitializeCommands(); + } + catch (Exception e) + { + OutputSink.WriteError($"Error connecting to \"{computerName}\" : {e.Message}"); + Environment.Exit(1); + } + + wmiRegProv = WMIUtil.WMIRegConnection(computerName, userName, password); + } + else + { + InitializeCommands(); + } + } + + public ManagementObjectSearcher GetManagementObjectSearcher(string nameSpace, string query) + { + + // helper that takes the current configuration for a remote management and builds the proper ManagementObjectSearcher object + // used for WMI searching + + if (string.IsNullOrEmpty(ComputerName)) + return new ManagementObjectSearcher(nameSpace, query); + + try + { + if (!string.IsNullOrEmpty(UserName) && !string.IsNullOrEmpty(Password)) + { + var options = new ConnectionOptions + { + Username = UserName, + Password = Password, + Impersonation = ImpersonationLevel.Impersonate, + EnablePrivileges = true + }; + + var scope = new ManagementScope($"\\\\{ComputerName}\\{nameSpace}", options); + scope.Connect(); + var queryObj = new ObjectQuery(query); + return new ManagementObjectSearcher(scope, queryObj); + } + else + { + var scope = new ManagementScope($"\\\\{ComputerName}\\{nameSpace}"); + scope.Connect(); + var queryObj = new ObjectQuery(query); + return new ManagementObjectSearcher(scope, queryObj); + } + } + catch (Exception e) + { + throw new Exception($"Error connecting to \"{ComputerName}\" : {e.Message}"); + } + } + + public string[]? GetSubkeyNames(RegistryHive hive, string path) + { + if (!string.IsNullOrEmpty(ComputerName)) + { + return RegistryUtil.GetSubkeyNames(hive, path, wmiRegProv); + } + + return RegistryUtil.GetSubkeyNames(hive, path); + } + + public string? GetStringValue(RegistryHive hive, string path, string value) + { + if (!string.IsNullOrEmpty(ComputerName)) + { + return RegistryUtil.GetStringValue(hive, path, value, wmiRegProv); + } + + return RegistryUtil.GetStringValue(hive, path, value); + } + + public uint? GetDwordValue(RegistryHive hive, string path, string value) + { + if (!string.IsNullOrEmpty(ComputerName)) + { + return RegistryUtil.GetDwordValue(hive, path, value, wmiRegProv); + } + + return RegistryUtil.GetDwordValue(hive, path, value); + } + + + public byte[]? GetBinaryValue(RegistryHive hive, string path, string value) + { + if (!string.IsNullOrEmpty(ComputerName)) + { + return RegistryUtil.GetBinaryValue(hive, path, value, wmiRegProv); + } + + return RegistryUtil.GetBinaryValue(hive, path, value); + } + + public Dictionary GetValues(RegistryHive hive, string path) + { + if (!string.IsNullOrEmpty(ComputerName)) + { + return RegistryUtil.GetValues(hive, path, wmiRegProv); + } + + return RegistryUtil.GetValues(hive, path); + } + + public string[] GetUserSIDs() + { + if (!string.IsNullOrEmpty(ComputerName)) + { + return RegistryUtil.GetUserSIDs(wmiRegProv); + } + + return RegistryUtil.GetUserSIDs(); + } + + public bool ISRemote() + { + return !string.IsNullOrEmpty(ComputerName); + } + + private void InitializeCommands() + { + foreach (var type in Assembly.GetExecutingAssembly().GetTypes()) + { + if (!type.IsSubclassOf(typeof(CommandBase)) || type.IsAbstract) + { + continue; + } + + var instance = (CommandBase)Activator.CreateInstance(type, new object[] { this }); + + if (instance.Command != "TEMPLATE") + { + AllCommands.Add(instance); + } + } + + AllCommands = AllCommands.OrderBy(c => c.Command).ToList(); + } + + + public void Execute() + { + foreach (var group in CommandGroups) + { + if (!ProcessGroup(group)) + { + OutputSink.WriteError($"Invalid command group \"{group}\""); + } + } + + foreach (var command in Commands) + { + try + { + if (!ProcessCommand(command)) + { + OutputSink.WriteError($"Error running command \"{command}\""); + } + } + catch (Exception e) + { + OutputSink.WriteError($"Error running {command}: {e}"); + + } + } + } + + + private bool ProcessGroup(string command) + { + var commandGroupStrings = Enum.GetNames(typeof(CommandGroup)).ToList().Select(g=> g.ToLower()); + + if (!commandGroupStrings.Contains(command.ToLower())) + return false; + + List toExecute; + + switch (command.ToLower()) + { + case "all": + toExecute = AllCommands; + break; + + default: + CommandGroup commandGroup; + try + { + var groupName = Enum.GetNames(typeof(CommandGroup)).FirstOrDefault(c => c.ToLower() == command.ToLower()); + commandGroup = (CommandGroup)Enum.Parse(typeof(CommandGroup), groupName); + } + catch (ArgumentException) + { + return false; + } + + toExecute = AllCommands.Where(g => g.Group.Contains(commandGroup)).ToList(); + break; + } + + toExecute.ForEach(c => + { + ExecuteCommand(c, new string[] { }); + }); + + return true; + } + + + private bool ProcessCommand(string commandLine) + { + var args = Shell32.CommandLineToArgs(commandLine); + + var commandName = args[0]; + var command = AllCommands.FirstOrDefault(c => c.Command.Equals(commandName, StringComparison.InvariantCultureIgnoreCase)); + + if (command == null) + { + return false; + } + + var commandArgs = new string[] { }; + if (args.Length > 1) + { + commandArgs = args.SubArray(1, args.Length - 1); + } + + ExecuteCommand(command, commandArgs); + + return true; + } + + + private void ExecuteCommand(CommandBase? command, string[] commandArgs) + { + try + { + OutputSink.WriteOutput(new HostDTO($"====== {command.Command} ======\n")); + var results = command.Execute(commandArgs); + if (results != null) + { + // OutputSink.BeginOutput(); + foreach (var result in results) + { + OutputSink.WriteOutput(result); + } + // OutputSink.EndOutput(); + } + } + catch (Exception e) + { + // TODO: Return an error DTO + OutputSink.WriteError($" [!] Terminating exception running command '{command.Command}': " + e); + } + } + } +} +#nullable enable \ No newline at end of file diff --git a/Seatbelt/Seatbelt.cs b/Seatbelt/Seatbelt.cs new file mode 100644 index 0000000..97ff9b4 --- /dev/null +++ b/Seatbelt/Seatbelt.cs @@ -0,0 +1,165 @@ +using Seatbelt.Commands; +using Seatbelt.Output.Sinks; +using Seatbelt.Output.TextWriters; +using System; +using System.Linq; +using System.Text; + +namespace Seatbelt +{ + public class Seatbelt + { + public bool FilterResults { get; } + + private readonly IOutputSink _outputSink; + private readonly Runtime _runtime; + private const string Version = "1.0.0"; + private SeatbeltOptions Options { get; set; } + + public Seatbelt(string[] args) + { + Options = (new SeatbeltArgumentParser(args)).Parse(); + + _outputSink = OutputSinkFromArgs(Options.OutputFile); + + _runtime = new Runtime( + _outputSink, + Options.Commands, + Options.CommandGroups, + Options.FilterResults, + Options.ComputerName, + Options.UserName, + Options.Password + ); + } + + private IOutputSink OutputSinkFromArgs(string? outputFileArg) + { + if (outputFileArg == null) + return new TextOutputSink(new ConsoleTextWriter(), FilterResults); + + if (outputFileArg == string.Empty) + throw new Exception("Invalid filename"); + + + if (outputFileArg.EndsWith(".json")) + { + return new JsonFileOutputSink(outputFileArg, FilterResults); + } + + return new TextOutputSink(new FileTextWriter(outputFileArg), FilterResults); + + } + + public void Start() + { + var watch = System.Diagnostics.Stopwatch.StartNew(); + + if (!Options.Commands.Any() && !Options.CommandGroups.Any()) + { + PrintLogo(); + Usage(); + return; + } + + if (!Options.QuietMode) + PrintLogo(); + + _runtime.Execute(); + + watch.Stop(); + + if (!Options.QuietMode) + { + _outputSink.WriteVerbose($"\n\n[*] Completed collection in {(watch.ElapsedMilliseconds / 1000.0)} seconds\n"); + } + } + + public void PrintLogo() + { + _outputSink.WriteHost("\n\n %&&@@@&& "); + _outputSink.WriteHost(" &&&&&&&%%%, #&&@@@@@@%%%%%%###############% "); + _outputSink.WriteHost(" &%& %&%% &////(((&%%%%%#%################//((((###%%%%%%%%%%%%%%%"); + _outputSink.WriteHost("%%%%%%%%%%%######%%%#%%####% &%%**# @////(((&%%%%%%######################((((((((((((((((((("); + _outputSink.WriteHost("#%#%%%%%%%#######%#%%####### %&%,,,,,,,,,,,,,,,, @////(((&%%%%%#%#####################((((((((((((((((((("); + _outputSink.WriteHost("#%#%%%%%%#####%%#%#%%####### %%%,,,,,, ,,. ,, @////(((&%%%%%%%######################(#(((#(#(((((((((("); + _outputSink.WriteHost("#####%%%#################### &%%...... ... .. @////(((&%%%%%%%###############%######((#(#(####(((((((("); + _outputSink.WriteHost("#######%##########%######### %%%...... ... .. @////(((&%%%%%#########################(#(#######((#####"); + _outputSink.WriteHost("###%##%%#################### &%%............... @////(((&%%%%%%%%##############%#######(#########((#####"); + _outputSink.WriteHost("#####%###################### %%%.. @////(((&%%%%%%%################ "); + _outputSink.WriteHost(" &%& %%%%% Seatbelt %////(((&%%%%%%%%#############* "); + _outputSink.WriteHost($" &%%&&&%%%%% v{Version} ,(((&%%%%%%%%%%%%%%%%%, "); + _outputSink.WriteHost(" #%%%%##, \n\n"); + } + + + private void Usage() + { + // List all available commands + _outputSink.WriteHost("Available commands (+ means remote usage is supported):\n"); + _runtime.AllCommands.ForEach(c => + { + if (c.SupportRemote) + { + _outputSink.WriteHost($" + {c.Command,-22} - {c.Description}"); + } + else + { + _outputSink.WriteHost($" {c.Command,-22} - {c.Description}"); + } + }); + + + // List all command groupings + var commandGroups = Enum.GetNames(typeof(CommandGroup)).ToArray(); + _outputSink.WriteHost("\n\nSeatbelt has the following command groups: " + string.Join(", ", commandGroups)); + _outputSink.WriteHost("\n You can invoke command groups with \"Seatbelt.exe \"\n"); + + var sb = new StringBuilder(); + foreach (var group in commandGroups) + { + + if (group == "All") + { + sb.Append($" \"Seatbelt.exe -group={group.ToLower()}\" runs all commands\n\n"); + continue; + } + + sb.Append($" \"Seatbelt.exe -group={group.ToLower()}\" runs the following commands:\n\n "); + + var groupCommands = _runtime.AllCommands + .Where(c => c.Group.Contains((CommandGroup)Enum.Parse(typeof(CommandGroup), group))) + .Select(c => c.Command) + .ToArray(); + + for (var i = 0; i < groupCommands.Length; i++) + { + sb.Append(groupCommands[i]); + + if (i != groupCommands.Length - 1) + { + sb.Append(", "); + } + + if (i % 4 == 0 && i != 0) + { + sb.Append("\n "); + } + } + sb.AppendLine("\n"); + } + + _outputSink.WriteHost(sb.ToString()); + _outputSink.WriteHost("Examples:"); + _outputSink.WriteHost(" 'Seatbelt.exe [Command2] ...' will run one or more specified checks only"); + _outputSink.WriteHost(" 'Seatbelt.exe -full' will return complete results for a command without any filtering."); + _outputSink.WriteHost(" 'Seatbelt.exe \" [argument]\"' will pass an argument to a command that supports it (note the quotes)."); + _outputSink.WriteHost(" 'Seatbelt.exe -group=all' will run ALL enumeration checks, can be combined with \"-full\"."); + _outputSink.WriteHost(" 'Seatbelt.exe -computername=COMPUTER.DOMAIN.COM [-username=DOMAIN\\USER -password=PASSWORD]' will run an applicable check remotely"); + _outputSink.WriteHost(" 'Seatbelt.exe -group=remote -computername=COMPUTER.DOMAIN.COM [-username=DOMAIN\\USER -password=PASSWORD]' will run remote specific checks"); + _outputSink.WriteHost(" 'Seatbelt.exe -group=system -outputfile=\"C:\\Temp\\out.txt\"' will run system checks and output to a .txt file."); + _outputSink.WriteHost(" 'Seatbelt.exe -group=user -q -outputfile=\"C:\\Temp\\out.json\"' will run in quiet mode with user checks and output to a .json file."); + + } + } +} \ No newline at end of file diff --git a/Seatbelt/Seatbelt.csproj b/Seatbelt/Seatbelt.csproj index 3e586c1..c0362e4 100755 --- a/Seatbelt/Seatbelt.csproj +++ b/Seatbelt/Seatbelt.csproj @@ -9,7 +9,7 @@ Properties Seatbelt Seatbelt - v3.5 + v4.0 512 publish\ true @@ -26,6 +26,9 @@ false false true + + 8.0 + enable AnyCPU @@ -37,6 +40,11 @@ prompt 4 false + false + true + CS8632 + 8.0 + enable AnyCPU @@ -47,22 +55,167 @@ prompt 4 + false + false + true + CS8632,CA1401 + 8.0 + enable + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -71,6 +224,10 @@ true + + + +