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

Заключение

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

Проверьте эти другие связанные статьи:

https://ortiz.sh/identity/2022/07/17/BGGP3.html

https://remyhax.xyz/posts/bggp3-cob/