mirror of
https://github.com/hashcat/hashcat.git
synced 2025-02-08 05:32:41 +00:00
Merge pull request #3485 from Lars-Saetaberget/lastpass_iv
Add support for non-zero IV to lastpass (-m 6800)
This commit is contained in:
commit
1f0fb154a5
@ -18,6 +18,11 @@
|
||||
#define COMPARE_S M2S(INCLUDE_PATH/inc_comp_single.cl)
|
||||
#define COMPARE_M M2S(INCLUDE_PATH/inc_comp_multi.cl)
|
||||
|
||||
typedef struct lastpass
|
||||
{
|
||||
u32 iv[4];
|
||||
} lastpass_t;
|
||||
|
||||
typedef struct lastpass_tmp
|
||||
{
|
||||
u32 ipad[8];
|
||||
@ -70,7 +75,7 @@ DECLSPEC void hmac_sha256_run_V (PRIVATE_AS u32x *w0, PRIVATE_AS u32x *w1, PRIVA
|
||||
sha256_transform_vector (w0, w1, w2, w3, digest);
|
||||
}
|
||||
|
||||
KERNEL_FQ void m06800_init (KERN_ATTR_TMPS (lastpass_tmp_t))
|
||||
KERNEL_FQ void m06800_init (KERN_ATTR_TMPS_ESALT (lastpass_tmp_t, lastpass_t))
|
||||
{
|
||||
/**
|
||||
* base
|
||||
@ -154,7 +159,7 @@ KERNEL_FQ void m06800_init (KERN_ATTR_TMPS (lastpass_tmp_t))
|
||||
}
|
||||
}
|
||||
|
||||
KERNEL_FQ void m06800_loop (KERN_ATTR_TMPS (lastpass_tmp_t))
|
||||
KERNEL_FQ void m06800_loop (KERN_ATTR_TMPS_ESALT (lastpass_tmp_t, lastpass_t))
|
||||
{
|
||||
const u64 gid = get_global_id (0);
|
||||
|
||||
@ -260,7 +265,7 @@ KERNEL_FQ void m06800_loop (KERN_ATTR_TMPS (lastpass_tmp_t))
|
||||
}
|
||||
}
|
||||
|
||||
KERNEL_FQ void m06800_comp (KERN_ATTR_TMPS (lastpass_tmp_t))
|
||||
KERNEL_FQ void m06800_comp (KERN_ATTR_TMPS_ESALT (lastpass_tmp_t, lastpass_t))
|
||||
{
|
||||
const u64 gid = get_global_id (0);
|
||||
const u64 lid = get_local_id (0);
|
||||
@ -367,6 +372,11 @@ KERNEL_FQ void m06800_comp (KERN_ATTR_TMPS (lastpass_tmp_t))
|
||||
out[2] = hc_swap32_S (out[2]);
|
||||
out[3] = hc_swap32_S (out[3]);
|
||||
|
||||
out[0] ^= esalt_bufs[DIGESTS_OFFSET_HOST].iv[0];
|
||||
out[1] ^= esalt_bufs[DIGESTS_OFFSET_HOST].iv[1];
|
||||
out[2] ^= esalt_bufs[DIGESTS_OFFSET_HOST].iv[2];
|
||||
out[3] ^= esalt_bufs[DIGESTS_OFFSET_HOST].iv[3];
|
||||
|
||||
truncate_block_4x4_le_S (out, salt_len);
|
||||
|
||||
if ((out[0] == salt_buf[0])
|
||||
|
@ -14,6 +14,7 @@
|
||||
## Technical
|
||||
##
|
||||
|
||||
- Modules: Added support for non-zero IVs for -m 6800 (Lastpass). Also added `tools/lastpass2hashcat.py`
|
||||
- Status Code: Add specific return code for self-test fail (-11)
|
||||
|
||||
* changes v6.2.5 -> v6.2.6
|
||||
|
@ -25,7 +25,7 @@ static const u64 OPTS_TYPE = OPTS_TYPE_STOCK_MODULE
|
||||
| OPTS_TYPE_PT_GENERATE_LE;
|
||||
static const u32 SALT_TYPE = SALT_TYPE_EMBEDDED;
|
||||
static const char *ST_PASS = "hashcat";
|
||||
static const char *ST_HASH = "82dbb8ccc9c7ead8c38a92a6b5740f94:500:pmix@trash-mail.com";
|
||||
static const char *ST_HASH = "02eb97e869e0ddc7dc760fc633b4b54d:100100:pmix@trash-mail.com:9b071db7b8e265d4cadd3eb65ac0864a";
|
||||
|
||||
u32 module_attack_exec (MAYBE_UNUSED const hashconfig_t *hashconfig, MAYBE_UNUSED const user_options_t *user_options, MAYBE_UNUSED const user_options_extra_t *user_options_extra) { return ATTACK_EXEC; }
|
||||
u32 module_dgst_pos0 (MAYBE_UNUSED const hashconfig_t *hashconfig, MAYBE_UNUSED const user_options_t *user_options, MAYBE_UNUSED const user_options_extra_t *user_options_extra) { return DGST_POS0; }
|
||||
@ -42,6 +42,11 @@ u32 module_salt_type (MAYBE_UNUSED const hashconfig_t *hashconfig,
|
||||
const char *module_st_hash (MAYBE_UNUSED const hashconfig_t *hashconfig, MAYBE_UNUSED const user_options_t *user_options, MAYBE_UNUSED const user_options_extra_t *user_options_extra) { return ST_HASH; }
|
||||
const char *module_st_pass (MAYBE_UNUSED const hashconfig_t *hashconfig, MAYBE_UNUSED const user_options_t *user_options, MAYBE_UNUSED const user_options_extra_t *user_options_extra) { return ST_PASS; }
|
||||
|
||||
typedef struct lastpass
|
||||
{
|
||||
u32 iv[4];
|
||||
} lastpass_t;
|
||||
|
||||
typedef struct lastpass_tmp
|
||||
{
|
||||
u32 ipad[8];
|
||||
@ -52,6 +57,13 @@ typedef struct lastpass_tmp
|
||||
|
||||
} lastpass_tmp_t;
|
||||
|
||||
u64 module_esalt_size (MAYBE_UNUSED const hashconfig_t *hashconfig, MAYBE_UNUSED const user_options_t *user_options, MAYBE_UNUSED const user_options_extra_t *user_options_extra)
|
||||
{
|
||||
const u64 esalt_size = (const u64) sizeof (lastpass_t);
|
||||
|
||||
return esalt_size;
|
||||
}
|
||||
|
||||
bool module_unstable_warning (MAYBE_UNUSED const hashconfig_t *hashconfig, MAYBE_UNUSED const user_options_t *user_options, MAYBE_UNUSED const user_options_extra_t *user_options_extra, MAYBE_UNUSED const hc_device_param_t *device_param)
|
||||
{
|
||||
// AMD Radeon Pro W5700X Compute Engine; 1.2 (Apr 22 2021 21:54:44); 11.3.1; 20E241
|
||||
@ -120,7 +132,7 @@ int module_hash_decode (MAYBE_UNUSED const hashconfig_t *hashconfig, MAYBE_UNUSE
|
||||
|
||||
hc_token_t token;
|
||||
|
||||
token.token_cnt = 3;
|
||||
token.token_cnt = 4;
|
||||
|
||||
token.len_min[0] = 32;
|
||||
token.len_max[0] = 64;
|
||||
@ -138,6 +150,12 @@ int module_hash_decode (MAYBE_UNUSED const hashconfig_t *hashconfig, MAYBE_UNUSE
|
||||
token.sep[2] = ':';
|
||||
token.attr[2] = TOKEN_ATTR_VERIFY_LENGTH;
|
||||
|
||||
token.len_min[3] = 32;
|
||||
token.len_max[3] = 32;
|
||||
token.sep[3] = ':';
|
||||
token.attr[3] = TOKEN_ATTR_VERIFY_LENGTH
|
||||
| TOKEN_ATTR_VERIFY_HEX;
|
||||
|
||||
const int rc_tokenizer = input_tokenizer ((const u8 *) line_buf, line_len, &token);
|
||||
|
||||
if (rc_tokenizer != PARSER_OK) return (rc_tokenizer);
|
||||
@ -165,6 +183,12 @@ int module_hash_decode (MAYBE_UNUSED const hashconfig_t *hashconfig, MAYBE_UNUSE
|
||||
|
||||
if (parse_rc == false) return (PARSER_SALT_LENGTH);
|
||||
|
||||
lastpass_t *lastpass = (lastpass_t *) esalt_buf;
|
||||
|
||||
const int iv_size = hex_decode ((const u8 *) token.buf[3], token.len[3], (u8 *) lastpass->iv);
|
||||
|
||||
if (iv_size != sizeof (lastpass->iv)) return (PARSER_IV_LENGTH);
|
||||
|
||||
return (PARSER_OK);
|
||||
}
|
||||
|
||||
@ -172,19 +196,25 @@ int module_hash_encode (MAYBE_UNUSED const hashconfig_t *hashconfig, MAYBE_UNUSE
|
||||
{
|
||||
const u32 *digest = (const u32 *) digest_buf;
|
||||
|
||||
const lastpass_t *lastpass = (const lastpass_t *) esalt_buf;
|
||||
|
||||
char tmp_salt[SALT_MAX * 2];
|
||||
|
||||
const int salt_len = generic_salt_encode (hashconfig, (const u8 *) salt->salt_buf, (const int) salt->salt_len, (u8 *) tmp_salt);
|
||||
|
||||
tmp_salt[salt_len] = 0;
|
||||
|
||||
return snprintf (line_buf, line_size, "%08x%08x%08x%08x:%u:%s",
|
||||
return snprintf (line_buf, line_size, "%08x%08x%08x%08x:%u:%s:%08x%08x%08x%08x",
|
||||
digest[0],
|
||||
digest[1],
|
||||
digest[2],
|
||||
digest[3],
|
||||
salt->salt_iter + 1,
|
||||
tmp_salt);
|
||||
tmp_salt,
|
||||
byte_swap_32(lastpass->iv[0]),
|
||||
byte_swap_32(lastpass->iv[1]),
|
||||
byte_swap_32(lastpass->iv[2]),
|
||||
byte_swap_32(lastpass->iv[3]));
|
||||
}
|
||||
|
||||
void module_init (module_ctx_t *module_ctx)
|
||||
@ -207,7 +237,7 @@ void module_init (module_ctx_t *module_ctx)
|
||||
module_ctx->module_dgst_pos3 = module_dgst_pos3;
|
||||
module_ctx->module_dgst_size = module_dgst_size;
|
||||
module_ctx->module_dictstat_disable = MODULE_DEFAULT;
|
||||
module_ctx->module_esalt_size = MODULE_DEFAULT;
|
||||
module_ctx->module_esalt_size = module_esalt_size;
|
||||
module_ctx->module_extra_buffer_size = MODULE_DEFAULT;
|
||||
module_ctx->module_extra_tmp_size = MODULE_DEFAULT;
|
||||
module_ctx->module_extra_tuningdb_block = MODULE_DEFAULT;
|
||||
|
166
tools/lastpass2hashcat.py
Executable file
166
tools/lastpass2hashcat.py
Executable file
@ -0,0 +1,166 @@
|
||||
#!/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()
|
@ -18,9 +18,9 @@ sub module_generate_hash
|
||||
{
|
||||
my $word = shift;
|
||||
my $salt = shift;
|
||||
my $iter = shift // 500;
|
||||
my $iter = shift // 100100;
|
||||
|
||||
my $iv = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00";
|
||||
my $iv = random_bytes(16);
|
||||
|
||||
my $hasher = Crypt::PBKDF2->hasher_from_algorithm ('HMACSHA2', 256);
|
||||
|
||||
@ -45,7 +45,9 @@ sub module_generate_hash
|
||||
|
||||
my $hash_buf = substr (unpack ("H*", $encrypt), 0, 32);
|
||||
|
||||
my $hash = sprintf ("%s:%i:%s", $hash_buf, $iter, $salt);
|
||||
my $iv_buf = unpack("H*", $iv);
|
||||
|
||||
my $hash = sprintf ("%s:%i:%s:%s", $hash_buf, $iter, $salt, $iv_buf);
|
||||
|
||||
return $hash;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user