Merge pull request #3485 from Lars-Saetaberget/lastpass_iv

Add support for non-zero IV to lastpass (-m 6800)
pull/3493/head
Jens Steube 2 years ago committed by GitHub
commit 1f0fb154a5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -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;

@ -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…
Cancel
Save