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

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

st4m1n4 (st4m1n4@lovecity.ru)

Ежедневник эксплойтера

Практически каждый день в различных сводках по security проскакивают сообщения: "Найдено переполнение буфера в такой-то софтине; некорректная обработка параметра там-то; исполнение кода с административных привилегий" и т.д. Что же делает в таких случаях среднестатистический хакер? Правильно! Начинает судорожно искать рабочий эксплойт и, при обнаружении оного, так же судорожно ломать с помощью находки уязвимые хосты. В некоторых случаях это не требующее особой мозговой деятельности занятие оправдывает себя. Например, когда нашему хакеру необходимо выполнить и перевыполнить план по отдефейсенным сайтам и благодаря этому стать в своих кругах общепринятой элитой.

Но все же у многих возникает желание выйти за границы, исчисляемые десятками и сотнями использованных эксплоитов, и заняться "чиста отцовской" деятельностью - не пустым использованием, а исследованием. О чем я? О том, что читать bugtraq хорошо, а уметь обходиться без него/использовать с наибольшей пользой - еще лучше! Что делать? Будем вести направленное изучение программного обеспечения с целью нахождения в нем ошибок. Большая часть качественного, профессионального современного ПО написана на языке Си, поэтому именно о нем пойдет речь, хотя хотелось бы сказать, что нижеизложенная информация применима к любому софту, написанному для Intel и Intel-like процессоров.

Buffer overflow

Пожалуй, самым распространенным словосочетанием, ходящим между людьми, каким-либо боком связанными с security, является "buffer overflow", или по-нашенски - "переполнение буфера". На самом деле переполнением буфера можно назвать технологию или еще лучше - класс ошибок, благодаря которым можно творить интересные вещи с программой, в которой они имеются. Чтобы не разводить мутных базаров на несколько страниц, объясню основную мысль практически всех переполнений на конкретном примере, включающем "классическое переполнение буфера". Допустим, есть у нас следующая "дырявая" программа:

void main (int argc, char **argv)

{

if (argc != 2)

retu;

bugged (argv[1]);

}

void bugged (char *str)

{

char c;

char buffer[2];

strcpy (buffer, str);

}

Если ее скомпилировать и запустить с параметром длиной в более чем один символ:

"./a.out 1111111111111111111"

...то она просто-напросто упадет.

Wazzzzzzzzzup?

Теперь разберемся, из-за чего это происходит. В финале кода вызывается библиотечная функция strcpy(), копирующая строку, на которую указывает второй параметр, в массив, указатель на который передается в качестве первого параметра. С чего начинается Родина, тьфу, строка? Первым ее параметром канает char-массив фиксированной длины в 2 элемента, второй элемент - строка, поступающая в качестве параметра для проги. В нашем случае - "1111111111111111111", т.е., включая завершающий нуль, она представляет собою char-массив из 20-и элементов. Что же здесь происходит? Мы пытаемся скопировать в массив из двух байт другой массив, длина которого - 20 байт. Производя копирование, функция strcpy записывает в массив "buffer" 2 байта, а остальные 18 байт пишутся в стек дальше, уже за пределы отведенного под buffer места, туда, где располагается переменная "c". Там же находится переменная "str" и, наконец, там же находится адрес возврата функции. Помимо этого, может перекрываться еще куча других данных, которые зависят от архитектуры процессора, операционной системы, компилятора. Естественно, значения всех этих переменных заменяются на копируемые данные.

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

ttfb: 5.9459209442139 ms