Информационная безопасность
[RU] switch to English


Уязвимость форматной строки

Андрей Колищак

март, 2001

[email protected]

Уязвимость форматной строки

Не секрет, что большая часть программного обеспечения, помимо специфичных уязвимостей, содержит “дыры”, связанные с некорректным стилем программирования. Если одни из этих дыр, такие как переполнения буфера, известны много лет, то уязвимость форматной строки стали обсуждать сравнительно недавно. Это кажется странным, если впервые информация о проблеме появилась, по крайней мере, 9 лет назад [1].

Данная уязвимость связана с функциями форматного вывода и форматирования строки, которые используют форматные спецификаторы: printf, fprintf, sprintf, snprintf.

Все эти функции имеют неопределенное количество параметров, т.е. содержат определение: “const char *format, ...”. При этом число параметров и их интерпретация определяются посредством строки форматных спецификаторов format. Эта особенность и является причиной уязвимости при типичном использовании данных функций. Рассмотрим пример:

//example1.c

//

main(int argc, char *argv[])

{

int num = 7;

printf(argv[0]);

}

Отметим, что уязвимость форматной строки имеет место не зависимо от ОС. ОС определяет лишь некоторое отличие механизмов атаки. Все приведенные примеры тестировались на windows2000 sp1.

В результате работы программы на экран выводится название запущенного исполняемого файла.

Компилируем, запускаем:

fsbug>example1

example1

все штатно, ни чего необычного. Теперь попробуем переименовать example1.exe так, чтобы имя содержало форматные спецификаторы для printf, а затем запустим его снова.

fsbug>ren example1.exe %x_%x_%x_example1.exe

fsbug>%x_%x_%x_example1

7_12ffc0_401102_example1

Рассмотрим, что произошло.

Так как printf воспринимает форматные спецификаторы, то, очевидно, что 12ffc0_4010f8_1 является соответствием %x_%x_%x, т.е. мы видим в шестнадцатеричном представлении значения 2-го, 3-го и 4-го параметров вызова функции printf. Printf “не знает” о количестве и наличии параметров. Определяющим фактором здесь является лишь форматная строка, которая указывает на наличие таких параметров. Поэтому в качестве параметров выводится текущее содержимое стека.

Чтобы понять, что именно означают выведенные числа, посмотрим на структуру стека.

 

Из рисунка видно, что выведенные числа это:

7 – переменная num

12ffc – указатель предыдущего кадра локальных переменных

401102 – адрес возврата из функции main.

Ошибочность данного примера в том, что строка форматных спецификаторов, не является константой, и ее значение может быть изменено пользователем. Если

printf(argv[0]);

заменить на:

printf(“%s”, argv[0]);

то все будет работать корректно.

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

Другой интересной целью для атаки является возможность модификации содержимого стека и данных.

Обратимся к следующему примеру.

//example2.c

//

main(int argc, char *argv[])

{

char param1[100];

int num = 7;

if (argc > 1) strncpy(param1, argv[1], sizeof(param1));

printf("\nnum = %d, &num = %p\n", num, &num);

printf(param1);

printf("\nnum = %d, &num = %p\n", num, &num);

}

fsbug>example2 AAAA

num = 7, &num = 0012FF18

AAAA

num = 7, &num = 0012FF18

Видно, что первый параметр программы копируется в буфер param1. При этом буфер не переполняется - все нормально.

Попробуем задать параметр с форматными спецификаторами:

fsbug>example2 AAAA_%x_%x_%x

num = 7, &num = 0012FF18

AAAA_7_41414141_5f78255f

num = 7, &num = 0012FF18

Полученный результат легко объясняется структурой стека.

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

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

sprintf(s,”123%n”, &pos);

pos будет равен трем, так как до обработки модификатора %n было выведено 3 символа ‘123’.

Очевидно, что для модификации памяти необходимо лишь задать адрес, куда будет занесен результат %n. Так как адрес переменной num известен, то чтобы изменить значение переменной num, достаточно ввести следующую форматную строку: %x%x%x%n\x18\xff\x12.

Здесь ‘\x18\xff\x12’ часть адреса переменной num. Старший нулевой байт будет сформирован без нашего участия как символ окончания строки. Спецификаторы %x необходимы для исключения начала форматной строки, чтобы значение адреса num \x18\xff\x12 стало одним из параметров. Рассмотрим следующий пример.

//example3.c

//

#include <windows.h>

main(int argc, char *argv[])

{

WinExec("example2 %x%x%x%n\x18\xff\x12", 0);

}

Запускаем и получаем результат:

fsbug>example3

fsbug>

num = 7, &num = 0012FF18

7782578256e257825↑ ↕

num = 17, &num = 0012FF18

Таким образом, можно задать лишь ограниченный набор значений num. Действительно, большее значение потребует увеличения длины форматной строки, так как значение определяется позицией в строке. Но строка имеет ограниченный размер (для нашего примера param1 имеет размер 100 байт). Для решения этой проблемы при атаках применяются 3 метода:

  • Префикс h (спецификатор %hn). Префикс h – уменьшает до двух байт размер операнда, в который записывается результат %n.
  • Спецификатор ширины выводимого значения. Например, “%100x%n”. Таким образом, для вывода будет использовано минимум 100 символов и в результате обработки спецификатора %n будет записано значение 100.
  • Метод смещенной записи. Суть метода смещенной записи заключается в последовательном побайтном формировании значения. Например, чтобы сформировать значение 0x777504035 по адресу А, выполняется следующее:
    1. 0x00000035 сохраняется по адресу А
    2. 0x00000040 сохраняется по адресу А+1
    3. 0x00000075 сохраняется по адресу А+2
    4. 0x00000077 сохраняется по адресу А+3

 

При этом на формируемое значение накладываются определенные ограничения.

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

    1. 0x000000D0 сохраняется по адресу А
    2. 0x000000E8 сохраняется по адресу А+2
    3. 0x00000177 сохраняется по адресу А+3

Если адрес, по которому производится запись значения, содержит нулевые байты, то данный метод неприменим. В этом случае нулевые байты для первого заданного адреса будут восприняты как конец строки. Например, невозможно данным методом сформировать произвольное значение по адресу переменной num для примера 2.

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

Посредством модификации адресов возврата, указателей на функции применяется схема атаки аналогичная используемой при переполнение буфера. При этом атака сводится к обычной атаке на переполнение буфера, а форматная строка содержит спецификаторы %n с целью модификации указанных адресов для передачи управления коду эксплоита.

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

Коды эксплоитов на известные уязвимости форматной строки представлены ссылками: [3, 7, 8, 9, 10].

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

syslog, printf, fprintf, sprintf, snprintf и др. Если форматная строка не является константой, то, вероятнее всего, имеет место уязвимость. Для автоматизированного поиска можно воспользоваться сканером PScan [12].

В отличие от переполнения буфера, поиск уязвимости при отсутствии исходных кодов аналогичен поиску с исходным кодом и не представляет особой сложности, если воспользоваться дизассемблером, таким как IDA (www.datarescue.com). В данном случае просто проверяются все перекрестные ссылки на функции, работающие с форматными строками.

Рассмотрим, что можно сделать для защиты от атак на данную уязвимость.

  1. Прежде всего, это аудит исходного кода, в том числе и с использованием автоматических сканеров, например PScan [12].
  2. Использование макросов-подстановок вместо оригинальных функций, воспринимающих форматные строки [13]. Таким образом, все вызовы потенциально уязвимых функций перенаправляются в вызовы другой функции, которая проверяет число аргументов и число спецификаторов %. Если эти числа равны, управление передается оригинальной функции, иначе - вызов завершается с ошибкой. На основе данной методики был разработан продукт FormatGuard, который реализован в виде модифицированной версии glibc 2.2 [14]. Для защиты требуется перекомпиляция программы с модифицированной версией glibc.
  3. Применение фильтрации для обнаружения и исключения “опасных” форматных спецификаторов, таких как %n. Эта методика реализуется Libformat [15]. Libformat представляет собой динамическую библиотеку, которая экспортирует функции libc, воспринимающие форматные спецификаторы. Данная библиотека указывается в LD_PRELOAD, что позволяет перехватывать вызовы всех потенциально уязвимых функций. При обнаружении форматной строки, содержащей спецификатор %n, Libformat формирует запись в syslog и завершает текущий процесс. Важно, что при этом не требуется перекомпиляция программы.

 

Ссылки:

  1. Scott Hardy, http://www.securityfocus.com/archive/82/81334
  2. Tim Newsham, Format String Attacks, http://www.guardent.com / rd_whtpr_formatNewsham.html
  3. Pascal Bouchareine, “Format bugs”, Bugtraq, http://www.securityfocus.com / archive / 1 / 70552
  4. Secure Programming for Linux and Unix HOWTO, Control Data Formatting (``Format Strings''), http://www.dwheeler.com / secure-programs / Secure-Programs-HOWTO / control-formatting.html
  5. Avoiding security holes when developing an application - Part 4: format strings http://www-syntim.inria.fr / fractales / Staff / Raynal / LinuxMag / SecProg / Art4 / index.html
  6. Andreas Thuemmel, Analysis of Format Strings Bugs http://www.securityfocus.com/data/library/format-bug-analysis.pdf
  7. tf8, “WuFTPD: Providing remote root since at least1994”, Bugtraq, http://www.securityfocus.com/archive/1/66367
  8. [email protected], “[pkc] format bugs in icecast 1.3.8b2 and prior”, Bugtraq, http://www.securityfocus.com/archive/1/157638
  9. Guido Bakker, “ /bin/su local libc exploit yielding a root shell”, Bugtraq, http://www.securityfocus.com/archive/1/137163
  10. Solar Eclipse, “Exploiting the Libc Locale Subsystem Format String”, http://www.cse.nau.edu/~mc8/whitepapers/locale_sol.txt
  11. Andrey Kolishak, “NT drivers are potentially vulnerable to format string bug”, Bugtraq, http://www.securityfocus.com/archive/1/164555
  12. PScan, http://www.striker.ottawa.on.ca/~aland/pscan/
  13. Mike Frantzen, “Poor man's solution to format bugs”, Bugtraq, http://www.securityfocus.com/archive/1/72118
  14. FormatGuard, http://immunix.org/formatguard.html
  15. Libformat, http://users.rendrag.net/~tim/software/libformat.php3
  16. Крис Касперски, "Неизвестная уязвимость printf", http://www.osp.ru/os/2000/11/030.htm
О сайте | Условия использования
© SecurityVulns, 3APA3A, Владимир Дубровин
Нижний Новгород