Издательский дом ООО "Гейм Лэнд"СПЕЦВЫПУСК ЖУРНАЛА ХАКЕР #45, АВГУСТ 2004 г.

Дерни printf за хвост

Крис Касперски aka мыщъх

Спецвыпуск Xakep, номер #045, стр. 045-032-5


Пошагово трассируя программу по F10 (Step Over – трассировка без захода внутрь функций), введи заданную строку, когда тебя об этом попросят (экран консоли начнет призывно мигать) и продолжай трассировку вплоть до достижения строки 0040103Сh, вызывающей функцию printf. Теперь перейди в окно дампа памяти и введи в адресной строке "ESP", сообщая отладчику, что нам угодно просмотреть содержимое стека, а затем вернись к дизассемблерному коду и нажми F10 еще раз.

Содержимое буфера пользовательского ввода немедленно изменится, подсвечивая ядовито-красным цветом число "0F 00 00 00", записанное в его начале. Перезапись выбранной ячейки памяти успешно состоялась!

Напоминаю, если спецификаторы перекрывают буфер пользовательского ввода, мы можем самостоятельно сформировать указатель, перезаписывая выбранные ячейки памяти произвольным образом. То есть почти произвольным. К ограничениям выбора целевых адресов теперь еще присоединяются и ограничения выбора перезаписываемого значения, которые, между прочим, очень жестки.

Дисбаланс спецификаторов

Каждому спецификатору должен соответствовать парный аргумент. Но должен еще не значит обязан. Ведь спецификаторы и аргументы программисту приходится набивать вручную, и ему ничего не стоит ошибиться! Транслятор откомпилирует такую программу вполне нормально, возможно, негромко выругавшись при этом и выдав на экран предупреждающий warning. Но что произойдет потом?

Если аргументов окажется больше, чем спецификаторов, "лишние" будут проигнорированы. В противном случае функция форматированного вывода, не зная сколько ей аргументов реально передали, снимет со стека первый встретившийся ей мусор. События будут развиваться по сценарию, описанному выше ("Навязывание собственных спецификаторов"), с той лишь разницей, что навязывать спецификаторы злоумышленник сможет только косвенно или не сможет вовсе.

Переполнение буфера-приемника

Функция sprintf относится к числу самых опасных, и все руководства по безопасности в один голос твердят, что лучше пользоваться ее безопасным аналогом – snprintf. Почему? Природа форматированного вывода такова, что предельно достижимую длину результирующей строки очень трудно рассчитать заранее. Рассмотрим следующий код:

Переполним буфер-приемник?

f()

{

char buf[???];

sprintf(buf,"имя:%s возраст:%02d вес:%03d рост:%03d\n",

name, age, m, h);

...

}

Как вы думаете, каких размеров буфер нам потребуется? Из неизвестных факторов здесь присутствуют: длина строки name и "длина" целочисленных переменных age, m, h, преобразуемых функцией sprintf в символьное представление. Кажется логичным, если мы отводим два столбца на возраст и по три на рост и вес, то за вычетом имени и длины форматной строки нам потребуется всего 8 байт. Правильно? А вот и нет! Если строковое представление переменных не умещается в отведенных ему позициях, оно автоматически расширяется, дабы избежать усечения результата. В действительности же, десятичное представление 32-разрядных переменных типа int требует резервирования 11 байт памяти, в противном случае возникает угроза переполнения буфера.

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