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

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

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

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


Первым идет двойное слово 0012FF5Ch (на микропроцессорах архитектуры Intel младший байт располагается по меньшему адресу, то есть все числа записываются в памяти в обратном порядке). Это указатель, соответствующий аргументу функции printf, которому, в свою очередь, соответствует буфер buf_out, содержащий непарный спецификатор "%s" и заставляющий функцию printf извлекать следующее двойное слово из стека, которое представляет собой обыкновенный мусор, оставленный предыдущей функцией. По стечению обстоятельств он (мусор и указатель одновременно) указывает на тот же самый buf_out, и потому нарушения доступа не происходит, зато слово "hello" выводится дважды.

Будем рыть дальше, снимая со стека следующую последовательность адресов: 00408000h (указатель на строку "hello, %s!\n"), 0012FF3Ch (указатель на buf_out), 0012FF3Ch (снова он), 0040800Ch (указатель на строку "введи имя:"), 73257325h (содержимое буфера buf_in, трактуемое как указатель, между прочим указывающий на невыделенную ячейку памяти).

Таким образом, первые пять спецификаторов "%s" проходят сквозь интерпретатор форматного вывода вполне безболезненно, а вот шестой посылает его в космос. Процессор выбрасывает исключение, и выполнение программы аварийно прекращается. Разумеется, спецификаторов не обязательно должно быть ровно шесть – до остальных все равно не дойдет управление.

Обрати внимание: Windows NT приводит именно тот адрес, который мы и ожидали.

Реализация peek

Для просмотра содержимого памяти уязвимой программы можно воспользоваться спецификаторами "%X", "%d" и "%c". Спецификаторы "%X" и "%d" извлекают парное им двойное слово из стека и выводят его в шестнадцатеричном или десятичном виде соответственно. Спецификатор "%c" извлекает парное двойное слово из стека, преобразует его до однобайтового типа char и выводит в символьном виде, отсекая три старших байта. Таким образом, наиболее значимыми из всех являются спецификаторы "%X" и "%c".

Каждый спецификатор "%X" отображает всего лишь одно двойное слово, лежащее в непосредственной близости от вершины стека (точное расположение зависит от прототипа вызываемой функции). Соответственно, N спецификаторов отображают 4*N байт, а максимальная глубина просмотра равна 2*C, где C – предельно допустимый размер пользовательского ввода в байтах. Увы! Читать всю память уязвимого приложения нам никто не даст, отдавая на растерзание лишь крошечный кусочек, в котором, если повезет, могут встретиться секретные данные (например, пароли) или указатели на них. Впрочем, узнать текущее положение указателя тоже неплохо. Но обо всем по порядку.

Запустим нашу демонстрационную программу и введем спецификатор "%X". Она ответит:

введи имя: %X

hello, 12FF5C!

Откуда взялось12FF5C? Обращаясь к дампу памяти, мы видим, что это – двойное слово, следующее за аргументом buf_out и представляющее собой результат жизнедеятельности предыдущей функции, или, попросту говоря, мусор. Ну и какая нам радость от этого? Буфер содержит наш собственный ввод, в котором заведомо нет ничего интересного. Но это лишь часть айсберга. Как уже говорилось в статье, посвященной переполняющимся буферам, для передачи управления на shell-код необходимо знать его абсолютный адрес, который в большинстве случаев неизвестен, и спецификатор "%X" как раз и выводит его на экран!

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