From 5439b5c1984a591a92d3ab8e89b0c2d4574676cc Mon Sep 17 00:00:00 2001 From: Gabriele Gristina Date: Sun, 16 Apr 2023 15:25:31 +0200 Subject: [PATCH] Fixed test module and cleanup implementation of MetaMask short (26610) --- OpenCL/m26610-pure.cl | 11 +++--- src/modules/module_26610.c | 67 ++++++++++++++++++------------------ tools/test_modules/m26610.pm | 36 ++++--------------- 3 files changed, 44 insertions(+), 70 deletions(-) diff --git a/OpenCL/m26610-pure.cl b/OpenCL/m26610-pure.cl index 9c51ea572..5408c5cdf 100644 --- a/OpenCL/m26610-pure.cl +++ b/OpenCL/m26610-pure.cl @@ -21,11 +21,11 @@ typedef struct pbkdf2_sha256_tmp { - u32 ipad[8]; - u32 opad[8]; + u32 ipad[8]; + u32 opad[8]; - u32 dgst[32]; - u32 out[32]; + u32 dgst[32]; + u32 out[32]; } pbkdf2_sha256_tmp_t; @@ -34,7 +34,7 @@ typedef struct pbkdf2_sha256_aes_gcm u32 salt_buf[64]; u32 iv_buf[4]; u32 iv_len; - u32 ct_buf[784]; // TODO this can be smaller and would speedup the attack, only 64 bytes of ciphertext are allowed + u32 ct_buf[16]; u32 ct_len; } pbkdf2_sha256_aes_gcm_t; @@ -368,7 +368,6 @@ KERNEL_FQ void m26610_comp (KERN_ATTR_TMPS_ESALT (pbkdf2_sha256_tmp_t, pbkdf2_sh AES_GCM_decrypt (key, J0, ct, 32, pt, s_te0, s_te1, s_te2, s_te3, s_te4); - const int correct = is_valid_printable_32 (pt[0]) + is_valid_printable_32 (pt[1]) + is_valid_printable_32 (pt[2]) diff --git a/src/modules/module_26610.c b/src/modules/module_26610.c index 96ec0a12c..9aec101f9 100644 --- a/src/modules/module_26610.c +++ b/src/modules/module_26610.c @@ -18,7 +18,6 @@ static const u32 DGST_POS2 = 2; static const u32 DGST_POS3 = 3; static const u32 DGST_SIZE = DGST_SIZE_4_4; static const u32 HASH_CATEGORY = HASH_CATEGORY_CRYPTOCURRENCY_WALLET; -// 22610 generates a decryption key based on a password-guess and uses that to AES-GCM decrypt the data decrypts static const char *HASH_NAME = "MetaMask Wallet (short hash, plaintext check)"; static const u64 KERN_TYPE = 26610; static const u32 OPTI_TYPE = OPTI_TYPE_ZERO_BYTE @@ -28,7 +27,6 @@ static const u64 OPTS_TYPE = OPTS_TYPE_STOCK_MODULE | OPTS_TYPE_DEEP_COMP_KERNEL; static const u32 SALT_TYPE = SALT_TYPE_EMBEDDED; static const char *ST_PASS = "hashcat1"; -// hash generated using with python3 tools/metamask2hashcat.py --vault tools/2hashcat_tests/metamask2hashcat.json --shortdata static const char *ST_HASH = "$metamask-short$jfGI3TXguhb8GPnKSXFrMzRk2NCEc131Gt5G3kZr5+s=$h+BoIf2CQ5BEjaIOShFE7g==$R95fzGt4UQ0uwrcrVYnIi4UcSlWn9wlmer+//526ZDwYAp50K82F1u1oacYcdjjhuEvbZnWk/uBG00UkgLLlOw=="; 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; } @@ -48,11 +46,11 @@ const char *module_st_pass (MAYBE_UNUSED const hashconfig_t *hashconfig, typedef struct pbkdf2_sha256_tmp { - u32 ipad[8]; - u32 opad[8]; + u32 ipad[8]; + u32 opad[8]; - u32 dgst[32]; - u32 out[32]; + u32 dgst[32]; + u32 out[32]; } pbkdf2_sha256_tmp_t; @@ -61,7 +59,7 @@ typedef struct pbkdf2_sha256_aes_gcm u32 salt_buf[64]; u32 iv_buf[4]; u32 iv_len; - u32 ct_buf[784]; // TODO this can be smaller and would speedup the attack, only 64 bytes of ciphertext are allowed + u32 ct_buf[16]; u32 ct_len; } pbkdf2_sha256_aes_gcm_t; @@ -135,8 +133,6 @@ int module_hash_decode (MAYBE_UNUSED const hashconfig_t *hashconfig, MAYBE_UNUSE pbkdf2_sha256_aes_gcm_t *metamask = (pbkdf2_sha256_aes_gcm_t *) esalt_buf; - #define CT_MAX_LEN_BASE64 (((3136+16) * 8) / 6) + 3 // TODO this can be smaller and would speedup the attack, only 64 bytes of ciphertext are allowed - hc_token_t token; token.token_cnt = 4; @@ -161,8 +157,8 @@ int module_hash_decode (MAYBE_UNUSED const hashconfig_t *hashconfig, MAYBE_UNUSE | TOKEN_ATTR_VERIFY_BASE64A; token.sep[3] = '$'; - token.len_min[3] = 32; - token.len_max[3] = 100; // TODO this can be smaller and would speedup the attack, only 64 bytes of ciphertext are allowed + token.len_min[3] = 88; + token.len_max[3] = 88; token.attr[3] = TOKEN_ATTR_VERIFY_LENGTH | TOKEN_ATTR_VERIFY_BASE64A; @@ -170,10 +166,6 @@ int module_hash_decode (MAYBE_UNUSED const hashconfig_t *hashconfig, MAYBE_UNUSE if (rc_tokenizer != PARSER_OK) return (rc_tokenizer); - u8 tmp_buf[CT_MAX_LEN_BASE64] = { 0 }; - - size_t tmp_len = 0; - // iter salt->salt_iter = 10000 - 1; @@ -183,9 +175,11 @@ int module_hash_decode (MAYBE_UNUSED const hashconfig_t *hashconfig, MAYBE_UNUSE const u8 *salt_pos = token.buf[1]; const int salt_len = token.len[1]; + u8 tmp_buf[88+1]; + memset (tmp_buf, 0, sizeof (tmp_buf)); - tmp_len = base64_decode (base64_to_int, salt_pos, salt_len, tmp_buf); + size_t tmp_len = base64_decode (base64_to_int, salt_pos, salt_len, tmp_buf); if (tmp_len != 32) return (PARSER_SALT_LENGTH); @@ -231,20 +225,23 @@ int module_hash_decode (MAYBE_UNUSED const hashconfig_t *hashconfig, MAYBE_UNUSE tmp_len = base64_decode (base64_to_int, ct_pos, ct_len, tmp_buf); + if (tmp_len != 64) return (PARSER_CT_LENGTH); + memcpy ((u8 *) metamask->ct_buf, tmp_buf, tmp_len); u32 j = tmp_len / 4; - if ((tmp_len % 4) > 0) j++; - - for (u32 i = 0; i < j; i++) metamask->ct_buf[i] = byte_swap_32 (metamask->ct_buf[i]); + for (u32 i = 0; i < j; i++) + { + metamask->ct_buf[i] = byte_swap_32 (metamask->ct_buf[i]); + } metamask->ct_len = tmp_len; - digest[0] = (metamask->ct_buf[0]); - digest[1] = (metamask->ct_buf[1]); - digest[2] = (metamask->ct_buf[2]); - digest[3] = (metamask->ct_buf[3]); + digest[0] = metamask->ct_buf[0]; + digest[1] = metamask->ct_buf[1]; + digest[2] = metamask->ct_buf[2]; + digest[3] = metamask->ct_buf[3]; return (PARSER_OK); } @@ -255,40 +252,42 @@ int module_hash_encode (MAYBE_UNUSED const hashconfig_t *hashconfig, MAYBE_UNUSE // salt - #define SALT_LEN_BASE64 ((32 * 8) / 6) + 3 - #define IV_LEN_BASE64 ((16 * 8) / 6) + 3 - #define CT_MAX_LEN_BASE64 (((3136+16) * 8) / 6) + 3 // TODO this can be much smaller now, probably 32 ? + u8 salt_buf[44+1]; - u8 salt_buf[SALT_LEN_BASE64] = { 0 }; + memset (salt_buf, 0, sizeof (salt_buf)); base64_encode (int_to_base64, (const u8 *) salt->salt_buf, (const int) salt->salt_len, salt_buf); // iv - u32 tmp_iv_buf[4] = { 0 }; + u32 tmp_iv_buf[4]; tmp_iv_buf[0] = byte_swap_32 (metamask->iv_buf[0]); tmp_iv_buf[1] = byte_swap_32 (metamask->iv_buf[1]); tmp_iv_buf[2] = byte_swap_32 (metamask->iv_buf[2]); tmp_iv_buf[3] = byte_swap_32 (metamask->iv_buf[3]); - u8 iv_buf[IV_LEN_BASE64+1] = { 0 }; + u8 iv_buf[24+1]; + + memset (iv_buf, 0, sizeof (iv_buf)); base64_encode (int_to_base64, (const u8 *) tmp_iv_buf, (const int) metamask->iv_len, iv_buf); // ct + u32 tmp_buf[16]; + + memset (tmp_buf, 0, sizeof (tmp_buf)); + u32 ct_len = metamask->ct_len; u32 j = ct_len / 4; - if ((ct_len % 4) > 0) j++; - - u32 tmp_buf[784] = { 0 }; // TODO this can be smaller and would speedup the attack, only 64 bytes of ciphertext are allowed - for (u32 i = 0; i < j; i++) tmp_buf[i] = byte_swap_32 (metamask->ct_buf[i]); - u8 ct_buf[CT_MAX_LEN_BASE64] = { 0 }; + u8 ct_buf[88+1]; + + memset (ct_buf, 0, sizeof (ct_buf)); base64_encode (int_to_base64, (const u8 *) tmp_buf, (const int) metamask->ct_len, ct_buf); diff --git a/tools/test_modules/m26610.pm b/tools/test_modules/m26610.pm index 45dc4e18c..71f2d05b0 100644 --- a/tools/test_modules/m26610.pm +++ b/tools/test_modules/m26610.pm @@ -21,9 +21,6 @@ sub module_generate_hash my $iv = shift // random_hex_string (32); my $ct = shift; - my $ct_min_len = 30; - my $ct_max_len = 64; - my $kdf = Crypt::PBKDF2->new ( hasher => Crypt::PBKDF2->hasher_from_algorithm ('HMACSHA2', 256), @@ -31,34 +28,18 @@ sub module_generate_hash output_len => 32 ); + my $ct_len = 64; + my $salt_bin = pack ("H*", $salt); my $key = $kdf->PBKDF2 ($salt_bin, $word); my $iv_bin = pack ("H*", $iv); - my $pt; - if (defined $ct) - { - my $ct_bin = pack ("H*", $ct); + my $pt = "[{\"type\":\"HD Key Tree\",\"data\":{\"mnemonic\":[112,97,121,109,101,110,116,32,117,112,115,101,116,32,109,101,116,97,108,32,99,104,97,112,116,101,114,32,114,117,110,32,97,100,109,105,116,32,109,101,97,115,117,114,101,32,114,101,109,105,110,100,32,115,117,112,112,108,121,32,104,111,112,101,32,101,110,101,109,121,32,104,101,100,103,101,104,111,103],\"numberOfAccounts\":1,\"hdPath\":\"m/44'/60'/0'/0\"}}]"; - my $data_bin = $ct_bin; - - my $aes = Crypt::AuthEnc::GCM->new ("AES", $key, $iv_bin); - - $pt = $aes->decrypt_add ($data_bin); - - } - else - { - # generate plaintext - - # TODO now the data is all ASCII 'a', would be better to have it mimic the same structure as the reference data: - # [{"type":"HD Key Tree","data":{"mnemonic":[112,97,121,109,101,110,116,32,117,112,115,101,116,32,109,101,116,97,108,32,99,104,97,112,116,101,114,32,114,117,110,32,97,100,109,105,116,32,109,101,97,115,117,114,101,32,114,101,109,105,110,100,32,115,117,112,112,108,121,32,104,111,112,101,32,101,110,101,109,121,32,104,101,100,103,101,104,111,103],"numberOfAccounts":1,"hdPath":"m/44'/60'/0'/0"}}] - # generated from tools/2hashcat_tests/metamask2hashcat-test.py - $pt = "a" x ($ct_min_len + int (rand ($ct_max_len - $ct_min_len)) + 1); - } + $pt = substr ($pt, 0, $ct_len); my $aes = Crypt::AuthEnc::GCM->new ("AES", $key, $iv_bin); @@ -80,7 +61,7 @@ sub module_verify_hash my $hash = substr ($line, 0, $idx); my $word = substr ($line, $idx + 1); - return unless substr ($hash, 0, 10) eq '$metamask-short$'; + return unless substr ($hash, 0, 16) eq '$metamask-short$'; my (undef, $signature, $salt, $iv, $ct) = split '\$', $hash; @@ -95,12 +76,7 @@ sub module_verify_hash return unless length $salt_bin == 32; return unless length $iv_bin == 16; - - my $ct_len = length ($ct_bin); - my $ct_min_len = 30; - my $ct_max_len = 3136; - - return unless ($ct_len >= $ct_min_len && $ct_len <= $ct_max_len); + return unless length $ct_bin == 64; my $word_packed = pack_if_HEX_notation ($word);