col - pwnable.kr

Sat 27 July 2019 by TORGiren pl en 

W tym poście rozwiążemy sobie zadanie col ze strony pwnable.

Analiza wstępna

Aby zalogować się do zadania wykonujemy:

$ ssh col@pwnable.kr -p2222

a jako hasło podajemy guest.

Po zalogowaniu się widzimy 3 pliki

fd@ubuntu:~$ ls -l
total 16
-r-sr-x--- 1 col_pwn col     7341 Jun 11  2014 col
-rw-r--r-- 1 root    root     555 Jun 12  2014 col.c
-r--r----- 1 col_pwn col_pwn   52 Jun 11  2014 flag

flaga znajduje się w pliku flag, natomiast nie mamy do niego dostępu. Mamy natomiast możliwość wykonania programu col, który ma ustawiony suid pozwalający na odczyt flagi.

Uruchomienie aplikacji daje następujące wyjście:

fd@ubuntu:~$ ./col
usage : ./col [passcode]

Dlatego sprawdźmy co robi ta aplikacja. Kod programu znajduje się w pliku col.c

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <stdio.h>
#include <string.h>
unsigned long hashcode = 0x21DD09EC;
unsigned long check_password(const char* p){
     int* ip = (int*)p;
     int i;
     int res=0;
     for(i=0; i<5; i++){
             res += ip[i];
     }
     return res;
}

int main(int argc, char* argv[]){
     if(argc<2){
             printf("usage : %s [passcode]\n", argv[0]);
             return 0;
     }
     if(strlen(argv[1]) != 20){
             printf("passcode length should be 20 bytes\n");
             return 0;
     }

     if(hashcode == check_password( argv[1] )){
             system("/bin/cat flag");
             return 0;
     }
     else
             printf("wrong passcode.\n");
     return 0;
}

Patrząc na linie 15-22 widzimy, że aplikacja oczekuje co najmniej jednego argumentu oraz że pierwszy argument będzie miał długość dokładnie 20 znaków.

Następnie w linii 24 widzimy, że wynik funkcji check_password uruchomiony z podanych przez nas argumentem, musi być równy globalnej zmiennej hashcode równej 0x21DD09EC.

Głównym celem tego zadania jest analiza funkcji check_password oraz znalezienie takiego argumentu p, aby wynik równy był 0x21DD09EC.

Funkcja check_password interpretuje podany 20-bajtowy ciąg znaków, jako zmienne typu int.

Aby wiedzieć jaki rozmiar ma zmienna typu int, należy sprawdzić jak została skompilowana aplikacja.

col@ubuntu:~$ file col
col: setuid ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.24, BuildID[sha1]=05a10e253161f02d8e6553d95018bc82c7b531fe, not stripped

Widzimy, że col jest aplikacją 32-bitową, a to znaczy, że najprawdopodobniej zmienna typu int będzie miała rozmiar 32 bitów, czyli 4 bajtów.

Wynika z tego, że 20 bajtowy ciąg znaków, będący argumentem funkcji check_password, może zostać zinterpretowany jako pięć 4-bajtowych wartości typu int.

Dodatkowo, należy sprawdzić w jakiej konwencji są zapisywane bity w pamięci.

col@ubuntu:~$ readelf col -h
ELF Header:
  Magic:   7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
  Class:                             ELF32
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              EXEC (Executable file)
  Machine:                           Intel 80386
  Version:                           0x1
  Entry point address:               0x80483e0
  Start of program headers:          52 (bytes into file)
  Start of section headers:          4428 (bytes into file)
  Flags:                             0x0
  Size of this header:               52 (bytes)
  Size of program headers:           32 (bytes)
  Number of program headers:         9
  Size of section headers:           40 (bytes)
  Number of section headers:         30
  Section header string table index: 27

Widzimy, że została zastosowana little endian.

Exploit

Aby warunek poprawności hasła został spełniony, suma 5 liczb całkowitych otrzymanych z podanego string-a musi być równa 0x21DD09EC. Jednym ze sposobów aby to osiągnąć, jest znalezienie znalezienie 5 liczb których suma da taką wartość, a następnie zapisanie ich w postaci pojedynczych bajtów.

W celu poszukiwania liczb użyjemy pythona, gdyż dobrze sprawdza się jako kalkulator. na początku próbujemy podzielić szukaną liczbę przez 5, a gdy to się nie uda, to szukamy największej liczby, mniejszej od naszego wyniku, która będzie podzielna przez 5

>>> 0x21DD09EC/5.0
113626824.8
>>> 0x21DD09EC - 5 * 113626824
4
>>> hex(113626824)
'0x6c5cec8'
>>> hex(0x6c5cec8 + 4)
'0x6c5cecc'
>>> 4 * 0x6c5cec8 + 0x6c5cecc ==  0x21DD09EC
True

Z powyższego widzimy, że potrzebujemy przekazać cztery wartości 0x6c5cec8 oraz jedną o 4 większą, czyli 0x6c5cecc .

Ponieważ aplikacja jest w konwencji little endian, bajty należy podawać od końca.

Aby wypisać konkretne bity, użyjemy echo z bash i podamy bajty od tył i przekażemy wynik do aplikacji col, jako pierwszy argument.

col@ubuntu:~$ ./col $(echo -ne "\xc8\xce\xc5\x06\xc8\xce\xc5\x06\xc8\xce\xc5\x06\xc8\xce\xc5\x06\xcc\xce\xc5\x06")
daddy! I just managed to create a hash collision :)

I otrzymaliśmy szukaną flagę.


Fork me on GitHub