Portál AbcLinuxu, 20. května 2025 20:38
The Catch je soutěž v počítačích, programování a problem solvingu. Ve kvalifikačním online kole jsme byli třetí a proto nás pozvali na finále do Dejvic.
Dostal jsem feedback k mým předchozím blogpostům o CTF. Zohledním žádosti o detailnější postupy - alespoň u těch úloh, které jsem řešil, a tudíž o nich tyto podrobnosti vím.
BMP obrázek s 24 bity na pixel, ale obsah je černobílý. Nejdřív jsem ho otevřel v GIMPu a pomocí Colors→Levels hledal velmi světlé (e.g. hodnota 254 v bílé části)/barevné pixely. Následně jsem ho převedl do formátu PPM, což je velmi jednoduchý, volitelně plně textový a snadno parsovatelný formát, ve kterém obrázek vypadá třeba takto:
P3 # hlavička 2 2 # rozměry, zde 2x2 px 255 # maximální hodnota, zde 1 bajt na barvu 255 0 0 0 255 0 # samotná data pixelů ve formátu R G B 0 255 0 255 0 0 # zobrazí obrázek s červenou diagonálou a zelenou antidiagonálou
a v takto převedeném obrázku pixely s hodnotami jinými než 0 nebo 255 grepoval. Nic jsem nenašel, a tak jsem si otevřel článek Petra Tišnovského o BMP a podle něj si přečetl hlavičku. Opět nic podezřelého, ale pak jsem si všiml, soubor je nějaký podivně velký (obrázek měl jen 600x230 px) a v hexdumpu vidím očekávaná data, pak nic (samá "ffff" a hvězdičky pro vynechání) a pak něco dalšího. Aha, takže obrázek říká, že je malý, ale data pokračují dál (že BMP má ve skutečnosti data „pozpátku“ mi nedošlo).
Jedna možnost byla editovat hlavičku BMP, ale mnohem jednodušší mi přišlo přeplácnout ji hlavičkou PPM, s tím, že výšku jsem prostě střelil 10000 - z předchozího ladění jednoho bugu vím, že EoG PPM s blbě zadanou velikostí dokáže přečíst (GIMP ne), tak co bych to řešil.
Viděl jsem, že tam něco je, ale neseděla šířka, tudíž řádky byly zaliasované do sebe. Ze zkušenosti s příjmem pomaloběžné televize vím, že když synchronizace nesedí o méně než zhruba 5 pixelů na řádek, tak je jednoznačně poznat, co s tím je, zatímco u větších rozdílů už můžete vidět jenom maglajz a nevíte nic.
Začal jsem tedy s originální šířkou 600 px a nechal jsem zobrazovat obrázky s šířkou nastavenou na 610, 620 atd. (v cyklu přepsání čísla v PPM hlavičce a zobrazení) Po dojetí na 640 obrázek najednou vypadal trochu jako z prokládané televize (ale obsah stále nebyl okem jasně rozpoznatelný), věděl jsem tedy, že už nemá cenu zkoušet po 10, ale skutečná šířka bude „dobrý zlomek“ k 640. 640 je 2^7*5 (věděli jste že v coreutils je program factor
?), to nám moc nepomohlo, ale otipoval jsem nějaká ajťácká čísla která se používají jako rozlišení (960, 1024, 1280, 1920) a trefil to. Teda - výsledek byl zrcadlově převrácený a vzhůru nohama, což se ale samozřejmě triviálně vyřeší v grafickém editoru. Až po soutěži jsem si uvědomil, že to vzniklo samo od sebe tím, že BMP má souřadnice „pozpátku“.
Z toho popisu to zní dlouze, ale ve skutečnosti tohle byla nejkratší úloha.
TL;DR Obrázek, který v hlavičce tvrdí, že má 600x230 px, ale ve skutečnosti datový stream pokračuje a v neviditelné části je ukryt flag.
Zdroják v nějakém dialektu jazyka Karel, po spuštění z cihel vyskládá flag. Tohle řešil mladší kamarád a bohužel mu proto nedošlo, že to je Karel (na druhou stranu po soutěži jsem zkoušel první vygooglený interpret Karla a nefungovalo to -- ale ostatní soutěžící říkali, že jim nějaký jiný fungoval), a tak to přepsal do Pythonu.
PDF plné vzorečků jako je tento (tohle konkrétně vykreslí 85 % kolečka - které ve výsledné grafice bude tvořit písmenko c)
4(x − 0.5)2 + (y − 1)2 = 1 {x ≤ 0.85}
alternativně byl též dostupný jeho TeXový zdroják.
Cíl nejspíš tedy je vykreslit tyto funkce. Jako první jsem nažhavil KmPlot, což je klikátko, které toto umí - plán byl tam pár rovnic vložit ručně, a následně editovat XML, ve kterém si to funkce ukládá, a automaticky do toho přidat všechny ty ostatní. Bohužel mám na počítači dlouhodobě nějak rozbité Qt a KmPlot tak moc nešlapal. Jasná další volba pro mě je Python s Matplotlibem. Napsal jsem tedy šílený skript, který každý řádek LaTeXu, jako například již zmíněné
4\left(x-0.5\right)^2+\left(y-1\right)^2=1\left\{x\le0.85\right\}
převede na
xrange = np.arange(LL, 0.85, delta) yrange = np.arange(LL, UL, delta) x, y = np.meshgrid(xrange,yrange) F=4*(x-0.5)**2+(y-1)**2 G=1 matplotlib.pyplot.contour(x, y, (F - G), [0])
(bylo to 50 řádků poctivého sedu a shellu.) Následně se na začátek přilepí hlavička
import matplotlib.pyplot import numpy as np delta = 0.025 LL = -10 UL = 35 # odhadnuto empiricky z toho že v tom PDF se vyskytují čísla od 0 do 30)
a celé se to uloží do souboru a spustí. Na první pokus jsem blbě zpracovával absolutní hodnotu, některá písmenka proto nebyla úplně korektně nakreslená a flag jsme odevzdali blbě. Ale to se hned opravilo.
Obdoba úlohy The Transmission z předchozího kola, akorát tentokrát to bylo DHCPv6 a místo MAC adres se tak lišily DUID.
PoDrátě 5, úloha 11, akorát na počítači běží implementace brainfucku (reportedly, sám jsem to neřešil).
Dostali jsme ESP8266, jakoby (fake) ořezávátko na tužky, které reportuje tvrdost vložené tužky přes wifi. Po spuštění na sériák vypíše, že se snaží připojit k wifi síti, a následně občas vypíše, že odesílá změřenou tvrdost na http://192.168.1.1/spy.
Atalax z toho vydumpoval firmware (normálně esptoolem), pustil strings a všiml si, že je tam zdroják v MicroPythonu, který cosi odněkud čte a XORuje to s 0x11. VyXORoval proto celý dumpnutý obraz 0x11 a spustil strings znovu; tentokrát jsme se dozvěděli jiný zdroják, a to připojení k wifi (SSID a heslo) a následnou smyčku, která čísla odesílala.
Síť s tímto SSID jsme v okolí nikde neviděli (a ESP nedělalo APčko), tak mě napadlo, že bychom si ji mohli udělat. Tak jsem spustil hostapd, DHCP server přidělující z toho rozsahu 192.168.1, web server aby to postování přes HTTP fungovalo a Wireshark. Kvůli různým technickým problémům toto chvíli trvalo, a než jsme je vyřešili, zjistili jsme, že se na naše AP připojilo několik MAC adres.
Pak jsme to chvíli řešili (zařízení opravdu jenom posílalo data a podle toho zdrojáku to nevypadalo, že bychom třeba vhodnou odpovědí mohli zařídit remote code execution - ale firmware, který nám zařízení samo poskytlo, může být samozřejmě falešný). Postupně jsme si při tom uvědomovali, že asi unášíme spojení celé místnosti (na začátku jsem si myslel, že každý tým dostal zařízení s naprogramovaným vlastním SSID), a že bychom třeba mohli zapnout filtr podle MAC adresy. Do toho organizátoři vyhlásili, že zjistili, že někdo vysílá vlastní soutěžní síť, a účastníci se pak „připojí do ní a ne do té, ve které běží server s flagem“. Okamžitě jsem svoje AP killnul a z toho tedy vyplynulo, že asi nebylo cílem tu síť vytvořit, nýbrž připojit se do existující.
Pak jsem se do této sítě pokoušel připojit - zjevně tedy má skryté SSID (ve scanu hned na začátku jsme ji neviděli), ale pořád se nic neděje, pak mě napadlo ukrást si MAC adresu toho ESP (zjevně nevím, jak funguje MAC filtr, myslel jsem, že mě AP odmítne až v nějaké pozdější fázi, ne, že se bude wpa_supplicant tvářit, jako kdyby síť neexistovala - i když ono to dává smysl). V síti se pak stačilo připojit na zmíněný web server, kde byla vlajka napsaná. Mimochodem PSK bylo 31415926536
, což je 14 GPU-hodin pokud by člověk tipl, že to je číselné, tedy nejspíš mírně za hranicí cracknutelnosti hrubou silou během soutěže (SSID bylo elbonet
a pro něj rainbow tabulky nejspíš mít nebudete (ano, do hashování klíče pro wifi vstupuje jako salt ssid)).
Bach na piáno (syntetizovaný) s tím, že některé noty „nesedí“. Nejdřív jsem to důkladně prohlížel Sonic Visualiserem. Kamarád našel co nejpodobnější originál a já jsem pak strávil přes hodinu signal processingem:
numpy.fromstring(f.read(), dtype='i2').astype("f4")
a dostat pole floatů.f.write(pole.tostring())
), naimportoval do Audacity (File → Import → Raw Data) a ověřil, že to sedí.numpy.hamming()
a udělat FFT/spektrum (numpy.fft.fft
a z toho absolutní hodnotu)Řešení bylo najít Twitter fiktivní osoby o které tato hra byla, zjistit, že se na něm chlubí touto skladbou vytvořenou stejným syntetizérem avšak se správnými notami, zvuky od sebe odečíst (tím, že je to stejný syntetizér a stejné nastavení, vyjde rozdíl „hezky“) a interpretovat segmenty nulového a nenulového rozdílu jako invertovanou morseovku (nulový = symbol; Kubáč psal že takhle to původně fungovalo, aby se zjistilo, že došlo k přerušení vedení). Meh.
Obdoba úkolu Web z minulého zápisku, akorát tentokrát je cookie za-base64-ovaná a ve tvaru „admin@nopassword“ a bylo potřeba ji změnit na „admin@admin“. Stránka navíc obsahovala formuláře a další prvky, takže to svádělo strašně řešit ne tohle, ale SQL Injection a podobně. Nakonec jsme na takové trivialitě strávili snad dvě člověkohodiny.
110 MB logů ze syslogu, snortu a webserveru. Dalo se vygooglit, že logy jsou generované pomocí generátorů a většinu věcí tak vyházet. Následně tam byly vidět požadavky různých uživatelů a jeden z nich už ani nevím proč vyčníval. A tento uživatel měl UID (nějaký obecný string, ne číselné unixové) něconěcoCT18něconěco (ostatní měli třeba ...CL18..., ...CV18... atd.). Jenže bez pomlček! Všechny flagy dosud byly v pomlčkovém formátu a tak jsem nevěřil, že by to mohlo být ono. Ale kamarád to nakonec vyzkoušel a ono fakt jo.
Vyzkoušel jsem si tak CTF, krásně jsem si s kamarády zahackoval a i se něco naučil, ale už se mi to začíná trochu zajídat, takže si dám nějakou chvíli pauzu. On-line kolo (tj. to minulé) TheCatch mi přišlo technicky vymakanější, byla to škála zajímavých programovacích a technických úloh, zatímco v dnešním kole bylo několik úkolů (některé jsem v tomto zápisku ani nepopsal), u kterých spíš než o techniku provedení šlo o okamžitý nápad, který může ale nemusí přijít a pak je zkoušení slepých cestiček akorát frustrující.
Nakonec jsme vyřešili všechny úlohy kromě jedné další notové (kterou nevyřešil nikdo, mělo se prý sčítat, jak dlouho noty ve skladbě celkem hrají a délka je pak hodnota znaku v ASCII tabulce; výsledek pak přečteme v pořadí podle výšky noty) a soutěž jsme vyhráli.
To teda znamená, že nás chtějí nominovat na světové finále, které je bohužel v Japonsku a tam kvůli tomu fakt nepojedu (pro úplnost nutno dodat, že nám víza, letenku a ubytování zaplatili). Proto jsem za sebe navrhl náhradníka - někoho z 2. místa. O tom koho z nich rozhodl osud - v hospodě na after jsem kecal se dvěma z nich (zbytek odešel || seděl jinde a tím vypadli) a jeden z nich byl podobný člověku co předtím nevěděl jak funguje WPA2 handshake a strašně do mě ryl že jsem mu svým rogue APčkem ukradl heslo k síti (se svým face recognition netuším, jestli to byli stejní lidé). To je metoda, co.
Něco nevědět není problém, obzvláště u takto specializovaných a pokročilých témat. Tyhle dva odkazy by měly věci osvětlit.
Tiskni
Sdílej:
To teda znamená, že nás chtějí nominovat na světové finále, které je bohužel v Japonsku a tam kvůli tomu fakt nepojedu (pro úplnost nutno dodat, že nám víza, letenku a ubytování zaplatili).A jakou lepší příležitost bys chtěl?
U Koncertu jsem skončil s vyfiltrovanými tóny navíc a zatím to nedávám ani s nápovědou, že je to morseovka.Screenshot v příloze. Kudos že jsi toho tolik dal sám.
věděli jste že v coreutils je program factor?Ne. Noice. Díky
~> time factor "$( echo "`openssl prime -generate -bits 50` * `openssl prime -generate -bits 50`" | bc)" 809830085898266752175990132009: 897472964658073 902344825737233 real 0m1,158sKdyž už jsme u toho, málo se zná ještě sponge z moreutils.
Když už jsme u toho, málo se zná ještě sponge z moreutils.To vypadá pěkně, škoda, že až to budu potřebovat, tak si nevzpomenu, jak se to jmenuje
To teda znamená, že nás chtějí nominovat na světové finále, které je bohužel v Japonsku a tam kvůli tomu fakt nepojedu (pro úplnost nutno dodat, že nám víza, letenku a ubytování zaplatili). Proto jsem za sebe navrhl náhradníkaHrachu, co že si udělal? Ty nechceš abych se s tebou bavil, že?
Jinak ještě k té analýze not, ffmpeg má na to docela dobrý filtr.
ISSN 1214-1267, (c) 1999-2007 Stickfish s.r.o.