Человеческий фактор (Human Factor)
Человеческий фактор (Human Factor)
Platform: hackerlab | Category: Forensics | Type: Challenge | Difficulty: Easy | OS: Linux | Author: XESXOR | Date: 2026-01-05 | Status: Solved Techniques: aes_xts_decryption, dictionary_attack, encrypted_container_identification, manual_truecrypt_decrypt, pbkdf2_key_derivation, tcp_stream_reconstruction, truecrypt_password_cracking
Summary
Task: forensics PCAP where an intern uploads a 1 MiB encrypted file over TCP; reconstruct it and decrypt. Solution: carve the TrueCrypt volume from TCP stream 4, crack its weak dictionary password (kittykitty) with hashcat mode 6221 (SHA512+AES-XTS-512), then manually decrypt the AES-XTS volume to recover a FAT12 image holding FLAG.TXT.
Человеческий фактор (Human Factor) — HackerLab
Description
Конфиденциальные данные компании были получены хакерами. Узнайте, как это произошло.
English summary: A single network capture is provided. A plaintext chat in the capture shows a senior employee telling an intern to "encrypt and send your file — just use a normal password." The intern then uploads a file. The goal is to reconstruct that uploaded file, defeat its weak password, and recover the flag inside.
Crucial author hints (platform chat, author Андрей/Exited3n): "true crypt" and "approach it from the very end" (the TrueCrypt backup header lives at the end of the volume; the human factor is the weak dictionary password).
Analysis
Step 1: PCAP examination
task.zip contains only traffic.pcapng — a loopback (127.0.0.1) capture with 5 TCP streams.
- Streams 0–3: plaintext ASCII chat on ports 3434/3535 (no length framing):
- "Hey, did you drop the server yesterday? :)"
- "Yes, I don't want to think about it."
- "Okay, intern, I get you. Encrypt and send me your file. Just use a normal password!"
- "OK"
- Stream 4 (
49883 -> 3535): client uploads exactly 1,048,576 bytes (1 MiB); the server returns 0 application bytes. This is the encrypted file.
Reconstruct the client→server payload of stream 4:
bash read-only • copy allowedtshark -r traffic.pcapng -q -z follow,tcp,raw,4 # or extract only client->server bytes: tshark -r traffic.pcapng \ -Y "tcp.stream==4 && tcp.srcport==49883 && tcp.len>0" \ -T fields -e tcp.payload | tr -d '\n' | xxd -r -p > upload.bin
upload.bin: 1,048,576 bytes, md5 6a65732a3cd19da359cc020d1bc852b1.
Step 2: Identifying the container
- Entropy ~7.997 across every window (full entropy)
- NO file header, NO `Salted__` prefix, NO recognizable magic at offset 0
- Length is a multiple of 16 and exactly a round 1 MiB
- All 16-byte blocks unique (no ECB/CTR keystream reuse)
Headerless + full entropy + round size = an encrypted disk container, not a raw cipher over plaintext. TrueCrypt/VeraCrypt have no plaintext magic because the header itself is encrypted. The author hint "true crypt" confirms it: this is a TrueCrypt volume.
This identification is the whole challenge. Once you stop trying to "guess the AES mode" and treat it as a TrueCrypt container, the rest is mechanical.
Solution
Step 3: Crack the password with hashcat
hashcat reads the raw TrueCrypt container file directly for modes 62xx — do NOT feed it truecrypt2hashcat.py output, the $truecrypt$ string format will not load.
Iterate over the TrueCrypt legacy sub-modes:
| Mode | KDF + cipher |
|---|---|
| 6211 | RIPEMD160 + AES XTS512 (default) |
| 6212/6213 | RIPEMD160 cascades |
| 6221 | SHA512 + AES XTS512 |
| 6222/6223 | SHA512 cascades |
| 6231/6232/6233 | Whirlpool |
| 6241/6242/6243 | RIPEMD160 boot-mode |
bash read-only • copy allowed# 6211 exhausted with no hit; 6221 cracked it: hashcat -m 6221 upload.bin rockyou.txt # Recovered password: kittykitty
GPU note: on a rented RTX 4090 (vast.ai) mode 6211 runs ~1.4 MH/s vs ~25 kH/s on CPU (~56×). GPU rental is justified here precisely because it is a known slow PBKDF2 KDF + a dictionary password + a correctness oracle (TrueCrypt header validation gives a hard yes/no).
Step 4: Decrypt the volume manually
The vast.ai GPU container had no device-mapper (dm_mod), so cryptsetup open --type tcrypt failed. Solution: decrypt in pure Python (pycryptodome). (Alternatively use veracrypt/tcplay on a host with dm support.)
TrueCrypt SHA512 + AES-XTS-512 layout:
salt= first 64 bytes of the volume.- Header key =
PBKDF2-HMAC-SHA512(password, salt, iterations=1000, dklen=64)→ key1 (data, 32B) | key2 (tweak, 32B). - Decrypt encrypted header
[64:512]with AES-XTS (sector 0); verify magic== b'TRUE'. - Master keys at decrypted-header offset
192..256(mk1[192:224], mk2[224:256]). - Volume data starts at offset
131072; decrypt with AES-XTS using master keys, sector number =data_offset / 512.
python read-only • copy allowed#!/usr/bin/env python3 # TrueCrypt volume decryptor: SHA512 + AES-XTS-512 (hashcat mode 6221) import sys, hashlib, struct, re from Crypto.Cipher import AES PASSWORD = b"kittykitty" data = open("upload.bin", "rb").read() salt = data[:64] # first 64 bytes = salt header_enc = data[64:512] # encrypted header (448 bytes) dk = hashlib.pbkdf2_hmac("sha512", PASSWORD, salt, 1000, 64) key1, key2 = dk[:32], dk[32:64] def xts_decrypt(ct, k1, k2, sector_start=0, sector_size=512): out = bytearray() enc_tweak = AES.new(k2, AES.MODE_ECB) dec = AES.new(k1, AES.MODE_ECB) for i in range(0, len(ct), sector_size): block = ct[i:i+sector_size] sector = sector_start + i // sector_size t = int.from_bytes(enc_tweak.encrypt(struct.pack("<Q", sector) + b"\x00"*8), "little") for j in range(0, len(block), 16): tb = t.to_bytes(16, "little") xblk = bytes(a ^ b for a, b in zip(block[j:j+16], tb)) pblk = dec.decrypt(xblk) out += bytes(a ^ b for a, b in zip(pblk, tb)) carry = (t >> 127) & 1 t = ((t << 1) & ((1 << 128) - 1)) ^ (0x87 if carry else 0) return bytes(out) # Header is XTS-encrypted as one chunk at sector 0 hdr = xts_decrypt(header_enc, key1, key2, sector_start=0, sector_size=len(header_enc)) print("Header magic:", hdr[:4], "(should be b'TRUE')") assert hdr[:4] == b"TRUE", hdr[:8].hex() mk1, mk2 = hdr[192:224], hdr[224:256] # volume master keys DATA_OFFSET = 131072 pt = xts_decrypt(data[DATA_OFFSET:], mk1, mk2, sector_start=DATA_OFFSET // 512, sector_size=512) open("decrypted.bin", "wb").write(pt) for m in re.finditer(rb"CODEBY\{[^}]*\}", pt): print("FLAG:", m.group().decode())
decrypted.bin is a FAT12 filesystem (\xeb\x3c\x90MSDOS5.0 ... FAT12) containing FLAG.TXT:
bash read-only • copy allowedfile decrypted.bin # DOS/MBR boot sector, FAT12 strings decrypted.bin | grep CODEBY # CODEBY{REDACTED}
Tools
tshark/wireshark— TCP stream reconstruction (follow,tcp,raw,4).hashcat— modes 62xx (TrueCrypt) / 137xx (VeraCrypt); reads the raw volume directly. Iterate RIPEMD160 / SHA512 / Whirlpool × XTS 512/1024/1536 × boot variants.python3+pycryptodome— manual PBKDF2-HMAC-SHA512 + AES-XTS-512 decryption when device-mapper is unavailable.
Lessons
- Headerless data + entropy ~8.0 + round size ⇒ encrypted container. Stop guessing AES modes; identify the container format.
- TrueCrypt/VeraCrypt have no plaintext magic — the header is encrypted. Use context and hints.
- hashcat reads the raw volume for modes 62xx/137xx; the
$truecrypt$text format does not load. Try every sub-mode (default 6211 RIPEMD160 is common, but 6221 SHA512 here). - GPU rental is justified only when you have a known slow KDF + dictionary password + a success oracle (TrueCrypt header validation provides this).
- When
cryptsetup/dm_modis unavailable (containers, restricted hosts), implement the TrueCrypt PBKDF2 + AES-XTS decryption manually in Python.
Dead ends (do not repeat)
- The string
@2FtP=Gat offset 796387 inupload.binis statistical noise (in 1 MiB of random data ~950 coincidental 7-char printable runs are expected), NOT the password. - XOR (single-byte and repeating-key, lengths 1–256 via IoC/Hamming/autocorrelation) ruled out conclusively.
- AES/ChaCha/Salsa/RC4/3DES with passwords derived directly (md5/sha256/PBKDF2/scrypt) over full rockyou = no hit — because it is a TrueCrypt container, not a raw cipher over the plaintext.
- Covert channels (timing, packet sizes, IP ID, TTL, pcapng comments) all clean; bimodal packet timing was just natural TCP write-loop batching.