parent
fba4682adc
commit
e97b184755
|
@ -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 <file>" 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 <HIVE[\PATH\TO\KEY]> [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 <days> <targetUserRegex>`
|
||||
* 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
|
|
@ -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.
|
472
README.md
472
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) == <search path> <pattern1,pattern2,...>
|
||||
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 <group>"
|
||||
|
||||
"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 <Command> [Command2] ...' will run one or more specified checks only
|
||||
'Seatbelt.exe <Command> -full' will return complete results for a command without any filtering.
|
||||
'Seatbelt.exe "<Command> [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 <Command> -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 == \<directory\> \<depth\> \<regex\> |
|
||||
| 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) == \<search path\> \<pattern1,pattern2,...\> |
|
||||
| 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!
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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<CommandDTOBase?> 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<Bookmark>();
|
||||
|
||||
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<Dictionary<string, object>>(contents);
|
||||
var roots = (Dictionary<string, object>)deserialized["roots"];
|
||||
var bookmarkBar = (Dictionary<string, object>)roots["bookmark_bar"];
|
||||
var children = (ArrayList)bookmarkBar["children"];
|
||||
|
||||
foreach (Dictionary<string, object> 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<Bookmark> bookmarks)
|
||||
{
|
||||
UserName = userName;
|
||||
Bookmarks = bookmarks;
|
||||
}
|
||||
public string UserName { get; }
|
||||
public List<Bookmark> 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<CommandDTOBase?> 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<string>();
|
||||
|
||||
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<string> urLs)
|
||||
{
|
||||
UserName = userName;
|
||||
URLs = urLs;
|
||||
}
|
||||
public string UserName { get; }
|
||||
public List<string> 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<CommandDTOBase?> 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");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<CommandDTOBase?> 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<string>();
|
||||
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<string> history)
|
||||
{
|
||||
UserName = userName;
|
||||
History = history;
|
||||
}
|
||||
public string UserName { get; }
|
||||
public List<string> 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<CommandDTOBase?> 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<CommandDTOBase?> 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<string>();
|
||||
|
||||
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<string> 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
|
|
@ -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<CommandDTOBase?> 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; }
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<CommandDTOBase?> 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<TypedUrl>();
|
||||
|
||||
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<TypedUrl> urls)
|
||||
{
|
||||
Sid = sid;
|
||||
Urls = urls;
|
||||
}
|
||||
public string Sid { get; }
|
||||
public List<TypedUrl> 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<CommandDTOBase?> 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));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
namespace Seatbelt.Commands
|
||||
{
|
||||
public interface CommandDTOBase
|
||||
{
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<CommandDTOBase?> 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
|
|
@ -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<CommandDTOBase?> 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<DirectoryListDTO> ListDirectory(string path, Regex regex, int depth,
|
||||
bool ignoreErrors)
|
||||
{
|
||||
if (depth < 0)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
var dirList = new List<string>();
|
||||
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
|
|
@ -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<CommandDTOBase?> 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; }
|
||||
}
|
||||
}
|
|
@ -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<CommandDTOBase?> 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<string> 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<string>();
|
||||
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<string> GetFiles(string path)
|
||||
{
|
||||
var queue = new Queue<string>();
|
||||
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<string> 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
|
|
@ -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) == <search path> <pattern1,pattern2,...>";
|
||||
|
||||
public override CommandGroup[] Group => new[] { CommandGroup.Misc };
|
||||
public override bool SupportRemote => false; // maybe?
|
||||
|
||||
public SearchIndexCommand(Runtime runtime) : base(runtime)
|
||||
{
|
||||
}
|
||||
|
||||
private IEnumerable<WindowsSearchIndexDTO> 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<string>();
|
||||
|
||||
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<CommandDTOBase?> 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
|
|
@ -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<CommandDTOBase?> 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; }
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<CommandDTOBase?> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<CommandDTOBase?> 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<CommandDTOBase> 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<CommandDTOBase> 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\<OFFICE APP>\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<CommandDTOBase> 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
|
|
@ -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<CommandDTOBase?> 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<OutlookDownload>();
|
||||
|
||||
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<OutlookDownload> 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
|
|
@ -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<CommandDTOBase?> 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<string>();
|
||||
|
||||
foreach (var kvp in hostKeys)
|
||||
{
|
||||
keys.Add($"{kvp.Key}");
|
||||
}
|
||||
|
||||
yield return new PuttyHostKeysDTO(
|
||||
sid,
|
||||
keys
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
internal class PuttyHostKeysDTO : CommandDTOBase
|
||||
{
|
||||
public PuttyHostKeysDTO(string sid, List<string> hostKeys)
|
||||
{
|
||||
Sid = sid;
|
||||
HostKeys = hostKeys;
|
||||
}
|
||||
public string Sid { get; }
|
||||
public List<string> 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<CommandDTOBase?> 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<Dictionary<string, string>>();
|
||||
|
||||
foreach (var sessionName in subKeys)
|
||||
{
|
||||
var Settings = new Dictionary<string, string>
|
||||
{
|
||||
["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<Dictionary<string, string>> sessions)
|
||||
{
|
||||
SID = sid;
|
||||
Sessions = sessions;
|
||||
}
|
||||
public string SID { get; }
|
||||
|
||||
public List<Dictionary<string,string>> 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<CommandDTOBase?> 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<string>();
|
||||
|
||||
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<string> rdgFiles)
|
||||
{
|
||||
FileName = fileName;
|
||||
LastAccessed = lastAccessed;
|
||||
LastModified = lastModified;
|
||||
RdgFiles = rdgFiles;
|
||||
}
|
||||
public string FileName { get; }
|
||||
|
||||
public DateTime LastAccessed { get; }
|
||||
|
||||
public DateTime LastModified { get; }
|
||||
|
||||
public List<string> 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<CommandDTOBase?> 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
|
|
@ -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<CommandDTOBase?> 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<Download>();
|
||||
|
||||
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<Dictionary<string, object>>(contents);
|
||||
|
||||
foreach (var w in deserialized)
|
||||
{
|
||||
var dls = (Dictionary<string, object>)w.Value;
|
||||
foreach (var x in dls)
|
||||
{
|
||||
var dl = (Dictionary<string, object>)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<Download> 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
|
|
@ -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<CommandDTOBase?> 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<CommandDTOBase?> 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<Workspace>();
|
||||
|
||||
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<Dictionary<string, object>>(contents);
|
||||
|
||||
foreach (var w in deserialized)
|
||||
{
|
||||
var settings = (Dictionary<string, object>)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<Workspace> workspaces)
|
||||
{
|
||||
UserName = userName;
|
||||
Workspaces = workspaces;
|
||||
}
|
||||
|
||||
public string UserName { get; }
|
||||
public List<Workspace> 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
|
|
@ -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<CommandDTOBase?> 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
|
|
@ -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<CommandDTOBase?> 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
|
|
@ -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<CommandDTOBase?> 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
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<CommandDTOBase?> 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
|
|
@ -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<CommandDTOBase?> 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<int, ARPTableDTO>();
|
||||
|
||||
// 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<string>();
|
||||
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<string>();
|
||||
|
||||
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<MIB_IPNETROW>();
|
||||
|
||||
// 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<ARPEntry>();
|
||||
}
|
||||
|
||||
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<string> InterfaceIPs { get; set; }
|
||||
|
||||
public List<string> DNSServers { get; set; }
|
||||
|
||||
public List<ARPEntry> 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
|
|
@ -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<CommandDTOBase?> 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; }
|
||||
}
|
||||
}
|
|
@ -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<CommandDTOBase?> 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<string>();
|
||||
|
||||
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<string>? 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<string>? 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
|
|
@ -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<CommandDTOBase?> 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<AuditEntry> ParseClassicPolicy(string path)
|
||||
{
|
||||
// parses a "classic" auditing policy (GptTmpl.inf), returning a list of AuditEntries
|
||||
|
||||
var results = new List<AuditEntry>();
|
||||
|
||||
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<AuditEntry> ParseAdvancedPolicy(string path)
|
||||
{
|
||||
// parses a "advanced" auditing policy (audit.csv), returning a list of AuditEntries
|
||||
|
||||
var results = new List<AuditEntry>();
|
||||
|
||||
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<string> 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<string>();
|
||||
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<string> GetFiles(string path)
|
||||
{
|
||||
var queue = new Queue<string>();
|
||||
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<AuditEntry> 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<AuditEntry> 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
|
|
@ -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<CommandDTOBase?> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<CommandDTOBase?> 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<string>();
|
||||
|
||||
foreach (KeyValuePair<string, object> kvp in settings)
|
||||
{
|
||||
entry.Entries.Add(kvp.Value.ToString());
|
||||
}
|
||||
|
||||
yield return entry;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal class AutoRunDTO : CommandDTOBase
|
||||
{
|
||||
public string Key { get; set; }
|
||||
public List<string> 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
|
|
@ -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<CommandDTOBase?> 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
|
|
@ -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<CommandDTOBase?> 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;
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<CommandDTOBase?> 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
|
|
@ -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<CommandDTOBase?> Execute(string[] args)
|
||||
{
|
||||
var installedVersions = new List<string>();
|
||||
#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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<CommandDTOBase?> 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<MasterKey>();
|
||||
|
||||
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<MasterKey> 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
|
|
@ -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<CommandDTOBase?> 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
|
|
@ -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<CommandDTOBase?> Execute(string[] args)
|
||||
{
|
||||
var currentProcessVars = new SortedDictionary<string, string>();
|
||||
|
||||
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<string, string>();
|
||||
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
|
|
@ -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<CommandDTOBase?> 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(" <Dates the credential was used to logon>\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
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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<string, List<string>> events = new Dictionary<string, List<string>>();
|
||||
|
||||
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<string>
|
||||
// {
|
||||
// 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();
|
||||
//}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<CommandDTOBase?> 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<string>();
|
||||
var NTLMv2Users = new HashSet<string>();
|
||||
var KerberosUsers = new HashSet<string>();
|
||||
|
||||
// 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<string> 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
|
|
@ -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<CommandDTOBase?> 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<string>();
|
||||
|
||||
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; }
|
||||
}
|
||||
}
|
|
@ -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<CommandDTOBase?> 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
|
|
@ -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<CommandDTOBase?> 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
|
|
@ -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<CommandDTOBase?> 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
|
|
@ -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<CommandDTOBase?> 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<CommandDTOBase> 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
|
|
@ -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<CommandDTOBase?> 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<ExplorerRunCommand>();
|
||||
|
||||
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<ExplorerRunCommand> commands)
|
||||
{
|
||||
Sid = sid;
|
||||
Commands = commands;
|
||||
}
|
||||
public string Sid { get; set; }
|
||||
public List<ExplorerRunCommand> 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
|
|
@ -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<CommandDTOBase?> 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");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<CommandDTOBase?> 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<string, string>()
|
||||
{
|
||||
{ "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<string, string>()
|
||||
{
|
||||
{"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<string, string>()
|
||||
{
|
||||
{"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<string>().ToList().Contains(processName))
|
||||
{
|
||||
display = true;
|
||||
category = "defensive";
|
||||
product = defensiveProcesses[processName];
|
||||
}
|
||||
else if (browserProcesses.Keys.OfType<string>().ToList().Contains(processName, System.StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
display = true;
|
||||
category = "browser";
|
||||
product = browserProcesses[processName];
|
||||
}
|
||||
else if (interestingProcesses.Keys.OfType<string>().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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<CommandDTOBase?> 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
|
|
@ -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<CommandDTOBase?> 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; }
|
||||
}
|
||||
}
|
|
@ -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<CommandDTOBase?> 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<string, Dictionary<string, object>>();
|
||||
|
||||
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; }
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<CommandDTOBase?> 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<Principal> members)
|
||||
{
|
||||
ComputerName = computerName;
|
||||
GroupName = groupName;
|
||||
GroupComment = groupComment;
|
||||
Members = members;
|
||||
}
|
||||
|
||||
public string ComputerName { get; }
|
||||
public string GroupName { get; }
|
||||
public string GroupComment { get; }
|
||||
public IEnumerable<Principal> 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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<CommandDTOBase?> 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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<CommandDTOBase?> 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
|
|
@ -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<CommandDTOBase?> 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<string, string[]>();
|
||||
|
||||
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<LogonSessionsDTO>();
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<CommandDTOBase?> 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
|
|
@ -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<CommandDTOBase?> 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<string>();
|
||||
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
|
|
@ -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<CommandDTOBase?> 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
|
|
@ -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<CommandDTOBase?> 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; }
|
||||
}
|
||||
}
|
|
@ -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<CommandDTOBase?> 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
|
|
@ -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<CommandDTOBase?> 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
|
|
@ -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<CommandDTOBase?> 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<PluginAccess>();
|
||||
|
||||
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<PluginAccess> permission)
|
||||
{
|
||||
Plugin = plugin;
|
||||
Permission = permission;
|
||||
}
|
||||
public string Plugin { get; }
|
||||
public List<PluginAccess> 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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<string> GetWindowsPowerShellVersions()
|
||||
{
|
||||
var versions = new List<string>();
|
||||
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<string> GetPowerShellCoreVersions()
|
||||
{
|
||||
var versions = new List<string>();
|
||||
|
||||
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<CommandDTOBase?> Execute(string[] args)
|
||||
{
|
||||
|
||||
var installedVersions = new List<string>();
|
||||
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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<CommandDTOBase?> 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
|
|
@ -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<CommandDTOBase?> 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
|
|
@ -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<CommandDTOBase?> 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<ManagementObject>()
|
||||
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<Module>();
|
||||
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<Module> 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
|
|
@ -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<CommandDTOBase?> 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<RDPConnection>();
|
||||
|
||||
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<RDPConnection> 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
|
|
@ -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<CommandDTOBase?> 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
|
|
@ -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<CommandDTOBase?> 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<string>();
|
||||
|
||||
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<string>();
|
||||
|
||||
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<string> 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
|
|
@ -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<CommandDTOBase?> 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
|
|
@ -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<CommandDTOBase?> 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<RegistryValueDTO> 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<RegistryValueDTO> 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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<string, string> Properties { get; set; }
|
||||
}
|
||||
|
||||
class ScheduledTaskAction
|
||||
{
|
||||
public object Type { get; set; }
|
||||
|
||||
public object Id { get; set; }
|
||||
|
||||
public Dictionary<string, object> 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<CommandDTOBase?> 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<ScheduledTaskAction>();
|
||||
foreach (var obj in actions)
|
||||
{
|
||||
var action = new ScheduledTaskAction();
|
||||
action.Type = $"{obj.SystemProperties["__SUPERCLASS"].Value}";
|
||||
|
||||
var Properties = new Dictionary<string, object>();
|
||||
|
||||
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<ScheduledTaskTrigger>();
|
||||
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<string, string>();
|
||||
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<ScheduledTaskAction> Actions { get; set; }
|
||||
|
||||
public List<ScheduledTaskTrigger> 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<string, object>)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
|
|
@ -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<CommandDTOBase?> 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<SecurityPackagesDTO>();
|
||||
|
||||
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
|
|
@ -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<CommandDTOBase?> 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
|
|
@ -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<CommandDTOBase?> Execute(string[] args)
|
||||
{
|
||||
var AF_INET = 2; // IP_v4
|
||||
uint tableBufferSize = 0;
|
||||
var tableBuffer = IntPtr.Zero;
|
||||
var rowPtr = IntPtr.Zero;
|
||||
var processes = new Dictionary<string, string>();
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<CommandDTOBase?> 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
|
|
@ -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<CommandDTOBase?> 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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<CommandDTOBase?> Execute(string[] args)
|
||||
{
|
||||
var AF_INET = 2; // IP_v4
|
||||
uint tableBufferSize = 0;
|
||||
var tableBuffer = IntPtr.Zero;
|
||||
var rowPtr = IntPtr.Zero;
|
||||
var processes = new Dictionary<string, string>();
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<CommandDTOBase?> 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
|
|
@ -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<CommandDTOBase?> 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<Principal> 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
|
|
@ -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<CommandDTOBase?> 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<string, object>();
|
||||
|
||||
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<string, object>)dto.Properties)
|
||||
{
|
||||
WriteLine(" {0,-30} : {1}", kvp.Key, kvp.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<CommandDTOBase?> 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
|
|
@ -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<CommandDTOBase?> 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; }
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue