mirror of
https://github.com/hashcat/hashcat.git
synced 2024-11-30 03:48:17 +00:00
167 lines
4.8 KiB
Python
167 lines
4.8 KiB
Python
|
#!/usr/bin/env python3
|
||
|
# -*- coding: utf-8 -*-
|
||
|
|
||
|
# Author: hansvh <6390369+hans-vh@users.noreply.github.com>
|
||
|
# Version: 0.0.6
|
||
|
# License: MIT
|
||
|
|
||
|
"""
|
||
|
Files can be found here:
|
||
|
Android: /data/data/com.lastpass.lpandroid/files
|
||
|
Others: See https://support.lastpass.com/help/where-is-my-lastpass-data-stored-on-my-computer-lp070008
|
||
|
|
||
|
Tested OK with:
|
||
|
- LastPass for Android (com.lastpass.lpandroid) v5.12.0.10004
|
||
|
- LastPass for Chrome v4.101.1
|
||
|
- LastPass for Opera v4.101.1
|
||
|
- LastPass for Firefox v4.101.0
|
||
|
"""
|
||
|
|
||
|
import sys
|
||
|
import os
|
||
|
import sqlite3
|
||
|
from base64 import b64decode
|
||
|
from re import search
|
||
|
|
||
|
|
||
|
def parse_encu(data):
|
||
|
"""Parse ENCU and return IV and AES-256-CBC encrypted email to compare against"""
|
||
|
data = data.decode("utf-8")
|
||
|
|
||
|
initialization_vector = None
|
||
|
encrypted_email = None
|
||
|
|
||
|
try:
|
||
|
# Format: ![B64]|[B64]
|
||
|
result = search(r"^!(.*)\|(.*)$", data)
|
||
|
initialization_vector = result.group(1)
|
||
|
encrypted_email = result.group(2)
|
||
|
|
||
|
initialization_vector = b64decode(initialization_vector).hex()
|
||
|
encrypted_email = b64decode(encrypted_email).hex()
|
||
|
except:
|
||
|
# B64 Only. This implies EBC, not CBC, mode and IV is found elsewhere, e.g., in database
|
||
|
encrypted_email = b64decode(data).hex()
|
||
|
|
||
|
return initialization_vector, encrypted_email
|
||
|
|
||
|
|
||
|
def open_file(file_name):
|
||
|
"""Open file and return contents"""
|
||
|
with open(file_name, "rb") as file_handle:
|
||
|
return file_handle.read()
|
||
|
|
||
|
|
||
|
def parse_vault(xml):
|
||
|
"""Parse Vault according to format: 4 bytes ASCII identifier, 4 bytes size, size bytes data"""
|
||
|
magic_bytes = xml[:4].decode("utf-8")
|
||
|
if magic_bytes != "LPAV":
|
||
|
sys.exit(f"Expected LPAV in base 64 decoded XML, but found {magic_bytes}")
|
||
|
|
||
|
offset = 0
|
||
|
while offset < len(xml):
|
||
|
identifier = xml[offset:offset + 4].decode("utf-8")
|
||
|
offset = offset + 4
|
||
|
size = int.from_bytes(xml[offset:offset + 4], byteorder='big')
|
||
|
offset = offset + 4
|
||
|
data = xml[offset:offset + size]
|
||
|
|
||
|
if identifier == 'ENCU':
|
||
|
initialization_vector, encrypted_email = parse_encu(data)
|
||
|
return initialization_vector, encrypted_email
|
||
|
|
||
|
offset = offset + size
|
||
|
|
||
|
return None, None
|
||
|
|
||
|
|
||
|
def sqlite_parse_chromium(cur):
|
||
|
"""Chrome and Opera"""
|
||
|
iterations = -1
|
||
|
xml = ""
|
||
|
try:
|
||
|
res = cur.execute("SELECT data FROM LastPassData WHERE type='accts'")
|
||
|
(xml,) = res.fetchone()
|
||
|
result = search(r"^iterations=(\d+);(.*)$", xml)
|
||
|
iterations = result.group(1)
|
||
|
xml = result.group(2)
|
||
|
xml = b64decode(xml)
|
||
|
except:
|
||
|
return None, None
|
||
|
|
||
|
return iterations, xml
|
||
|
|
||
|
|
||
|
def sqlite_parse_firefox(cur):
|
||
|
"""Firefox"""
|
||
|
iterations = -1
|
||
|
encu = ""
|
||
|
try:
|
||
|
res = cur.execute("SELECT value FROM data WHERE key LIKE '%sch'")
|
||
|
encu, = res.fetchone()
|
||
|
encu = encu.decode("utf-8")
|
||
|
encu = encu[encu.find("!"):]
|
||
|
encu = encu[:encu.find("\n")]
|
||
|
encu = bytes(encu, "utf-8")
|
||
|
res = cur.execute("SELECT value FROM data WHERE key LIKE '%key_iter'")
|
||
|
iterations, = res.fetchone()
|
||
|
iterations = int(iterations)
|
||
|
except:
|
||
|
return None, None
|
||
|
|
||
|
return iterations, encu
|
||
|
|
||
|
|
||
|
def main():
|
||
|
"""Entry point"""
|
||
|
if len(sys.argv) < 3:
|
||
|
sys.exit(f"Usage: {sys.argv[0]} <xml or sqlite file> <username (email)>")
|
||
|
|
||
|
file_name = sys.argv[1]
|
||
|
if not os.path.exists(file_name):
|
||
|
sys.exit(f"File {file_name} does not exist")
|
||
|
|
||
|
file_content = open_file(file_name)
|
||
|
magic_bytes = file_content[:5].decode("utf-8")
|
||
|
|
||
|
# Output will contain the following fields (in order), colon separated
|
||
|
encrypted_email = ""
|
||
|
iterations = -1
|
||
|
email = sys.argv[2].lower()
|
||
|
initialization_vector = ""
|
||
|
|
||
|
if magic_bytes == "LPB64":
|
||
|
# Android App
|
||
|
iterations = 100100
|
||
|
xml = b64decode(file_content[5:])
|
||
|
initialization_vector, encrypted_email = parse_vault(xml)
|
||
|
|
||
|
elif magic_bytes == "SQLit":
|
||
|
# Browser Extension
|
||
|
con = sqlite3.connect(file_name)
|
||
|
cur = con.cursor()
|
||
|
|
||
|
# First try Chromium based browsers
|
||
|
iterations, xml = sqlite_parse_chromium(cur)
|
||
|
if iterations and xml:
|
||
|
initialization_vector, encrypted_email = parse_vault(xml)
|
||
|
|
||
|
# Then try Firefox
|
||
|
if not encrypted_email or not iterations or not initialization_vector:
|
||
|
iterations, encu = sqlite_parse_firefox(cur)
|
||
|
initialization_vector, encrypted_email = parse_encu(encu)
|
||
|
|
||
|
# Finally give up
|
||
|
if not encrypted_email or not iterations or not initialization_vector:
|
||
|
sys.exit("Unexpected behaviour in SQLite database parsing")
|
||
|
|
||
|
con.close()
|
||
|
else:
|
||
|
sys.exit(f"Expected LPB64 or SQLit in file, but found {magic_bytes}")
|
||
|
|
||
|
print(f"{encrypted_email}:{iterations}:{email}:{initialization_vector}")
|
||
|
|
||
|
|
||
|
if __name__ == "__main__":
|
||
|
main()
|