col – pwnable.kr

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

Dodaj komentarz