Zadatak s Hacknite platforme - Elemental Fighters
Uz zadatak je dan i izvorni kod.
Spajanjem na zadatak otvara se izbor borca, mogući izbor su 3 borca i 3 elementa za svakog borca.
Analizom koda u zadatku može se zaključiti da zapravo ne postoji kombinacija borca koja bi ni u najsretnijem slučaju uspjela pobijediti sve neprijatelje i doći do flaga, to znači da je potrebno nešto drugo učiniti jer ne postoji izbor pravog borca.
Analizom koda zanimljiva je varijabla s, koja je “scale”, odnosno određuje koliko će se oslabiti ili ojačati neprijatelje.
Ova varijabla se kasnije u kodu množi s atributima neprijatelja i što je manja, neprijatelji su slabiji, a što je veća, neprijatelji su jači.
Također zanimljiv dio koda je player default, koji definira player varijablu kao Milo(“ice”), ako je player None.
Ključan dio koda je funkcija kojom se inicijalizira player varijabla iz izbora igrača.
Ovdje se može vidjeti da se koristi funkcija eval, kojime se fighter unos poziva kao funkcija, a fighterType kao argument.
Može se vidjeti da su fighteri zapravo funkcije:
a fighterType vrijednosti su definirane u rječniku, te se parsiraju pri pozivanju fighter funkcije.
To znači da je unosom fighterType (“Odaberite element borca”) zapravo se definira argument funkcije, a unosom fighter (“Odaberite borca”) se definira funkcija kojoj će se argument proslijediti i koja će se izvršiti s proslijeđenim argumentom u eval funkciji.
Svaki unos prolazi kroz validate_input funkciju, koja je filter koji određuje koji su unosi dozvoljeni, a koji nisu.
Kako bi provjerili da možemo pozvati proizvoljnu funkciju i argument, ako nemamo unos koji bi funkcija validacije zabranila, možemo probati pozvati:
list("aaa")
Odaberite element borca (Ice, Fire, Acid): aaaa Odaberite borca (Zorn, Krev, Milo)list
Vidimo da je program vratio listu u kojoj se nalaze svi znakovi iz stringa koji smo poslali kao prvi argument, te oko njih znakovi za zagrade, koji su znakovi zagrada oko fighterType varijable (vidi sliku 4).
Slično se može pozvati len funkcija.
len("aaa")
Poziva se na ovakav način.
Odaberite element borca (Ice, Fire, Acid): aaa Odaberite borca (Zorn, Krev, Milo)len
Vidimo da program vraća 5, što je točno 3 znakova a koje smo poslali plus dva znaka zagrada.
Flag nije zapisan niti u jednoj varijabli, nego je samo hardkodiran u zadnjem printu programa, pa je potrebno osmisliti način kako pozvati odgovarajuću funkciju i argument, da bi se program uspješno izvršio do kraja i ispisao flag.
Dobar pristup bi bio mijenjanje varijable s na slici 2, koja je skala jačine protivnika. Ako bi tu varijablu mogli smanjiti ili pretvoriti u 0, protivnici bi bili puno slabiji, dok činjenica da je unos potrošen na mijenjanje varijable s umjesto na inicijalizaciju našeg borca ne bi bio problem, jer postoji “default” odabir borca, ako borac nije definiran, prikazan na slici 3.
Cijeli unos igrača se izvršava unutar funkcije eval, koja je namijenjena samo za evaluiranje expression-a, koji nakon izvršavanja vraća neku vrijednost, na primjer:
"2+2"
vraćena vrijednost je 4,
len("(aaa)")
vraćena vrijednost je 5, i bilo koja vrijednost koja će biti vraćena nakon izvršavanja ekspresije u eval funkciji će biti pridružena varijabli player (vidi sliku 4).
Potrebno je pronaći način kako mijenjati druge varijable u globalnom kontekstu programa, kao varijablu s, unutar eval funkcije, čiji se rezultat izvršavanja pridružuje varijabli player.
Osim expression u Pythonu, koji izvršava izraz i vraća izračunatu vrijednost izraza, postoji i statement, koji izvršava definiranu akciju, no ne vraća ništa.
Primjer statement-a
a = 0
, no pošto statement ne vraća ništa
b = (a = 0)
je neispravan Python kod, kao što bi bilo i da se eval koristio.
eval("a = 0")
(probajte u Python interpreteru).
Slično funkciji eval, postoji funkcija exec, koja je upravo namijenjena za izvršavanje statement (a može izvršavati i expression), te može definirati ili mijenjati varijable u globalnom kontekstu programa, ako se također pokreće u globalnom kontekstu programa.
exec("a=0")
Pregledom funkcije za validaciju unosa na slici 6 vidi se da funkcija exec nije zabranjena.
Znači da se funkcija exec može koristiti za mijenjanje vrijednosti scale varijable s, koja je globalna varijabla.
Iz ovoga se može zaključiti da potencijalni način rješavanja ovog programa bi bio izvršiti:
exec(s=0)
Čime bi unos izgledao ovako:
Odaberite element borca (Ice, Fire, Acid): s=0 Odaberite borca (Zorn, Krev, Milo)exec
Pokretanjem ovog unosa, prikazanog na slici 9, pojavljuje se greška.
Greška je invalid syntax, koju zapravo baca parser eval funkcije, koji pregledava kod prije nego će se izvršiti da odredi je li kod zapravo validan expression, iako je kod namijenjen da bude proslijeđen exec funkciji koja bi ovaj kod mogla normalno izvršiti.
Opisana ponašanja su prikazana na slici 10.
Na slici je prikazano da eval može izvršiti:
a==3
jer je to zapravo validan expression koji se evaluira u False.
Način kako se ovo može postići je korištenjem Pythonovog “walrus” operatora, koji je zapravo expression, vraća rezultat evaluacije expressiona, ali također evaluirani expression, nakon evaluacije postavlja kao vrijednost dane varijable.
Walrus operator se označava znakovima `:=`.
Na slici 11 može se vidjeti kako funkcionira walrus operator: evaluira expression “1 == 1”, što je `True`, i to prosljeđuje kao rezultat if izrazu, ali također tu istu vrijednost postavlja kao vrijednost varijable a.
Walrus operatorom se može napisati validan expression, koji nakon evaluacije također mijenja vrijednost odabrane varijable, ciljano varijable s.
Walrus operator se ne može sam izvršavati niti unutar eval niti unutar exec funkcije, zato što je namijenjen da bude izvršen unutar expressiona.
No ako se stave zagrade oko njega, izraz:
(a:=3)
postaje validan expression, što je prikazano na slici ispod.
Ovo je slučaj i u kodu zadatka, zato što se oko unosa dodaju zagrade, što je prikazano na slici ispod.
Kada su zagrade oko unosa, unutar zagrada mora biti validan expression, što `s=0` nije nego je statement, kao što se vidi na slici ispod.
To čini ovaj pristup neizvedivim:
Odaberite element borca (Ice, Fire, Acid): s=0 Odaberite borca (Zorn, Krev, Milo)milo
“alrus operator je validan expression i namijenjen je za izvršavanje unutar expressiona, što se može napraviti stavljanjem dodatnih zagrada. Ovako se walrus operator može izvršiti i unutar eval i unutar exec funkcije, gdje u oba slučaja uspješno mijenja vrijednost varijable (ali samo ako se exec i eval pokreću unutar globalnog konteksta).
I zato se također može izvršavati na ovaj način:
Što je, uz dodavanje zagrada, zapravo ekvivalentno ovom kodu:
Odnosno ovome, nakon zamjene stringova da se sve vidi u jednoj liniji:
I ovo je format inicijalizacije player varijable u zadatku, pa time i rješenja ovog zadatka; ove zagrade su iste kao i u liniji koda inicijalizacije player varijable eval funkcijom na slici 4.
Nakon toga se mogu raditi daljnje redukcije, zamjenom eval funkcije i njenih argumenata s ekvivalentnim kodom, pa primjenom istog postupka za exec funkciju s proslijeđenim argumentima. Nakon redukcija ta linija koda postaje ekvivalentna samo walrus operatoru unutar zagrada.
Sada je poznato rješenje i način kako rješava zadatak.
Rješenje se može unijeti ovako:
Odaberite element borca (Ice, Fire, Acid): s:=0 Odaberite borca (Zorn, Krev, Milo)exec
Izvršava se prethodno opisan postupak izvršavanja ovog koda, koji rezultira u postavljanju varijable s u 0, a varijable player u None, te se player onda inicijalizira u default, odnosno Milo(“ice”).
Eval / Exec i global scope
Budući da je ovo izvršeno u global scopeu, exec može promijeniti vrijednost scale varijable s, dok, da se izvršavao unutar funkcije, ne bi mogao promijeniti niti lokalnu varijablu unutar istog scopea kao funkcija.
Dok je pri izvršavanju u global scopeu, ovo moguće.
Ovo pravilo vrijedi i za eval i za exec, obje funkcije mogu mijenjati globalne varijable samo kada se izvršavaju u globalnom scopeu.