Description:
A man named Michael Tanz bought 30 bitcoin in 2013 and stored it in his hardware wallet. He set the password for his hardware wallet through a password generator named “V1”. He remembers that his password is 20 characters long, and consisted of only alphanumeric characters and symbols. Michael however is not exactly sure of the date he generated the password - he knows it was between the 10th and the 11th of December 2013. Can you crack the password and help him recover his bitcoin ?
initial analysis#
Отримали два файли.
1$ file *
2decrypt.py: Python script, ASCII text executable
3V1: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=7d0ef4ad0fae598a68cba943d3a34c96ad6461d2, for GNU/Linux 4.4.0, not strippedскрипт decrypt.py розшифровує дані з використанням ключа, ключ потрібно дізнатися.
1from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
2from cryptography.hazmat.backends import default_backend
3from cryptography.hazmat.primitives import padding
4
5def decrypt_message(encrypted_message, key):
6 try:
7 key = key.ljust(32, b'\x00')
8 iv = encrypted_message[:16]
9 ciphertext = encrypted_message[16:]
10 cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend=default_backend())
11 decryptor = cipher.decryptor()
12 decrypted_padded_message = decryptor.update(ciphertext) + decryptor.finalize()
13 unpadder = padding.PKCS7(algorithms.AES.block_size).unpadder()
14 decrypted_message = unpadder.update(decrypted_padded_message) + unpadder.finalize()
15 return decrypted_message.decode()
16 except Exception as e:
17 print(f"An error occurred during decryption: {e}")
18 return None
19
20if __name__ == "__main__":
21 encrypted_message = bytes.fromhex('ad24426047b0ffb03b679773664838462a6f00bdcaf0589dd1748e9ed5c568601edc87d974894f9dd9b98cc35535145c494eb0af84c8f78d440a033c91c7de62d506d8cabdc2a10138b95139bbe60e89')
22 key = input("Please input your key : ")
23 decrypted_message = decrypt_message(encrypted_message, key.encode())
24 if decrypted_message:
25 print(f"Decrypted message: {decrypted_message}")
26 else:
27 print("Decryption failed or no valid message found.")by running executable nothing happend:
1$ ./V1
2Enter len (max 50):
3Include sym? (yes/no):yes
4Include sym? (yes/no):
5Include num? (yes/no):
6Generated password:
7
8$ ./V1
9Enter len (max 50):
10Include sym? (yes/no):no
11Include sym? (yes/no):
12Include num? (yes/no):
13Generated password: reversing with ida#
main func do nothing interested. from functions listing i saw unused generate_password() function. so, i started analysis it
alphabet for key is "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "@#$%^&*_+" + "0123456789":
1qmemcpy(v14, "abcdefghijklmnopqrstuvwxyz", 26);
2// ...[snip]...
3std::string::_M_append(&v14, "ABCDEFGHIJKLMNOPQRSTUVWXYZ", 26);
4// ...[snip]...
5std::string::_M_append(&v14, "!@#$%^&*_+", 10);
6// ...[snip]...
7std::string::_M_append(&v14, "0123456789", 10);cryptography using time-based seeds for password generation, whihc is insecure. its becomes feasible to brute-force
1v6 = localtime(&timer);
2srand(
3100000000 * (v6->tm_mon + 1)
4+ 1410065408 * (v6->tm_year + 1900)
5+ 10000 * v6->tm_hour
6+ v6->tm_sec
7+ 100 * v6->tm_min
8+ 1000000 * v6->tm_mday);the function then generates password character by character. for a2 iterations (password length), it:
- picks random char from alphabet using
rand() % charset_length - appends it to the output string
1if ( a2 > 0 )
2{
3 for ( i = 0; i != a2; ++i )
4 {
5 v9 = *(v14 + rand() % v15); // pick random char
6 // ... string manipulation to append v9 to result ...
7 *(*a1 + v10) = v9; // add char to password
8 a1[1] = v11; // update length
9 *(*a1 + v10 + 1) = 0; // null terminator
10 }
11}exploitation strategy#
since password generation is deterministic (same timestamp = same password), i can:
- iterate through all timestamps in dec 10-11, 2013
- for each timestamp, calculate the seed value
- generate 20-char password using C’s rand() with that seed
- attempt AES decryption with generated password
recreated the seed calculation and password generation logic in python using ctypes to call C’s srand()/rand():
1from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
2from cryptography.hazmat.backends import default_backend
3from cryptography.hazmat.primitives import padding
4import ctypes
5from datetime import datetime, timedelta
6
7def decrypt_message(encrypted_message, key):
8 try:
9 key = key.ljust(32, b'\x00')
10 iv = encrypted_message[:16]
11 ciphertext = encrypted_message[16:]
12 cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend=default_backend())
13 decryptor = cipher.decryptor()
14 decrypted_padded_message = decryptor.update(ciphertext) + decryptor.finalize()
15 unpadder = padding.PKCS7(algorithms.AES.block_size).unpadder()
16 decrypted_message = unpadder.update(decrypted_padded_message) + unpadder.finalize()
17 return decrypted_message.decode()
18 except Exception as e:
19 return None
20
21def pwd_gen(length, seed_value):
22 charset = "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "!@#$%^&*_+" + "0123456789"
23
24 libc = ctypes.CDLL(None)
25 libc.srand(seed_value)
26
27 pwd = ""
28 for _ in range(length):
29 rand_val = libc.rand()
30 pwd += charset[rand_val % len(charset)]
31
32 return pwd
33
34def seed_gen(year, month, day, hour, minute, second):
35 return (100000000 * (month + 1) +
36 1410065408 * (year + 1900) +
37 10000 * hour +
38 second +
39 100 * minute +
40 1000000 * day)
41
42
43encrypted_message = bytes.fromhex('ad24426047b0ffb03b679773664838462a6f00bdcaf0589dd1748e9ed5c568601edc87d974894f9dd9b98cc35535145c494eb0af84c8f78d440a033c91c7de62d506d8cabdc2a10138b95139bbe60e89')
44
45start = datetime(2013, 12, 10, 0, 0, 0)
46end = datetime(2013, 12, 11, 23, 59, 59)
47
48c = start
49count = 0
50
51while c <= end:
52 seed = seed_gen(
53 c.year - 1900,
54 c.month - 1,
55 c.day,
56 c.hour,
57 c.minute,
58 c.second
59 )
60
61 pwd = pwd_gen(20, seed)
62
63 d = decrypt_message(encrypted_message, pwd.encode())
64
65 if d and d.isprintable():
66 print(f"Password: {pwd}")
67 print(f"Decrypted message: {d}")
68 break
69
70 count += 1
71 if count % 10000 == 0:
72 print(f"Checked {count} timestamps...")
73
74 c += timedelta(seconds=1) 1$ python3 dec.py
2Checked 10000 timestamps...
3Checked 20000 timestamps...
4Checked 30000 timestamps...
5Checked 40000 timestamps...
6Checked 50000 timestamps...
7Checked 60000 timestamps...
8Checked 70000 timestamps...
9Checked 80000 timestamps...
10Checked 90000 timestamps...
11Checked 100000 timestamps...
12Checked 110000 timestamps...
13Checked 120000 timestamps...
14Checked 130000 timestamps...
15Password: eWXtk*Oe%j5cof7Od08G
16Decrypted message: d 30 Bitcoins! , HTB{T1me_0n_the_B1t5_1386784885}