Let's go - Autorské řešení úlohy
Solution #
Hned jednoduchou základní analýzou lze zjistit, že program by nejspíše napsán v jazyce Go. Tohle nám trochu znepříjemní život, jelikož dekompilace Go programů většinou nedopadá pěkně. Naneštěstí jiné možnosti moc nejsou… Na druhou stranu se alespoň nejedná o stripped program, takže máme alespoň nějaké informace o symbolech.
Po dekompilaci lze poměrně jednoduše najít, že se v programu vyskytuje pole
čísel označované jako main.expected a delka našeho vstupu se musí rovnat
délce toho pole (= 22). Dále zde máme funkci main.measure(), která bere jako
vstup kanál (= Go konstrukt pro předávání hodnot), index a char (a název
těchto dvou proměnných je poměrně vypovídající). Tato funkce je poměrně
jednoduchá - nejdříve zjistí čas, pak se uspí na dobu závislou na char,
zjistí čas znovu a výsledek je rozdíl naměřených časů vrácených přes vstupní
kanál (zabalených ve struktuře main.measureOutput).
Nyní zbývá už “jen” pořádně zanalyzovat main.main(). Když se pokusíme
odignorovat balast kolem a pokusíme se na to koukat z nadhledem (např. přes
nějaký “graph view” v dekompilačním programu), můžeme vidět 3 smyčky, jejichž
účel lze tipnout z nějakých význačných funkcí:
- První primárně volá “interní” Go funkci
runtime.newproc(), která nejspíše má za následek volánímain.measure()(nepřímo přesmain.main.func1(), na kterou je ve smyčce reference) - Ve druhé smyčce se opakovaně volá
runtime.chanrecv1(), můžeme předpokládat, že přijimá data vrácená/poslaná funkcímain.measure()a ukládá je očividně do nějakého bufferu - Poslední cyklus obsahuje nějaké porovnávání na základě kterého modifikuje
proměnnou, která pak po ukončení cyklu určuje, která zpráva o správnosti
vlajky/vstupu se zobrazí; Toto porovnávání porovnává pole
main.expecteds hodnotami pocházejícími (nejspíše) z předchozího cyklu
Když si celého toto shrneme, můžeme tušit, že náš vstup se postupně pošle do
funkce main.measure() a následně se porovná s main.expected. Není tedy nic
jednoduššího než zkusit vzít tyto hodnoty a zkusit z nich rekonstruovat správný
vstup - všechny hodnoty vydělíme konstantou z main.measure() kterou se násobí
vstupní proměnná char (= 4500000) a převedeme znaky… To se nám ale
nepodaří, protože 4500000 je moc veliké číslo a po dělení je vše 0. Znovu
kouknutím do main.measure() zjistíme, že se výsledné hodnoty dělí 1000, takže
chceme ve skutečnosti dělit dělitelem 4500. S tím už uspějeme a dosteneme
vlajku HCKR{n0_T1m3_T0_w4ST3}.
Exploit script #
Pro úplnost přikládám i velice primitivní Python skript který spočítá správnou vlajku z extrahovaných hodnot.
expected = [
0x4F376,
0x49AA9,
0x5270C,
0x5A314,
0x87221,
0x78E9D,
0x34C98,
0x68811,
0x5C50F,
0x35E28,
0x77E27,
0x381BA,
0x68606,
0x5C57A,
0x34E55,
0x6873C,
0x82EE5,
0x393C9,
0x5B3BA,
0x5C789,
0x38254,
0x897DF,
]
for x in expected:
print(chr(x // 4500), end="")