-
Notifications
You must be signed in to change notification settings - Fork 9
/
Copy pathbitwarden2hashcat.py
176 lines (152 loc) · 7.44 KB
/
bitwarden2hashcat.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
#!/usr/bin/env python3
"""Utility to convert Bitwarden hashes to hashcat-suitable hashes from browser data and local data.json files"""
# Currently supported: Chrome, Opera, Brave, Vivaldi, Edge on Windows; Chrome and Firefox on Linux
# OS X is currently not supported due to a lack of "test equipment"
# Tested with Firefox 79.0 (64-bit), Chromium 83.0.4250.0 (64-bit), Chrome 84.0.4147.105 (64-bit) on Windows 10 19042.421
# Tested with Firefox 79.0 (64-Bit) on Ubuntu 18.04 LTS
#
# Proudly brought to you by 0x6470 <https://github.com/0x6470/bitwarden2hashcat>
#
# The extraction process from browsers is buggy, errors are to be expected
#
# For licensing details, see LICENSE file
#
# Usage:
# python3 bitwarden2hashcat.py data.json
# python3 bitwarden2hashcat.py *.json
import json
import os
import sys
import base64
def extract_windows():
userprofile = os.getenv("userprofile")
locations = [
"data.json", # current directory
"bitwarden-appdata\\data.json", # portable installation
"{}\\AppData\\Local\\Packages\\8bitSolutionsLLC.bitwardendesktop_h4e712dmw3xyy\\LocalCache\\Roaming\\Bitwarden\\data.json".format(userprofile), # Windows 10 App
"{}\\AppData\\Roaming\\Bitwarden\\data.json".format(userprofile), # Bitwarden Windows
"{}\\AppData\\Roaming\\Bitwarden CLI\\data.json".format(userprofile) # Bitwarden CLI
]
for i in locations:
if os.path.exists(i):
return get_data(i)
else:
return None
def manual_extraction():
print("Automatic data extraction failed...")
print("Here are the manual steps\n")
print("Firefox: navigate to about:debugging#/runtime/this-firefox")
print("Click \"inspect\" at the Bitwarden entry")
print("Click \"extension storage\" in the storage tab")
print("")
print("Chrome: navigate to chrome://extensions/")
print("Turn the developer mode on")
print("Click \"Inspect views background.html\" at the Bitwarden entry")
print("Open the console tab")
print("Enter \" chrome.storage.local.get(null, function (data) { console.info(data) }); \"")
print("")
print("Those instructions apply to all chromium based browsers such as Vivaldi, Opera, Brave and the new Edge")
print("\n\n")
keyHash = input("Search for the value of the \"keyHash\" key and enter it here: ")
kdfIterations = input("Search for the value of the \"kdfIterations\" key and enter it here: ")
userEmail = input("Search for the value of the \"userEmail\" key and enter it here: ")
return userEmail, keyHash, kdfIterations
def extract_webbrowsers():
if "nt" in os.name:
userprofile = os.getenv("userprofile")
paths = [
"{}\\AppData\\Local\\Google\\Chrome\\User Data\\Default\\Local Extension Settings\\nngceckbapebfimnlniiiahkandclblb".format(userprofile), # Chrome
"{}\\AppData\\Roaming\\Opera Software\\Opera Stable\\Local Extension Settings\\ccnckbpmaceehanjmeomladnmlffdjgn".format(userprofile), # Opera
"{}\\AppData\\Local\\BraveSoftware\\Brave-browser\\User Data\\Default\\Local Extension Settings\\nngceckbapebfimnlniiiahkandclblb".format(userprofile), # Brave
"{}\\AppData\\Local\\Vivaldi\\User Data\\Default\\Local Extension Settings\\nngceckbapebfimnlniiiahkandclblb".format(userprofile), # Vivaldi
"{}\\AppData\\Local\\Microsoft\\Edge\\User Data\\Default\\Extensions\\jbkfoedolllekgbhcbcoahefnbanhhlh".format(userprofile) # chromium-based Edge
]
else:
userprofile = os.getenv("HOME")
paths = [
"{}/.config/google-chrome/Default/Local Extension Settings/nngceckbapebfimnlniiiahkandclblb".format(userprofile), # Chrome
"{}/snap/chromium/common/chromium/Default/Local Extension Settings/nngceckbapebfimnlniiiahkandclblb".format(userprofile) # Chromium snap
]
try:
import plyvel
except ImportError:
print("Please install the plyvel module")
sys.exit()
for path in paths:
try:
db = plyvel.DB(path, create_if_missing=False)
except plyvel._plyvel.Error:
continue
except plyvel._plyvel.IOError:
print("Please close the browser first")
sys.exit()
try:
email = db.get(b"userEmail").decode().strip("\"")
keyHash = db.get(b"keyHash").decode().strip("\"")
iterations = db.get(b"kdfIterations").decode().strip("\"")
except Exception:
print("Something in the structure changed, try to open and close the browser and if that fails please create an issue\n")
return None
return email, keyHash, iterations
else:
import sqlite3
print("It seems that you're using Firefox, please enter the path")
if "nt" in os.name:
print("by default, it looks like this: %AppData%\Mozilla\Firefox\Profiles\[your_profile]\storage\default\moz-extension+++[UUID]^userContextId=[integer]")
else:
print("by default, it looks like this: ~/.mozilla/firefox/your_profile/storage/default/moz-extension+++[UUID]^userContextID=[integer]")
print("The UUID can be found by visiting about:debugging#/runtime/this-firefox")
path = (input("Please enter path (replace \\ with / or with \\\\): ") + "/idb/3647222921wleabcEoxlt-eengsairo.sqlite").replace("~", os.getenv("HOME"))
if not os.path.exists(path):
print("Please enter a valid path")
return None
connection = sqlite3.connect(path)
cursor = connection.cursor()
try:
data = cursor.execute("SELECT * FROM object_data;").fetchall()
except sqlite3.OperationalError:
print("Please close the browser first")
sys.exit()
try:
iterations = int.from_bytes(data[9][4].strip(b"\xff").split(b"\xff")[-1].split(b"\x00")[0], byteorder="little") # very strange structure, might vary in the future
keyHash = data[10][4].strip(b"\xff").split(b"\xff")[-1].split(b"\x00")[0].decode()
email = data[21][4].strip(b"\xff").split(b"\xff")[-1].split(b"\x00")[0].decode()
except Exception:
print("Something in the structure changed, try to open and close the browser and if that fails please create an issue\n")
return None
return email, keyHash, iterations
def get_data(file):
with open(file) as f:
data = json.load(f)
email = data["userEmail"]
keyHash = data["keyHash"]
iterations = data["kdfIterations"]
return email, keyHash, iterations
def process(path=None):
data = None
if path:
try:
data = get_data(path)
except FileNotFoundError:
print("File {} not found... trying other methods".format(path))
if not data:
data = extract_webbrowsers()
if not data:
data = manual_extraction()
return data
def format_data(data):
return "$bitwarden$2*{}*{}*{}".format(data[2], base64.b64encode(data[0].encode()).decode(), data[1]) # version 2, see https://github.com/hashcat/hashcat/pull/3202
if __name__ == "__main__":
if len(sys.argv) > 1:
# Wildcard handling
if "*" in sys.argv[1]:
from glob import glob
for i in glob(sys.argv[1]):
print(format_data(process(i)))
if len(sys.argv) > 2:
for i in sys.argv[1:]:
print(format_data(process(i)))
else:
print(format_data(process(sys.argv[1])))
else:
print(format_data(process()))