Management Wants a Word | TryHackMe Writeup po polsku

Polski writeup TryHackMe Management Wants a Word: analiza KAPE, DPAPI, hasła Chrome i kontenera VeraCrypt. Końcowa flaga pozostaje ukryta.

· 12 min czytania
#thm#forensics#dpapi#veracrypt#chrome#dfir

To jest mój polski writeup do pokoju TryHackMe Management Wants a Word z serii Hacker Holidays. Zamiast typowego exploita dostajemy komplet artefaktów Windows zebranych przez KAPE i odtwarzamy cały łańcuch forensics: od lokalnych sekretów użytkownika, przez DPAPI i Chrome, aż po zaszyfrowany wolumen VeraCrypt.

Insider threat rzadko wygląda jak scena z filmu. Zwykle ktoś ma legalny dostęp, chowa dane w niczym niewyróżniającym się pliku i liczy, że nikt nie zejdzie warstwę niżej niż "dziwny plik w Dokumentach". Tutaj użytkowniczka vera wyniosła dane finansowe do kontenera udającego zwykły plik backup, a hasło zapisała w Chrome.

> Uwaga na spoilery: pokazuję pełną ścieżkę rozwiązania i odzyskane hasła, ale końcową flagę pozostawiam jako THM{REDACTED}.

Szybka mapa rozwiązania

W tym walkthrough pojawiają się:

  • triage KAPE i historia Chrome,
  • hive'y SAM, SYSTEM i SECURITY,
  • sekret LSA DefaultPassword,
  • DPAPI master key użytkownika,
  • klucz AES-256-GCM z Chrome Local State,
  • zapisane hasło z bazy Login Data,
  • kontener VeraCrypt bez magic bytes,
  • faktura PDF zawierająca flagę.

Cały łańcuch zależności wygląda tak:

LSA DefaultPassword (minivera)
  -> DPAPI Master Key
     -> Chrome Local State (klucz AES)
        -> Login Data (hasło do VeraCrypt)
           -> backup.vc -> faktura PDF jako obraz -> THM{REDACTED}

Każdy krok zależy od poprzedniego. Nie da się przeskoczyć od razu do końca, i o to właśnie chodzi w tym labie.

Co dostajemy na start

Materiał dowodowy to klasyczna kolekcja KAPE, czyli wybrane kawałki rejestru i profilu użytkownika, bez pełnego obrazu dysku. Po odsianiu szumu zostaje kilka naprawdę istotnych rzeczy:

KAPE/C/
  Windows/System32/config/          # SAM, SYSTEM, SECURITY, SOFTWARE
  Users/vera/
    Documents/backup                # ~100 MiB, bez rozpoznawalnego formatu
    AppData/Roaming/Microsoft/Protect/<SID>/   # DPAPI master key
    AppData/Local/Google/Chrome For Testing/User Data/
      Local State                   # zaszyfrowany klucz Chrome (DPAPI)
      Default/History
      Default/Login Data

To wystarczy. Mamy sekrety LSA, klucz DPAPI użytkownika, profil Chrome i podejrzany kontener. Reszta to już praca krok po kroku.

Ustalamy intencję: historia przeglądarki

Zanim zacznę cokolwiek deszyfrować, chcę wiedzieć, czego szukam. Historia Chrome siedzi w bazie SQLite, a znaczniki czasu są w formacie FILETIME (mikrosekundy od 1 stycznia 1601). Konwersja do czytelnej daty wygląda tak:

cp ".../Default/History" /tmp/hist.db
sqlite3 /tmp/hist.db \
"SELECT datetime(last_visit_time/1000000-11644473600,'unixepoch'), url
 FROM urls ORDER BY last_visit_time;"

Wynik układa się w opowieść: logowanie do wewnętrznego portalu bytelotus.thm, a potem wyszukiwania w stylu "how to exfiltrate data" i "tryhackme". Sam w sobie to jeszcze nie dowód, ale świetnie porządkuje priorytety. Wiadomo, gdzie szukać dalej.

Podejrzany plik `backup`

W Dokumentach leży plik o rozmiarze dokładnie 100 MiB, bez rozszerzenia i bez rozpoznawalnego formatu:

file Users/vera/Documents/backup   # data
stat -c %s .../backup              # 104857600 (dokładnie 100 MiB)
xxd .../backup | head              # sama entropia, zero nagłówka

Trzy rzeczy krzyczą "kontener VeraCrypt lub TrueCrypt":

  1. Brak magic bytes. Nagłówek jest zaszyfrowany celowo, żeby nie dało się go rozpoznać po sygnaturze.
  2. Wysoka i równa entropia. Zawartość wygląda jak czysty szum.
  3. Idealnie okrągły rozmiar. Ktoś ręcznie zdefiniował kontener na 100 MiB.

Problem w tym, że bez hasła to ślepa uliczka. A hasło, jak się okazuje, siedzi w Chrome.

Chrome Login Data i blob v10

Zapisane hasła Chrome trzyma w bazie Login Data. Wyciągam interesujący rekord:

cp ".../Default/Login Data" /tmp/login.db
sqlite3 /tmp/login.db \
"SELECT origin_url, username_value, hex(password_value) FROM logins;"

Dostaję wpis:

PoleWartość
URLhttp://bytelotus.thm:8080/
UżytkownikVeraSecretVault
Hasłohex zaczynający się od 763130, czyli ASCII v10

Od pewnej wersji Chromium prefiks v10 oznacza AES-256-GCM w układzie v10 || IV(12 bajtów) || szyfrogram || tag(16 bajtów). Klucz do tego GCM nie leży jednak w bazie. Jest owinięty przez DPAPI i schowany w pliku Local State. Trzeba go najpierw odzyskać.

Deszyfracja warstwa po warstwie

Warstwa 1: sekret LSA

Windowsowy autologon potrafi trzymać hasło użytkownika w LSA Secrets. Wyciągam je offline z hive'ów:

impacket-secretsdump -sam SAM -system SYSTEM -security SECURITY LOCAL

W wyniku pojawia się DefaultPassword w postaci jawnej: `minivera`. To jest punkt zaczepienia do całego DPAPI użytkownika vera. Dla pewności można jeszcze zweryfikować, że MD4(UTF-16LE("minivera")) zgadza się z hashem NT z SAM. W labie się zgadza.

Warstwa 2: DPAPI Master Key

Mam SID użytkownika i plik master key z folderu Protect:

S-1-5-21-2529683458-431225740-1723070931-1000
.../Protect/<SID>/c90719ef-5b98-474e-b934-136d606a702a

DPAPI wyprowadza klucz operacyjny z hasła użytkownika i jego SID, więc mając oba, odzyskuję master key:

impacket-dpapi masterkey \
  -file "$MK" -sid "$SID" -password minivera

Na wyjściu odszyfrowany master key zaczynający się od 0x5e5715ec9b6df5a86e....

Warstwa 3: klucz AES Chrome z Local State

Chrome trzyma swój klucz szyfrujący w pliku Local State, zakodowany w base64 i owinięty przez DPAPI. Pierwsze 5 bajtów to marker DPAPI, który trzeba obciąć:

import json, base64
ls = json.load(open(".../Local State"))
raw = base64.b64decode(ls["os_crypt"]["encrypted_key"])
open("/tmp/chrome_key_blob.bin", "wb").write(raw[5:])

Teraz odwijam ten blob odzyskanym wcześniej master keyem DPAPI:

impacket-dpapi unprotect -file /tmp/chrome_key_blob.bin -key 0x5e57...

W efekcie dostaję 32-bajtowy klucz AES-256 Chrome:

206a39a0971327ea9487e4aea9844f5d3670162456982276939a712646da0b02

Warstwa 4: hasło do sejfu

Mam klucz, mam blob z bazy. Rozbieram strukturę GCM i deszyfruję:

from Crypto.Cipher import AES
key = bytes.fromhex("206a39a0...")
blob = bytes.fromhex("763130...")  # wartość z Login Data
iv, ct, tag = blob[3:15], blob[15:-16], blob[-16:]
print(AES.new(key, AES.MODE_GCM, nonce=iv).decrypt_and_verify(ct, tag))

I wyskakuje hasło w postaci jawnej:

Wh4t1sV3raD0inG0nTh1sH0st

To jest passphrase do kontenera backup. Cały łańcuch domknięty.

Otwieramy VeraCrypt i szukamy flagi

Na Linuxie najprościej użyć wbudowanego wsparcia dla TrueCrypt/VeraCrypt w cryptsetup:

echo -n 'Wh4t1sV3raD0inG0nTh1sH0st' | \
  cryptsetup --type tcrypt --veracrypt open /tmp/backup.vc vaultvol
mount -o ro /dev/mapper/vaultvol /mnt/vault

Ja robiłem to na macOS bez FUSE i cryptsetup, więc poszedłem drogą w przestrzeni użytkownika: PBKDF2-HMAC-SHA512 z 500 000 iteracji na soli z nagłówka, potem AES-XTS na nagłówku (po deszyfracji pojawia się magiczne VERA), a następnie AES-XTS na całym payloadzie od offsetu 0x20000. Wychodzi z tego czysty obraz FAT32, który dalej rozpakowałem zwykłym 7z.

W środku dwa pliki:

secret_financial_documents/
  transactions_q3.csv
  important_invoice_byte_lotus.pdf

CSV wygląda niewinnie, ale jeden wiersz to wyraźna wskazówka:

2026-07-12, TXN-10531, Internal Adjustment, Image asset correction, 0.00, Archived

"Image asset correction" to nie przypadek. pdftotext na fakturze nie zwraca nic sensownego, bo treść jest tam wrenderowana jako obraz, a nie tekst. Trzeba więc wyciągnąć sam obrazek:

pdfimages -all important_invoice_byte_lotus.pdf /tmp/inv
# alternatywnie: mutool draw -r 200 -o /tmp/inv.png ...

Na wyrenderowanej fakturze Byte Lotus Resorts, w opisie pozycji numer 1, siedzi flaga:

THM{REDACTED}

Nie zdradzam jej tutaj w całości, bo to psułoby zabawę każdemu, kto chce przejść ten lab samodzielnie. Sam łańcuch dojścia jest ważniejszy niż konkretny ciąg znaków.

Co z tego wynika dla obrońcy

Kilka wniosków, które warto zabrać ze sobą do codziennej pracy DFIR:

  1. Autologon plus LSA Secrets to gotowe hasło użytkownika w postaci jawnej, dostępne już w samym triage'u. Hive'y SECURITY i SYSTEM traktuj jako materiał wysokiej wartości od pierwszej minuty.
  2. DPAPI nie jest magiczną barierą. Jeśli masz hasło użytkownika (albo klucze zapasowe czy kontekst domenowy), da się je odwrócić offline. A Chrome na Windowsie opiera swoje os_crypt właśnie na DPAPI.
  3. Hasła zapisane w przeglądarce to często klucze do kolejnych warstw: VPN, sejfów, kontenerów, chmury. Jedno wygodne "zapamiętaj hasło" potrafi rozłożyć całą obronę.
  4. Kontener bez sygnatury rozpoznajesz po kontekście, a nie po magic bytes. Rozmiar, entropia, ścieżka, historia przeglądarki i baza haseł mówią więcej niż nagłówek pliku.
  5. Dane schowane w obrazku wymkną się zwykłemu grep. Faktura jako obraz, screenshot czy skan przejdą bez echa, dopóki nie sięgniesz po OCR albo ekstrakcję grafiki. Wskazówka w CSV była tu położona celowo.

Dla red teamu morał jest lustrzany: nawet porządne szyfrowanie nie obroni danych, jeśli sekret do niego leży w autologonie i w przeglądarce.

Ściąga narzędziowa

KrokNarzędzie
Sekrety LSA i SAMimpacket-secretsdump
DPAPI master key i unprotectimpacket-dpapi
Bazy Chromesqlite3
Deszyfracja hasła GCMpycryptodome
VeraCryptcryptsetup (tryb tcrypt/veracrypt) lub AES-XTS w userspace
Wyciąganie plików z FAT7z, mount, sleuthkit
PDF do obrazupdfimages, mutool
OCR w razie potrzebytesseract

Podsumowanie

To śledztwo nie zaczyna się od łamania VeraCrypt. Zaczyna się od tożsamości i sekretów systemowych. Autologon prowadzi do DPAPI, DPAPI do klucza Chrome, klucz do hasła sejfu, hasło do kontenera, a kontener do faktury zapisanej jako obraz. Flaga siedziała na samym końcu, ale bez każdego wcześniejszego kroku była nieosiągalna.

Jeśli budujesz własne laby, ten scenariusz świetnie pokazuje jedną rzecz: szyfrowanie endpointu i sekrety przeglądarki to jeden ekosystem. A dobrze zebrany triage KAPE potrafi go złożyć w całość nawet bez pełnego obrazu dysku.


sharelinkedinx / twitter