rev2_ghidra
Differences
This shows you the differences between two versions of the page.
| Next revision | Previous revision | ||
| rev2_ghidra [2025/10/30 15:16] – created mbunic | rev2_ghidra [2025/12/01 11:40] (current) – external edit 127.0.0.1 | ||
|---|---|---|---|
| Line 1: | Line 1: | ||
| ==== rev2 ==== | ==== rev2 ==== | ||
| - | Uz zadatak je dana samo izvršna datoteka " | + | Uz zadatak je dana samo izvršna datoteka " |
| + | Pokretanjem programa, traži se lozinka i nakon unosa, ispisuje se poruka o pogrešnoj lozinki. | ||
| - | Pokretanjem programa, program nas pita za password i ispisuje krivi password, pri unosu netočnog passworda. | + | {{ rev2_ghidra: |
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | Slika 1. pokretanje programa | + | |
| - | + | ||
| - | {{ : | + | |
| Može se pokrenuti naredba file kako bi se saznale osnovne informacije o programu. | Može se pokrenuti naredba file kako bi se saznale osnovne informacije o programu. | ||
| Line 20: | Line 13: | ||
| </ | </ | ||
| - | slika 2. rezultat file naredbe | + | {{ rev2_ghidra: |
| - | {{ :slika2.png? | + | Program je statically linked, zato se može samostalno pokretati. Također je stripped, zato imena varijabli i funkcija neće biti sačuvane, što će malo otežati reverzno inženjerstvo nad programom. |
| - | Program je statically liked, znači da se može samostalno pokretati. Također je stripped, što znači | + | Nakon toga se može pokrenuti naredba strings |
| - | |||
| - | Nakon toga se može pokrenuti naredba, strings da se vidi ima li zanimljivih stringova u datoteci. | ||
| < | < | ||
| strings rev2 | strings rev2 | ||
| </ | </ | ||
| - | No ni jedan vraćeni rezultat ne izgleda kao da previše otkriva. Može se također odmah pretražiti | + | No niti jedan vraćeni rezultat ne izgleda kao da previše otkriva. Može se također odmah pretražiti |
| < | < | ||
| Line 38: | Line 29: | ||
| </ | </ | ||
| - | zastavica " | + | zastavica " |
| - | + | ||
| - | slika 3. rezultat strings naredbe | + | |
| - | + | ||
| - | {{ : | + | |
| - | Ovo nam također ništa | + | {{ rev2_ghidra: |
| + | Ovo nam također ništa korisno ne otkriva o programu, osim što prikazuje tekst koji se ispisuje pri unosu passworda i ispisu pri krivom unosu passworda. | ||
| Nakon toga se može probati naredba " | Nakon toga se može probati naredba " | ||
| Line 53: | Line 41: | ||
| </ | </ | ||
| + | {{ rev2_ghidra: | ||
| - | Slika 4. - rezultat strace naredbe | + | Također se na prvi pogled ne mogu pronaći dodatne korisne informacije o programu u ispisu. |
| - | {{ :slika4.png? | + | Sljedeći korak je korištenje besplatnog Ghidra alata za reverzno inženjerstvo nad programom (ako niste upoznati s alatom, pogledajte materijale o ovom alatu na poveznici - https://www.cert.hr/wp-content/ |
| - | Također se ne prvi pogled ne mogu pronaći dodatne korisne informacije o programu u ispisu. | + | {{ rev2_ghidra: |
| - | + | ||
| - | Slijedeći korak je korištenje besplatnog Ghidra softwarea za reverzno inženjerstvo nad programom (ako niste upoznati s alatom, pogledajte materijale o ovom alatu također dostupne na wiki stranicama na linku- | + | |
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | Slika 5. Ghidra alat | + | |
| - | + | ||
| - | {{ : | + | |
| Dvoklikom na rev2 datoteku u sučelju, otvara se sučelje za pregled dekompajliranog koda programa. | Dvoklikom na rev2 datoteku u sučelju, otvara se sučelje za pregled dekompajliranog koda programa. | ||
| - | + | {{ rev2_ghidra: | |
| - | + | ||
| - | + | ||
| - | + | ||
| - | Slika 5. sučelje za analizu koda i bitni dijelovi | + | |
| - | + | ||
| - | {{ : | + | |
| Na slici vidimo entry point, dio koda koji se prvi izvršava pri pokretanju programa, u Decompile sučelju vidi se da entry point funkcija samo poziva funkciju " | Na slici vidimo entry point, dio koda koji se prvi izvršava pri pokretanju programa, u Decompile sučelju vidi se da entry point funkcija samo poziva funkciju " | ||
| - | + | {{ rev2_ghidra: | |
| - | + | ||
| - | + | ||
| - | + | ||
| - | Slika 6. pregled funkcije | + | |
| - | + | ||
| - | {{ : | + | |
| Pregledom dekompajliranog koda ove funkcije, može se zaključiti da je ovo main funkcija, vidi se dio koda koji ispisuje "Enter password: ", i dio koda odmah ispod, koji bi mogao biti fgets funkcija za čitanje korisničkog unosa, koji zatim sprema na varijablu pbVar5. | Pregledom dekompajliranog koda ove funkcije, može se zaključiti da je ovo main funkcija, vidi se dio koda koji ispisuje "Enter password: ", i dio koda odmah ispod, koji bi mogao biti fgets funkcija za čitanje korisničkog unosa, koji zatim sprema na varijablu pbVar5. | ||
| Line 97: | Line 65: | ||
| </ | </ | ||
| - | Na početku main funkcije, | + | Na početku main funkcije, |
| < | < | ||
| Line 109: | Line 77: | ||
| </ | </ | ||
| - | također, vidi se da je drugi argument poziva funkcije | + | također, vidi se da je drugi argument poziva funkcije |
| - | + | ||
| Treći argument je pointer | Treći argument je pointer | ||
| - | + | {{ rev2_ghidra: | |
| - | + | ||
| - | Slika 7. vrijednost na koju pointer | + | |
| - | + | ||
| - | {{ : | + | |
| vrijednost je | vrijednost je | ||
| Line 128: | Line 90: | ||
| Prva 4 bajta FBAD su "magic number" | Prva 4 bajta FBAD su "magic number" | ||
| - | |||
| Sada znamo da bi ova linija | Sada znamo da bi ova linija | ||
| Line 136: | Line 97: | ||
| </ | </ | ||
| - | odgovarala | + | odgovarala |
| < | < | ||
| Line 142: | Line 103: | ||
| </ | </ | ||
| + | Može se označiti varijabla local_118, pritisnuti gumb L i preimenovati u input_buffer. | ||
| + | {{ rev2_ghidra: | ||
| + | Također se varijabla pbVar5 može preimenovati u user_input, PTR_DAT_004aa6d8 u stdin i | ||
| + | FUN_00404ee0 u fgets. Varijablu lVar2 nećemo preimenovati, | ||
| - | Sad se može označiti | + | Kako bi se olakšalo praćenje važnih varijabli, varijable koje su bitne u izvođenju programa mogu se označiti i pritiskom desnog klika i opcije highlight, mogu se postaviti da budu istaknuto označenu ostatku programa. |
| - | + | fgets funkcija pri uspješnom izvršavanju vraća pointer na string buffer, a ako je vraćena vrijednost NULL, dogodila se greška, što odgovara ovom dijelu | |
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | Slika 8. Preimenovanje varijable u Ghidri | + | |
| - | + | ||
| - | {{ : | + | |
| - | + | ||
| - | Također se varijable pbVar5 može preimenovati u user_input, PTR_DAT_004aa6d8 u stdin i | + | |
| - | FUN_00404ee0 u fgets. Varijablu lVar2 nećemo preimenovati, | + | |
| - | + | ||
| - | Kako bi se olakšalo praćenje važnih varijabli, varijable koje su bitne u izvođenju programa mogu se označiti i pritiskom desnog klika i opcije highlight, mogu se postaviti da budu bolje naznačenje u ostatku programa. | + | |
| - | + | ||
| - | fgets funkcija pri uspješnom izvršavanju, vraća pointer na string buffer, a ako je vraćena vrijednost NULL, dogodila se greška, što odgovara ovom djelu koda | + | |
| < | < | ||
| Line 180: | Line 122: | ||
| </ | </ | ||
| - | Ako je fgets vratio NULL, varijabla uVar3 se postavlja u 1 i skače se na kraj main funkcije, te se varijabla uVar3 vrača kao return vrijednost main funkcije, | + | Ako je fgets vratio NULL, varijabla uVar3 se postavlja u 1 i skače se na kraj main funkcije, te se varijabla uVar3 vraća kao return vrijednost main funkcije, |
| - | + | ||
| - | + | ||
| - | Ako se fgets uspješno izvršio, izvršava se else block, koji počinje ovim kodom | + | |
| + | Ako se fgets uspješno izvršio, izvršava se else blok, koji počinje ovim kodom | ||
| < | < | ||
| Line 197: | Line 136: | ||
| dvoklikom na adresu DAT_0047f021 na kojoj je vrijednost drugog argumenta proslijeđenog ovoj funkciji, može se vidjeti vrijednost na toj adresi | dvoklikom na adresu DAT_0047f021 na kojoj je vrijednost drugog argumenta proslijeđenog ovoj funkciji, može se vidjeti vrijednost na toj adresi | ||
| - | + | {{ rev2_ghidra: | |
| - | + | ||
| - | Slika 9. vrijednost na adresi | + | |
| - | + | ||
| - | {{ : | + | |
| Vrijednost je " | Vrijednost je " | ||
| - | + | Može se zaključiti da funkcija thunk_FUN_00412860 vjerojatno vraća poziciju | |
| - | Može se zaključiti da funkcija thunk_FUN_00412860 vjerojatno vraća poziciju na kojoj se u korisničkom unosu nalazi CR LF, odnosno kraj unosa. | + | |
| < | < | ||
| Line 216: | Line 150: | ||
| U tom slučaju bi varijabla | U tom slučaju bi varijabla | ||
| - | Nakon toga, u if statementu, vrijednost varijable | + | Nakon toga, u if statementu, vrijednost varijable lVar2 se uspoređuje s 0x15, odnosno 21 u dekadskom zapisu, što točno odgovara duljini FLAG formata |
| < | < | ||
| Line 222: | Line 156: | ||
| </ | </ | ||
| + | Ako je duljina korisničkog unosa jednaka 21, ulazi se u do - while blok, unutar kojega se nalazi switch sa 7 caseva. Ako uvjet nije zadovoljen, skače se na funkciju koja ispisuje "Wrong password!" | ||
| - | Ako je duljina korisničkog jednaka 21, ulazi se u do - while block, unutar kojega se nalazi switch sa 7 caseva, ako nije skače se na funkciju koja ispisuje "wrong password" | + | Pregledom switcha, na kraju switcha |
| - | Pregledom switcha, na kraju switcha se odmah može vidjeti | + | {{ rev2_ghidra: |
| - | + | Na početku do bloka, se nalazi ova linija | |
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | Slika 10. Switch case 0 | + | |
| - | + | ||
| - | {{ : | + | |
| - | + | ||
| - | Na početku do blocka, se nalazi ova linija | + | |
| < | < | ||
| Line 243: | Line 168: | ||
| </ | </ | ||
| - | koja postavlja varijablu bVar1 na dereferenciranu vrijednost user_input pointera, koji je pointer na string buffer. | + | koja postavlja varijablu bVar1 na dereferenciranu vrijednost user_input pointera, koji je pointer na string buffer. |
| - | može preimenovati u user_input_char. | + | |
| - | + | ||
| - | U switchu postoji 7 caseva, a case se određuje prema vrijednosti puVar2. | + | |
| - | + | ||
| - | + | ||
| - | Slika 10. - switch value | + | |
| - | + | ||
| - | {{ : | + | |
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | Slika 12. -switch | + | |
| - | {{ :slika13.png? | + | U switchu postoji 7 caseva, a case se određuje prema vrijednosti u puVar2. |
| + | {{ rev2_ghidra: | ||
| + | {{ rev2_ghidra: | ||
| Vidi se da se u svakom caseu uzima user_input_char, | Vidi se da se u svakom caseu uzima user_input_char, | ||
| - | + | u caseu 1 operacija je XOR (^), u caseu 2 operacije je ADD (+) u caseu 3 operacije je SUB (-), u caseu 4 operacije je MUL (*), u caseu 5 operacija je bitwise SHIFT (<< i >>), u caseu 6 operacija je bitwise NOT (~) i u caseu 7 operacije je također ADD (+), kao što je prethodno opisano. | |
| - | u case 1 operacija je XOR (^), u case 2 operacije je ADD (+) u case 3 operacije je SUB (-), u case 4 operacije je MUL (*), u case 5 operacija je bitwise SHIFT (<< i >>), u case 6 operacija je bitwise NOT (~) i u case 7 operacije je također ADD (+), kao što je prethodno opisano. | + | |
| - | + | ||
| - | + | ||
| odlaskom na adresu gdje je Stack[-0x158] i dvoklikom na XREF[3,12] | odlaskom na adresu gdje je Stack[-0x158] i dvoklikom na XREF[3,12] | ||
| - | + | {{ rev2_ghidra: | |
| - | + | ||
| - | Slika 13. Stack[-0x158] | + | |
| - | + | ||
| - | {{ : | + | |
| Također možemo vidjeti opis switch caseva, koji je upravo bio opisan. | Također možemo vidjeti opis switch caseva, koji je upravo bio opisan. | ||
| - | + | {{ rev2_ghidra: | |
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | Slika 14. switch caseovi. | + | |
| - | + | ||
| - | {{ : | + | |
| Još je preostalo pronaći što je varijabla koja određuje koji switch case će biti odabran, operandi u switch caseovima i očekivani rezultati operacija, koji su svi određeni u varijabli puVar2. | Još je preostalo pronaći što je varijabla koja određuje koji switch case će biti odabran, operandi u switch caseovima i očekivani rezultati operacija, koji su svi određeni u varijabli puVar2. | ||
| + | Varijabla puVar2 je definirana pri početku programa i pokazuje na adresu varijable local_158, koja sadržava hex vrijednosti. | ||
| - | Varijabla | + | {{ rev2_ghidra: |
| + | Budući da je program stripped, moguće je da je Ghidra krivo rekonstruirala ove varijable, ne njihove vrijednosti, | ||
| + | Na samom kraju do bloka, nakon switcha casea-7, se user_input pointer povećava za 1, tako će se za sljedeću iteraciju do bloka koristiti sljedeći character iz user inputa. | ||
| - | + | početak do bloka | |
| - | + | ||
| - | Slika 15. - puVar2 | + | |
| - | i local_158 | + | |
| - | + | ||
| - | {{ : | + | |
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | No pošto je program stripped, moguće je da je Ghdira krivo rekonstruirala ove varijable, ne njihove vrijednosti nego kako su definirane, varijable nakon local_158 se ne spominju dalje u main funkciji, a nalaze se odmah ispod local_158 na koju pokazuje puVar2. | + | |
| - | + | ||
| - | Na samoj kraju do blocka, nakon switcha casea-7, se user_input pointer povećava za 1, znači da se za sljedeću iteraciju do blocka koristiti slijedeći character iz user inputa | + | |
| - | + | ||
| - | + | ||
| - | početak do blocka | + | |
| < | < | ||
| Line 324: | Line 204: | ||
| </ | </ | ||
| - | a puVar2 se povećava za 3 u svakoj iteraciji, što taman odgovara 3 vrijednosti koje se koriste | + | a puVar2 se povećava za 3 u svakoj iteraciji, što taman odgovara 3 vrijednosti koje se koriste u svakom do bloku, puVar2 + 0 određuje koji switch case će se izvršiti, |
| - | u svakom do blocku, puVar2 + 0 određuje koji switch case će se izvršiti, | + | |
| + | {{ rev2_ghidra: | ||
| - | + | Budući da će se svaki user input character imati jednu vlastitu iteraciju, a puVar2 se povećava za 3 nakon svake iteracije, | |
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | Slika 16. inkrementiranje puVar2 | + | |
| - | + | ||
| - | {{ : | + | |
| - | + | ||
| - | Pošto | + | |
| Ovo se također može vidjeti na slici ispod, while se izvršava sve dok varijabla pbVar2 koja se povećava za 3 nije jednaka adresi spremljenoj na local_119, koja je RSP uvećan za 0x3f, odnosno 63 u dekadskom zapisu, zato što ima 63 byte vrijednosti spremljenih na stacku. | Ovo se također može vidjeti na slici ispod, while se izvršava sve dok varijabla pbVar2 koja se povećava za 3 nije jednaka adresi spremljenoj na local_119, koja je RSP uvećan za 0x3f, odnosno 63 u dekadskom zapisu, zato što ima 63 byte vrijednosti spremljenih na stacku. | ||
| + | {{ rev2_ghidra: | ||
| - | Slika 17. - local_119 | + | Na slici 16. se vidi da je puVar2 pointer na local_158, sada znamo da local_158 zapravo sadržava 63 bajtova |
| - | + | ||
| - | {{ : | + | |
| - | + | ||
| - | Na slici 15. se vidi da je puVar2 pointer na local_158, sada znamo da local_158 zapravo sadržava 63 byteova, | + | |
| - | + | ||
| - | + | ||
| - | + | ||
| - | Slika 18 - retype local_158 | + | |
| - | + | ||
| - | {{ : | + | |
| - | + | ||
| - | + | ||
| + | {{ rev2_ghidra: | ||
| Znamo da sadržava byte vrijednost i duljine je 63, pa type možemo postaviti u | Znamo da sadržava byte vrijednost i duljine je 63, pa type možemo postaviti u | ||
| Line 364: | Line 223: | ||
| </ | </ | ||
| + | {{ rev2_ghidra: | ||
| - | + | {{ rev2_ghidra: | |
| - | Slika 19 - local_158 byte[63] retype | + | |
| - | + | ||
| - | {{ : | + | |
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | Slika 20. Promijenjeni prikaz local_158 | + | |
| - | + | ||
| - | {{ : | + | |
| Sada je promijenjen prikaz i može se jasno vidjeti da su ovdje zapravo pohranjene vrijednosti: | Sada je promijenjen prikaz i može se jasno vidjeti da su ovdje zapravo pohranjene vrijednosti: | ||
| Line 419: | Line 232: | ||
| OPERAND | OPERAND | ||
| OČEKIVANI REZULTAT | OČEKIVANI REZULTAT | ||
| - | |||
| Vrijednosti su u trojkama, prva vrijednost je operacija koja će se izvršiti, odnosno vrijednost 0-7 koja određuje koji switch case 0-7 će se izvršiti, druga vrijednost je operand, treća vrijednost je očekivani rezultat. Za svaki od 21 user input charactera, postoji odgovarajuća trojka. | Vrijednosti su u trojkama, prva vrijednost je operacija koja će se izvršiti, odnosno vrijednost 0-7 koja određuje koji switch case 0-7 će se izvršiti, druga vrijednost je operand, treća vrijednost je očekivani rezultat. Za svaki od 21 user input charactera, postoji odgovarajuća trojka. | ||
| + | Pregled prvih triju vrijednosti je prikazan na slici ispod. | ||
| - | Pregled prvih triju vrijednosti je prikazan na slici 21. | + | {{ rev2_ghidra: |
| - | + | ||
| - | Slika 21. - prvih tri vrijednosti | + | |
| - | local_158 | + | |
| - | + | ||
| - | {{ : | + | |
| - | + | ||
| - | Ovdje se može vidjeti da je prva vrijednost 2, što znači da će se izvršiti switch case 2, koji je ADD (+), operand je 0x92, a očekivani rezultat je 0xd5. | + | Ovdje se može vidjeti da je prva vrijednost 2, zbog koje će se izvršiti switch case 2, koji je ADD (+), operand je 0x92, a očekivani rezultat je 0xd5. |
| - | Što znači | + | Odnosno |
| < | < | ||
| Line 441: | Line 247: | ||
| </ | </ | ||
| - | odnosno | + | Iz ovoga se može zaključiti koji je očekivani znak korisničkog unosa |
| < | < | ||
| Line 449: | Line 255: | ||
| 0xd5 - 0x92 je 0x43, odnosno 67 u dekadskom zapisu, što odgovara ASCII vrijednosti " | 0xd5 - 0x92 je 0x43, odnosno 67 u dekadskom zapisu, što odgovara ASCII vrijednosti " | ||
| + | Sada se ili ovako " | ||
| - | + | U Python | |
| - | Sada se ili ovako " | + | |
| - | + | ||
| - | + | ||
| - | U python | + | |
| < | < | ||
| Line 460: | Line 263: | ||
| </ | </ | ||
| - | I nakon toga možemo samo prekopirati sve linije koje sadržavaju definirane vrijednosti varijable local_158, kao što su prikazane na slici 20. | + | I nakon toga možemo samo prekopirati sve linije koje sadržavaju definirane vrijednosti varijable local_158, kao što su prikazane na slici 21. |
| - | + | ||
| - | Ovime već imamo validan python kod, u kojem smo definirali sve spremljene operacije, operande i rezultate koji se trebaju izvršiti kao u programu. | + | |
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | Slika 21. - prijenos varijable local_158 u python | + | |
| - | + | ||
| - | {{ : | + | |
| - | + | ||
| + | Ovime već imamo validan Python kod, u kojem smo definirali sve spremljene operacije, operande i rezultate koji se trebaju izvršiti kao u programu. | ||
| + | {{ rev2_ghidra: | ||
| Dodavanjem ovog koda na kraj, možemo dobiti ispis operacija i očekivanih rezultata koji se izvršavaju u programu. | Dodavanjem ovog koda na kraj, možemo dobiti ispis operacija i očekivanih rezultata koji se izvršavaju u programu. | ||
| - | Slika 22. - simulacija programske logike rev2 zadatka | + | {{ rev2_ghidra: |
| - | {{ :slika24.png? | + | Pokretanjem koda sada dobivamo ispis svih operacija nad korisničkim unosom i očekivane rezultate tih operacija: |
| + | {{ rev2_ghidra: | ||
| - | Pokretanjem koda sada dobivamo | + | Sada se samo Python programski kod za ispis operacija |
| - | Slika 23. - programska logika rev2 zadatka | + | < |
| + | local_158 = [0] * 63 | ||
| - | {{ : | + | local_158[0x38] = 0xca |
| + | local_158[0x39] = 7 | ||
| + | local_158[0x3a] = 0 | ||
| + | local_158[0x3b] = 0xcc | ||
| + | local_158[0] = 2 | ||
| + | local_158[1] = 0x92 | ||
| + | local_158[2] = 0xd5 | ||
| + | local_158[3] = 7 | ||
| + | local_158[4] = 0 | ||
| + | local_158[5] = 0xac | ||
| + | local_158[6] = 7 | ||
| + | local_158[7] = 0 | ||
| + | local_158[8] = 0xba | ||
| + | local_158[9] = 7 | ||
| + | local_158[10] = 0 | ||
| + | local_158[0xb] = 0xce | ||
| + | local_158[0xc] = 1 | ||
| + | local_158[0xd] = 0x42 | ||
| + | local_158[0xe] = 0x72 | ||
| + | local_158[0xf] = 1 | ||
| + | local_158[0x30] = 1 | ||
| + | local_158[0x31] = 0xb3 | ||
| + | local_158[0x32] = 0x87 | ||
| + | local_158[0x33] = 4 | ||
| + | local_158[0x34] = 0x89 | ||
| + | local_158[0x35] = 0xf8 | ||
| + | local_158[0x36] = 6 | ||
| + | local_158[0x37] = 0 | ||
| + | local_158[0x10] = 0x7f | ||
| + | local_158[0x11] = 0x4d | ||
| + | local_158[0x12] = 7 | ||
| + | local_158[0x13] = 0 | ||
| + | local_158[0x14] = 0xcb | ||
| + | local_158[0x15] = 4 | ||
| + | local_158[0x16] = 0xf1 | ||
| + | local_158[0x17] = 0xab | ||
| + | local_158[0x18] = 6 | ||
| + | local_158[0x19] = 0 | ||
| + | local_158[0x1a] = 0xce | ||
| + | local_158[0x1b] = 4 | ||
| + | local_158[0x1c] = 0x6b | ||
| + | local_158[0x1d] = 0xbc | ||
| + | local_158[0x1e] = 1 | ||
| + | local_158[0x1f] = 0x7d | ||
| + | local_158[0x3c] = 2 | ||
| + | local_158[0x3d] = 0x98 | ||
| + | local_158[0x3e] = 0xf5 | ||
| + | local_158[0x20] = 0x44 | ||
| + | local_158[0x21] = 1 | ||
| + | local_158[0x22] = 0xe5 | ||
| + | local_158[0x23] = 0xd4 | ||
| + | local_158[0x24] = 7 | ||
| + | local_158[0x25] = 0 | ||
| + | local_158[0x26] = 200 | ||
| + | local_158[0x27] = 4 | ||
| + | local_158[0x28] = 0xdd | ||
| + | local_158[0x29] = 7 | ||
| + | local_158[0x2a] = 5 | ||
| + | local_158[0x2b] = 7 | ||
| + | local_158[0x2c] = 0x9a | ||
| + | local_158[0x2d] = 7 | ||
| + | local_158[0x2e] = 0 | ||
| + | local_158[0x2f] = 0xcc | ||
| + | def reconstructUserInput(operation, | ||
| + | def ret(b): | ||
| + | return chr(b & 0xFF) | ||
| + | match operation: | ||
| + | case 1: # XOR | ||
| + | return ret(expected_result ^ operand) | ||
| + | case 2: # ADD | ||
| + | return ret((expected_result - operand) & 0xFF) | ||
| + | case 3: # SUB | ||
| + | return ret((expected_result + operand) & 0xFF) | ||
| + | case 4: # MUL | ||
| + | # solve (user * operand) & 0xFF == expected_result | ||
| + | # since all values are printable and operand is invertible modulo 256, use brute force | ||
| + | for x in range(256): | ||
| + | if (x * operand) & 0xFF == expected_result: | ||
| + | return ret(x) | ||
| + | case 5: # SHIFT (rotate-left) | ||
| + | n = operand & 7 | ||
| + | for x in range(256): | ||
| + | if ((x << n) | (x >> (8 - n))) & 0xFF == expected_result: | ||
| + | return ret(x) | ||
| + | case 6: # BITWISE NOT (no operand) | ||
| + | return ret((~expected_result) & 0xFF) | ||
| + | case 7: # ADD (no operand) | ||
| + | return ret((-expected_result) & 0xFF) | ||
| - | Sada se samo Python programski kod sa slike 22 može promijeniti da radi inverz operacija za svih 7 slučaja, tako nađe korisnički input koji bi riješio postavljenje jednadžbe | + | i = 0 |
| + | user_input_solve = "" | ||
| + | while i < 63: | ||
| + | operation = local_158[i] | ||
| + | operand = local_158[i + 1] | ||
| + | expected_result =local_158[i + 2] | ||
| - | Slika 24. - Inverz operacija zadatka | + | userInput = reconstructUserInput(operation, |
| - | {{ : | + | print(userInput) |
| + | |||
| + | user_input_solve += userInput | ||
| + | i += 3 | ||
| + | print(" | ||
| - | + | </code> | |
| - | Pokretanjem ovog programa, dobiva se rješenje zadatka | + | |
| - | + | ||
| - | + | ||
| - | Slika 25. - rješenje zadatka | + | |
| - | + | ||
| - | {{ : | + | |
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | Napomena: | + | |
| - | + | ||
| - | Preporučuje se da kad radite reversing, probate otvoriti datoteku i s drugim alatima za reverzno inženjerstvo, | + | |
| - | + | ||
| - | + | ||
| - | Slika 26. - Binary Ninja prikaz rev2 | + | |
| - | + | ||
| - | {{ : | + | |
| - | + | ||
| - | + | ||
| - | Može se vidjeti da je Binary Ninja odmah uspješno rekonstruirala dužinu i sadržaj local_158 varijable, koja sadrži trojke operacija, operand, očekivani rezultat, što je u Ghidri bilo potrebno ručno postaviti. | + | |
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | Angr rješenje: | + | |
| - | + | ||
| - | Angr je napredan i moćan alat koji se koristi za analizu binarnih izvršnih datoteka, gdje se može koristiti za reverzno inženjerstvo, | + | |
| - | + | ||
| - | Angr pruža mogućnost tehnike simboličnog izvršavanja (symbolic execution), koja spada pod dinamičku analizu. Simbolično izvršavanje može tretirati neki korisnički unos kao neodređenu simboličnu varijablu, umjesto definirane vrijednosti, | + | |
| - | + | ||
| - | + | ||
| - | Npr. simbolička varijabla " | + | |
| - | + | ||
| - | grana 1: len(password) = 21 | + | |
| - | grana 2: len(password) != 21 | + | |
| - | + | ||
| - | + | ||
| - | + | ||
| - | Nakon toga na provjeri prvog znaka korisničkog unosa zabilježi, | + | |
| - | + | ||
| - | grana 1.1: | + | |
| - | grana 1.2: | + | |
| - | + | ||
| - | + | ||
| - | I na takav način izvršava program, bez da definira vrijednost simbolične varijable password, sve dok ne dođe do željene grane, koja se može definirati kao grana u kojoj se ispiše string " | + | |
| - | + | ||
| - | + | ||
| - | Efektivno, Angr alatu je samo potrebno objasniti kojoj varijabli mora naći specifičnu vrijednost i koja je željena putanja izvršavanja programa, a Angr alat sam rješava ostalo. | + | |
| - | + | ||
| - | + | ||
| - | U nastavku je prikazana najjednostavnije Angr skripta koja rješava ovaj zadatak. | + | |
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | Slika 27. - Angr skripta - rješenje zadatka | + | |
| - | + | ||
| - | {{ : | + | |
| - | + | ||
| - | + | ||
| - | + | ||
| - | Preporučuje se korištenje python virtual environmenta (venv) za instalaciju Angr paketa i pokretanje ove skripte. | + | |
| - | + | ||
| - | + | ||
| - | + | ||
| - | Napravite novi direktorij, u njega stavite rev2 izvršnu datoteku i Python Angr skriptu, potom izvršite naredbe za postavljanje virtualnog okruženja: | + | |
| - | + | ||
| - | < | + | |
| - | python -m venv . | + | |
| - | source ./ | + | |
| - | pip install angr claripy | + | |
| - | python angrSolveScript.py | + | |
| - | </ | + | |
| - | + | ||
| - | Posljednjom naredbom se pokreće skripta (umjesto angrSolveScript.py stavite ime svoje skripte). | + | |
| - | + | ||
| - | Pokretanjem ove skripte, Angr nalazi rješenje zadatka. | + | |
| - | + | ||
| - | + | ||
| - | + | ||
| - | Slika 28. - Rješenje zadatka s pomoću Angr-a | + | |
| - | + | ||
| - | {{ : | + | |
| - | + | ||
| - | Skripta prikazana na slici 27 ima komentare u kojima su objašnjenja koda, no još će dodatno biti objašnjeno kako se pronađe početna adresa i željena adresa za pronalazak, kao i dvije hook funkcije. | + | |
| - | + | ||
| - | + | ||
| - | Na liniji koda 11, | + | |
| - | < | + | |
| - | state.regs.rip = 0x004017e0 | + | |
| - | </ | + | |
| - | + | ||
| - | Ova adresa je adresa prve instrukcije koja se izvršava u main funkciji, i prikazano je kako se može pronaći s pomoću Ghidre na slici ispod | + | |
| - | + | ||
| - | + | ||
| - | Slika 29. - adresa prve instrukcije unutar main funkcije | + | |
| - | + | ||
| - | {{ : | + | |
| - | + | ||
| - | Na liniji koda 41, | + | |
| - | < | + | |
| - | sm.explore(find = 0x00401914) | + | |
| - | </ | + | |
| - | + | ||
| - | Postavlja se adresa instrukcije do koje se želi da Angr nađe put, ova adresa je adresa puts funkcije koja ispisuje " | + | |
| - | + | ||
| - | Na slici ispod je prikazano kako se može pronaći s pomoću Ghidre. | + | |
| - | + | ||
| - | + | ||
| - | Slika 30. - adresa instrukcije kojom se poziva ispis " | + | |
| - | + | ||
| - | {{ : | + | |
| - | + | ||
| - | Druga hook funkcija koja preskače strcspn funkciju definirana na liniji 31 | + | |
| - | + | ||
| - | < | + | |
| - | @p.hook(0x00401887, | + | |
| - | def skip_strcspn(state): | + | |
| - | state.regs.rax = 0x15 | + | |
| - | </ | + | |
| - | + | ||
| - | Se postavlja, jer strcspn funkcija čita iz memorije, no kada se koristi simbolično izvršavanje, | + | |
| - | + | ||
| - | Funkcije vračaju return vrijednost preko RAX registra, mi unaprijed znamo da ova funkcija vraća duljinu unesenog korisničkog unosa i da je željena duljina korisničkog unosa 21, odnosno 0x15 hex, zato se ovo ovom linijom postavlja željena return vrijednost | + | |
| - | < | + | |
| - | state.regs.rax = 0x15 | + | |
| - | </ | + | |
| - | + | ||
| - | Prvi argument hook funkcije | + | |
| - | < | + | |
| - | 0x00401887 | + | |
| - | </ | + | |
| - | je adresa instrukcije koja poziva izvršavanje te funkcije, na slici ispod je prikazano kako je pronađena. | + | |
| - | + | ||
| - | + | ||
| - | Slika 31 - adresa instrukcije koja poziva strcspn funkciju | + | |
| - | + | ||
| - | {{ : | + | |
| - | + | ||
| - | Drugi argument hook funkcije, | + | |
| - | < | + | |
| - | length = 5 | + | |
| - | </ | + | |
| - | + | ||
| - | je duljina koja se se preskače, odnosno na kolikom offsetu nakon što se izvrši hook na definiranoj adresi se nastavlja izvršavanje. | + | |
| - | + | ||
| - | Adresa instrukcije odmah nakon adrese instrukcije poziva strscpn funkcije, je | + | |
| - | + | ||
| - | < | + | |
| - | 0x0040188c | + | |
| - | </ | + | |
| - | + | ||
| - | kao što se vidi na slici 31 (MOV instrukcija, | + | |
| - | + | ||
| - | < | + | |
| - | 0x0040188c - 0x00401887 = 5 | + | |
| - | </ | + | |
| - | + | ||
| - | Zato je length argumentu postavljena vrijednost 5. | + | |
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | Preostala hook funkcija se koristi kako bi se preskočio fgets, koji čita iz stdin (standard input), kojim se zapravo unosi korisnički unos, što je u ovom kontekstu zapravo definirana simbolična varijabla password u Angr skripti, koja se želi postaviti umjesto korisničkog unosa preko stdin. | + | |
| - | + | ||
| - | < | + | |
| - | @p.hook(0x0040186f, | + | |
| - | def skip_fgets(state): | + | |
| - | state.memory.store(state.regs.rsp + 0x40, | + | |
| - | state.regs.rax = state.regs.rsp + 0x40 | + | |
| - | </ | + | |
| - | + | ||
| - | + | ||
| - | Argumenti hook funkcije su isti kao i u prethodnom objašnjenju, | + | |
| - | + | ||
| - | + | ||
| - | Slika 31 - adresa instrukcije koja poziva fgets funkciju | + | |
| - | + | ||
| - | {{ : | + | |
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | No dio koda koji još nije jasan je sadržaj funkcije | + | |
| - | + | ||
| - | < | + | |
| - | state.memory.store(state.regs.rsp + 0x40, | + | |
| - | state.regs.rax = state.regs.rsp + 0x40 | + | |
| - | </ | + | |
| - | + | ||
| - | + | ||
| - | Fgets funkcija inače uzima korisnički unos iz stdin i sprema ga u buffer čija je adresa poslana kao prvi argument fgets funkcije. No pri simboličnom izvršavanju, | + | |
| - | + | ||
| - | + | ||
| - | < | + | |
| - | state.memory.store(arg1, | + | |
| - | </ | + | |
| - | + | ||
| - | simulira dio fgets funkcije koja korisnički unos sprema u buffer čija je adresa dana kao prvi argument. Ovaj kod će spremiti ono dano argumentom 2 na mjesto definirano argumentom 1. | + | |
| - | + | ||
| - | Jasno je da se simbolična varijabla password želi spremiti umjesto korisničkog unosa, pa je ona drugi argument ovog poziva, no kako pronaći adresu buffera u koji fgets funkcija sprema unos? | + | |
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | Prvi argument je user_input, podcrtan plavom bojom na slici 31. | + | |
| - | + | ||
| - | Pregledom koda u prikazu dekompajlirane main funkcije vidi se linija: | + | |
| - | + | ||
| - | < | + | |
| - | user_input = input_buffer; | + | |
| - | </ | + | |
| - | + | ||
| - | Također, prije poziva funkcije, funkciji se prvi argument prosljeđuje preko RDI registra. | + | |
| - | + | ||
| - | Ova adresa se može ili pronaći praćenjem što je bilo postavljeno u RDI registar prije poziva fgets funkcije, ili samo pronalaskom input_buffer adrese. | + | |
| - | + | ||
| - | Na slici ispod, prikazana su oba načina. | + | |
| - | + | ||
| - | + | ||
| - | Slika 32 - input_buffer adresa | + | |
| - | + | ||
| - | {{ : | + | |
| - | + | ||
| - | Vidi se da se odmah prije poziva fgets funkcije u RDI registar kojim se prosljeđuje prvi argument, odnosno input_buffer postavlja vrijednost RBX registra, koji je ranije definiran, gdje se vidi relativna adresa RSP+0x40. | + | |
| - | + | ||
| - | Također dvoklikom na input_buffer u prikazu dekompajliranog koda, označenog crvenom bojom s desne strane, se odmah prikazuje instrukcija označena crvenom bojom na vrhu slike, u kojoj se vidi relativna adresa RSP+0x40. | + | |
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | Zato se ovom linijom | + | |
| - | + | ||
| - | < | + | |
| - | state.memory.store(state.regs.rsp + 0x40, | + | |
| - | </ | + | |
| - | + | ||
| - | efektivno postavlja simbolična varijabla password u input_buffer. | + | |
| - | + | ||
| - | + | ||
| - | Nakon što se fgets funkcija uspješno izvrši, ona vraća pointer na buffer u koji je upisan unos, odnosno pointer na input_buffer u ovom slučaju. Pošto funkcija vraća return rezultat preko RAX registra, samo se postavlja vrijednost RAX registra na relativnu adresu input_buffera. | + | |
| - | + | ||
| - | < | + | |
| - | state.regs.rax = state.regs.rsp + 0x40 | + | |
| - | </ | + | |
| - | + | ||
| - | + | ||
| - | + | ||
| - | Ako vas Angr alat zanima i želite i više naučiti, na slici ispod je prikaz Angr skripte koja također rješava ovaj zadatak, ali na još brži i efikasniji način. | + | |
| - | Slika 33. - Brža i efikasnija Angr skripta | + | |
| - | + | ||
| - | {{ : | + | |
| - | + | ||
| - | Ova skripta ne radi full_init_state, | + | |
| - | + | ||
| - | + | ||
| - | + | ||
| - | Breakpoint counter rješenje: | + | |
| - | + | ||
| - | Pošto ovaj programski kod ima "early exit", odnosno kad detektira da je neka znamenka korisničkog unosa kriva, odmah ispisuje "Wrong password!" | + | |
| - | Zato se može zabilježiti koliko koda se izvršilo za koji korisnički unos, ili " | + | |
| - | + | ||
| - | < | + | |
| - | N(charset) * Length(input) | + | |
| - | </ | + | |
| - | + | ||
| - | No pošto je poznato da je unos duljine 21 te da je unos u formatu | + | |
| - | + | ||
| - | < | + | |
| - | CTF2025[< | + | |
| - | </ | + | |
| - | + | ||
| - | Jedini dio koji nije poznat je 12 znamenkasti broj, za koji bi prostor pretraživanja u najgorem slučaju bio | + | |
| - | + | ||
| - | < | + | |
| - | 10 (charset: znamenke 0-9) * 12 (broj nepoznatih znakova) = 120 | + | |
| - | </ | + | |
| - | + | ||
| - | Što je lagano rješivo. | + | |
| - | + | ||
| - | U slučaju da program nema raniji završetak izvršavanja programa pri pronalasku prvog neispravnog znaka, ovaj način rješavanja ne bi bio moguć, umjesto " | + | |
| - | + | ||
| - | < | + | |
| - | 10**12 | + | |
| - | </ | + | |
| - | + | ||
| - | što nije izvedivo. | + | |
| - | + | ||
| - | + | ||
| - | + | ||
| - | Pseudokod ovog rješenja je postavljanje breakpointa na određeni dio do while petlje te isprobavanje svih mogućih unosa za prvi nepoznati znak. Onaj znak koji je uzrokovao izvršavanje više koda (što će se dogoditi samo u slučaju kada je ispravan znak), odnosno znak za koji se u izvršavanju više puta prošlo breakpointom na ulasku u petlju, je ispravan znak za tu poziciju unosa. | + | |
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | Za rješenje koje broji prolaske breakpointova, | + | |
| - | + | ||
| - | < | + | |
| - | python -m venv . | + | |
| - | source ./ | + | |
| - | pip install libdebug | + | |
| - | python BPCountSolveScript.py | + | |
| - | </ | + | |
| - | + | ||
| - | Zadnja naredba pokreće skriptu. | + | |
| - | + | ||
| - | + | ||
| - | Skripta je prikazana na slici ispod。 | + | |
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | Važno je da skripta u svakom pokušaju pošalje unos duljine 21, kako bi uvijek bio zadovoljen uvjet da je korisnički unos duljine 21, te nakon toga na opisani način redoslijedom pronalazi jedan po jedan znak korisničkog unosa, koji jednom više prođe postavljenim breakpointom nego drugi znakovi na toj poziciji. Također je važan redoslijed, da prolazi znakove redom od najmanjeg do najvećeg indeksa (prvo znak odmah nakon " | + | |
| - | + | ||
| - | + | ||
| - | + | ||
| - | Adresa breakpointa je postavljena kao | + | |
| - | < | + | |
| - | 0x401900 | + | |
| - | </file> | + | |
| - | + | ||
| - | Adresa instrukcije na kojoj je postavljen breakpoint koji se broji pri izvršavanju je prikazan slikom ispod | + | |
| + | Pokretanjem ovog programa dobiva se rješenje zadatka | ||
| + | {{ rev2_ghidra: | ||
| - | Ovaj kod će se izvršiti samo ako je znamenka na toj poziciji odnosno u toj iteraciji petlje bila ispravna pa se nije dogodio jump na ispis "Wrong password!" | + | ==Napomena== |
| + | Preporučuje se da kad radite reversing, probate otvoriti datoteku i drugim alatima za reverzno inženjerstvo, | ||
| + | {{ rev2_ghidra: | ||
| - | Pokretanjem ove skripte dobiva | + | Može se vidjeti da je Binary Ninja odmah uspješno rekonstruirala duljinu i sadržaj local_158 varijable, koja sadrži trojke operacija, operand, očekivani rezultat, što je u Ghidri bilo potrebno ručno postaviti. |
rev2_ghidra.1761837372.txt.gz · Last modified: 2025/12/01 11:40 (external edit)