247CTF - Encrypted USB Drive

Encrypted USB Drive

An important USB drive containing sensitive information has been encrypted by some new ransomware variant. Can you reverse the ransomware encryption function and recover the files?

1. Initial Analysis

We are provided with a BitLocker-encrypted USB image (encrypted_usb.dd) and a large list of potential recovery keys (recovery_keys_dump.txt).

1
2
❯ file encrypted_usb.dd
encrypted_usb.dd: DOS/MBR boot sector, code offset 0x58+2, OEM-ID "-FVE-FS-", ... FAT (32 bit) ... NTFS ...

The goal is to find the correct recovery key, mount the image, and then deal with the "ransomware" that has encrypted the files inside.

2. BitLocker Decryption

Attempting John the Ripper

First, I tried using bitlocker2john to extract the hash and then cracked it with the provided recovery keys.

1
2
3
4
❯ bitlocker2john -i encrypted_usb.dd > hash
❯ john --wordlist=./recovery_keys_dump.txt --fork=6 hash
...
0 password hashes cracked, 4 left

John didn't seem to find the key directly (possibly due to format mismatch or configuration). Instead of debugging the hash format, I moved to a more direct approach: brute-forcing the mount command using dislocker.

Brute-forcing with Dislocker

I wrote a simple bash script to iterate through the recovery_keys_dump.txt and attempt to mount the image.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#!/usr/bin/env bash

IMG_FILE="encrypted_usb.dd"
MNT_DIR="/mnt/bitlocker_img"
USB_DIR="/mnt/unlocked_usb"
KEYS_FILE="recovery_keys_dump.txt"

if [[ $EUID -ne 0 ]]; then
echo "[-] Error: This script must be run as root."
exit 1
fi

mkdir -p "$MNT_DIR" "$USB_DIR"

while IFS= read -r key; do
# -p specifies the recovery password
if dislocker -V "$IMG_FILE" -p"$key" -- "$MNT_DIR" 2>/dev/null; then
echo -e "\n[+] Recovery Key Found: $key"
break
fi
done < "$KEYS_FILE"

Running the script successfully identified the key:

1
2
3
[+] Recovery Key Found: 334565-564641-129580-248655-292215-551991-326733-393679

sudo mount /mnt/bitlocker_img/dislocker-file /mnt/unlocked_usb

3. Ransomware Analysis

Inside the mounted drive, we find several encrypted files and the ransomware binary itself:

1
2
3
4
5
6
7
8
9
ls -lh /mnt/unlocked_usb/
total 3.2M
-rwxrwxrwx 1 root root 474K Oct 8 2022 crypto_passphrase.png.xxx.crypt
-rwxrwxrwx 1 root root 15K Oct 8 2022 cryptor
-rwxrwxrwx 1 root root 9.2K Oct 8 2022 do_not_open.png.xxx.crypt
-rwxrwxrwx 1 root root 133K Oct 8 2022 meeting_minutes.png.xxx.crypt
-rwxrwxrwx 1 root root 889K Oct 8 2022 passwords.png.xxx.crypt
-rwxrwxrwx 1 root root 386 Oct 8 2022 ransom.txt
-rwxrwxrwx 1 root root 1.7M Oct 8 2022 salary_screenshot.png.xxx.crypt

The ransom.txt claims to use a "secure XOR encryption algorithm".

1
2
Your files have been encrypted using a secure xor encryption algorithm and are completely unrecoverable!
To decrypt your files, you need your secret encryption key.

4. Recovery (Known Plaintext Attack)

Since the files are PNGs, we can perform a Known Plaintext Attack. We know that PNG files always start with the same 8-byte magic header: 89 50 4E 47 0D 0A 1A 0A.

By XORing the first 8 bytes of an encrypted file with this known PNG header, we can recover the XOR key.

Using CyberChef:

  1. Input the first few bytes of do_not_open.png.xxx.crypt.
  2. XOR with the PNG magic bytes 89 50 4E 47 0D 0A 1A 0A.
  3. The result reveals the key repeats as 66 63 6f 79 (ASCII: fcoy).

Applying the XOR key fcoy to the entire file do_not_open.png.xxx.crypt recovers the original image containing the flag.

247CTF{494f7cceb2baf33a0879543fe673blae}

5. Deep Dive: Reversing the cryptor Binary

I was curious about how the binary actually worked, so I threw it into IDA Pro.

Main Logic

The program expects a 4-character key as a command-line argument. It then iterates through the current directory, looking for files with the .xxx extension.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
__int64 __fastcall main(int a1, char **a2, char **a3)
{
// ...
// Key must be exactly 4 bytes long
if ( a1 != 2 || strlen(a2[1]) != 4 || (unsigned int)check_key_validity(a2[1]) != 1 )
return 1;

dirp = opendir(".");
if ( dirp )
{
while ( (v5 = readdir(dirp)) != 0 )
{
if ( (unsigned int)is_target_extension(v5->d_name) == 1 )
{
strcpy(dest, v5->d_name);
strcat(dest, ".crypt"); // Append .crypt to original name
encrypt_file(v5->d_name, dest, a2[1]);
}
}
closedir(dirp);
}
return 0;
}

Encryption Function

The encryption is indeed a simple byte-by-byte XOR using the 4-byte key provided.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
unsigned __int64 __fastcall encrypt_file(char *source, char *dest, char *key)
{
// ...
stream = fopen(source, "rb");
v16 = fopen(dest, "wb");
key_len = strlen(key);

do
{
// Read chunks matching the key length
bytes_read = fread(ptr, 1u, key_len, stream);
for ( i = 0; i < bytes_read; ++i )
*((_BYTE *)ptr + i) ^= key[i]; // XOR logic
fwrite(ptr, 1u, bytes_read, v16);
}
while ( bytes_read == key_len );

fclose(stream);
fclose(v16);
return ...;
}

The analysis confirms the Known Plaintext Attack was the correct approach, as the key length was short (4 bytes) and applied cyclically.