Найди дыру в Си-скрипте

Xakep, номер #032, стр. 032-070-3


Format String Bug

На сегодняшний день самый писк - format string bug. Все о нем говорят, но никто о нем не знает (не принимайте на свой счет, о, знающие люди :)). На самом деле идея и здесь предельно проста. Возьмем еще один пример, хотя он в полной мере не иллюстрирует все возможности переполнения буфера при помощи формат-стринговых функций:

void main (int argc, char **argv)

{

if (argc != 2)

return;

bugged (argv[1]);

}

void bugged (char *str);

{

char buffer[2];

sprintf (buffer, "%s", str);

}

Как видно, программа абсолютно аналогична представленной в предыдущем примере. Даже формат-стринговая функция играет точно такую же роль, что и strcpy() - копирует первый параметр скрипта в переменную buffer. И точно так же, как и там, здесь нет никакой проверки на длину буфера, благодаря чему становится возможным переполнение. Далее - все то же самое, что и с strcpy(), strcat() и т.д., поэтому, чтобы не повторяться, данный случай можно не рассматривать. На самом деле здесь не видна вся мощь формат-стринговых функций в реализации переполнения. Вот немного другой пример:

void main (int argc, char **argv)

{

if (argc != 2)

return;

bugged (argv[1]);

}

void bugged (char *str);

{

char buffer[2];

snprintf (buffer, sizeof(buffer), str);

}

Здесь уже используется функция snprintf(), которая будет копировать в массив buffer не более sizeof (buffer) символов из строки str. Таким образом, у нас сразу отпадает возможность переполнения массива buffer - в любом случае, независимо от длины строки str, в buffer будет скопировано не более двух байт. Но и здесь переполнение так же реально, как и в предыдущих примерах! Для этого нужно разобраться в том, как работают функции printf(), sprintf(), snprintf() и другие.

В нашем случае в качестве исходного буфера берется str. Когда формат-стринговая функция начинает обрабатывать этот буфер, в любом случае будет вестись поиск спецификаторов, которые начинаются с "%". При нахождении такого спецификатора (допустим, это будет спецификатор %d), она вставляет в строку str значение, соответствующее этому спецификатору, т.е.:

snprintf (buffer, sizeof(buffer), str, i);

Если str представляет собой строку "perem=%d", а переменная "i" равна 1000, то в результате работы получится строчка "perem=1000". Если же мы вызовем формат-стринговую функцию без переменной "i", но с точно таким же массивом str, равным "perem=%d":

snprintf (buffer, sizeof(buffer), str);

то поиск внутри стека целочисленной переменной для "%d" все равно будет производиться. В результате функция snprintf() заменит "%d" на значение, которое идет в стеке сразу за указателем на str. Чем же не переполнение? Правда, переполнение только на чтение - при помощи формат-стринговой функции мы можем прочитать практически весь стек софтины. Теперь самое горяченькое - запись в стек, при помощи чего мы можем сотворить все что угодно, начиная от замены адреса возврата и заканчивая заменой значения любой стековой переменной. Для этого вернемся еще раз к спецификаторам. Существует спецификатор "%n". Если snprintf() в исходном буфере находит такой спецификатор, то она будет воспринимать параметр, соответствующий данному спецификатору, как указатель на переменную int, в которую необходимо записать количество символов, идущее в строке перед этим спецификатором. Например, если имеется такой вызов функции:

Назад на стр. 032-070-2  Содержание  Вперед на стр. 032-070-4