BGGP3
Цель BGGP этого года — найти наименьший файл, который приведет к сбою конкретной программы. Мне понравилась идея этого челленджа, так как он показался интересным и доступным. Прочитав правила челленджа и примеры: https://tmpout.sh/bggp/3/ я решил поискать хорошую цель, чтобы начать фаззинг.
Цель
Моей первой целью был tshark, я решил, что это будет хорошо, так как я недавно проделал некоторую работу по изменению исходного кода wireshark и имел некоторое представление о том, как это работает. Я также понял, что из-за большого количества протоколов и типов файлов, поддерживаемых wireshark, происходит много синтаксического анализа. Честно говоря, я потратил на эту цель всего день и не продвинулся очень далеко. Я решил поискать какие-то другие цели, в идеале, которые включали бы много парсинга из общего типа файлов, и именно тогда я наткнулся на munpack: https://linux.die.net/man/1/munpack.
Начать фаззинг
Munpack — это программа, которая читает файлы электронной почты, извлекает любые вложения (технически только первое обнаруженное вложение) и записывает копию вложения на диск. Чтобы начать фаззинг munpack, мне нужно было найти несколько примеров файлов .eml. Мне удалось найти этот git-репозиторий, содержащий несколько образцов .eml: https://github.com/mikel/mail/blob/master/spec/fixtures/emails/
Я клонировал репозиторий git, скопировал несколько семплов в новый каталог и запустил honggfuzz с помощью следующей команды:
honggfuzz -i Samples -x — /usr/bin/munpack ___ФАЙЛ___
К моему удивлению, это вызвало сбой менее чем за 10 секунд работы. Я позволил фаззеру продолжаться еще немного, сбои росли каждые несколько секунд, но только один из них был уникальным. На этом я решил прекратить фаззинг и взглянуть на файл, вызвавший сбой.
Бинарный файл munpack, предоставленный apt, был удален, поэтому я скачал исходный код и скомпилировал его вручную, а затем снова протестировал сбой, который дал тот же результат.
Крушение
Взглянув на вывод GDB об этом сбое, я понял, что он был вызван переполнением буфера при вызове sprintf из функции с именем os_newfiletype. Я также мог видеть, что строка формата, переданная sprintf, была «%s.%d», что помогло определить, какой вызов sprintf вызывал это переполнение (было несколько вызовов sprintf, и некоторые из них также были уязвимы для подобных переполнений).
В поисках этой конкретной строки формата в функции os_newfiletype я обнаружил переполнение вызова sprintf:
Глядя на этот раздел кода, мы видим, что этот вызов sprintf происходит только тогда, когда флаг overwrite_files не установлен, и если вызов fopen(fname, «r») успешен, что указывает на то, что файл, который он пытается открыть, существует. . Если встречаются оба этих случая, исходное имя файла, fname, объединяется с суффиксом файла.
Целью этого кода является предотвращение перезаписи файлов с тем же именем в процессе извлечения вложений электронной почты, но проблема с этим конкретным вызовом sprintf заключается в том, что нет проверки, чтобы увидеть, превышает ли размер fname размер buf. ручка. Ранее в этой функции был объявлен buf со статическим размером 128 байт.
Я проверил, какой максимальный размер имени файла в Linux, и, по-видимому, имя файла может иметь длину до 255 символов (4096 символов, включая путь), что достаточно для переполнения 128-байтового буфера.
Уменьшить размер файла
Итак, на данный момент у меня есть файл, который вызывает сбой из-за переполнения буфера в имени файла, извлеченном из файла .eml, но сам файл довольно большой.
root@mern:~# ls -alh big.eml -rw-r--r-- 1 root root 645 Jul 19 19:41 big.eml
root@mern:~# cat big.eml Subject: this message JUST contains an attachment From: Test Test <[email protected]> To: [email protected] Content-Disposition: attachment; filename=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"\ Content-Transfer-Encoding: base64 Content-Description: Attachment has identical content to above foo.gz Message-Id: <blah@localhost> Mime-Version: 1.0 Date: 23 Oct 2003 22:40:49 -0700 Content-Type: text/plain;name="AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
blahblahblahblahblah
Чтобы уменьшить размер входного файла, я решил еще раз взглянуть на код и посмотреть, как именно идентифицируются и извлекаются вложения.
Присмотревшись к исходному коду, я увидел, что входной файл считывается в структуру, которая передается функции с именем handleMessage. Эта функция немедленно вызывает другую функцию с именем ParseHeaders, которая, как следует из названия, анализирует файл в поисках различных полей заголовков и устанавливает соответствующие переменные при обнаружении этих полей.
Одним из полей, которые анализируются во время этого процесса, является Content-Type, для которого, как видно из приведенного выше примера, установлено значение «text/plain». Этот тип содержимого передается функции saveToFile для обработки извлечения вложения.
Итак, теперь, когда я увидел, что поле Content-Type вызывает вызов saveToFile, а длина имени файла вызывает фактическое переполнение, я решил удалить весь остальной текст из тестового файла, кроме содержимого: тип и имя файла, что оставляет меня с этим:
Content-Type:text/plain;name="AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
Я прогнал этот раздетый файл через munpack, и, к моему удовольствию, он вызвал такой же сбой. Итак, теперь у меня есть файл размером 158 байт, что не очень мало, но хоть что-то.
Угон казни
Теперь, когда у меня есть рабочий сбой и уменьшенный файл, я попытался посмотреть, смогу ли я перехватить выполнение.
Глядя на вывод, полученный в результате сбоя, мы видим, что программа завершается из-за обнаружения переполнения буфера.
root@mern:~/mpack-1.6# munpack ~/final.eml
*** buffer overflow detected ***: terminated
Aborted (core dumped)
Кажется, что какая-то защита мешает этому переполнению быть полезным, и просмотр вывода GDB об этом сбое дает нам подсказку.
В приведенном выше выводе мы видим, что sprintf на самом деле вызывает другую функцию __sprintf_chk, которая в конечном итоге вызывает _IO_str_chk_overflow. Я решил изучить эти функции, чтобы понять, что здесь происходит.
В документации указано следующее о __sprintf_chk
"The interface __sprintf_chk() shall function in the same way as the interface sprintf(), except that __sprintf_chk() shall check for stack overflow before computing a result, depending on the value of the flag parameter. If an overflow is anticipated, the function shall abort and the program calling it shall exit."
Основываясь на этом описании, я решил взглянуть на этот сбой после отключения некоторых средств защиты стека. Я перекомпилировал munpack, добавив следующие флаги компилятора:
-fno-stack-protector -D_FORTIFY_SOURCE=0
Первоначально файл, который я использовал для сбоя munpack, не вызывал сбоя после отключения этих средств защиты, но после увеличения размера имени файла в файле я смог перезаписать достаточно стека, чтобы перезаписать несколько регистров, включая $rip.
К сожалению, мне не удалось полностью перехватить выполнение с помощью этого переполнения, так как munpack выполняет некоторую очистку имени файла перед попыткой его записи. Я должен представить, что есть способы использовать это переполнение для захвата выполнения, но я не смог понять это для себя.
Точки
Размер файла: 4096–158 = 3938
Запись: 3938 + 1024 = 4962
Всего: 4962
Заключение
Хотя другие примеры записей, которые я видел, более интересны, чем мои выводы, я все же был рад принять участие в этом испытании и определенно узнал кое-что по пути.
Проверьте эти другие связанные статьи: