Содержание статьи
Эта статья — опыт использования фаззинга для поиска уязвимостей в современных браузерах. Авторы знают, о чем говорят: на прошедшей недавно конференции PHD они выиграли конкурс «Взломай и унеси». Взломали они Safari (продемонстрировав рабочий 0day для версии под Windows), а домой унесли призовой ноутбук.
Введение в профессию
Итак, приступим? Зафиксируем для порядка список существующих браузеров:
- Windows Internet Explorer (6/7/8)
- Mozilla Firefox (3*/4*)
- Google Chrome
- Safari
- Opera
- Opera Mini
- Netscape Navigator
- Midori
- Skyfire
- Dolphin
- Konpueror
- Dooble
Из них только три продукта являются OpenSource-проектами: это Chrome, Firefox и Konqueror. Все браузеры спроектированы на основе некоторого базового функционала (engine-движка). Вот перечень существующих движков:
- Amaya
- Gecko
- HTMLayout
- KHTML
- Presto
- Prince
- Trident
- WebKit
Например, Chrome и Safari построены на OpenSource-движке WebKit, а популярный Firefox — на Gecko. Для работы JavaScript используют движки данного интерпретатора, вот, например, в Google используется V8 (хотя есть еще Rhino и SpiderMonkey). Также большинство браузеров поддерживают разные плагины, такие как flash, jre (для работы с апплетами) — это дополнительные векторы атак.
Фаззер для браузера
Как и во многих других ситуациях, одним из самых эффективных способов поиска уязвимостей в браузере является фаззинг. Публичных и действительно работающих инструментов здесь не так много, но одним из таковых является замечательная разработка cross_fuzz от Михаила Залевски. Миша работает в Google и с помощью своего фаззера обнаружил более ста багов во всех популярных веб-браузерах, многие из них оказались эксплуатируемы. В его блоге (bit.ly/lbgfqm) есть даже отчет о найденных уязвимостях для Internet Explorer, Firefox, Opera, а также браузеров на движке WebKit. Некоторые из найденных ошибок (даже несмотря на то, что разработчики поставлены в известность) до сих пор не устранены!
При всей своей эффективности cross_fuzz устроен довольно просто.
Фаззер выявляет проблемы путем создания чрезвычайно длинных закрученных последовательностей DOM-операций, которые затрагивают сразу несколько документов. Проверяя возвращаемые объекты и вновь рекурсивно используя их, удается создавать круговые зависимости узлов, с помощью которых устраивается настоящий стресс-тест для механизмов сборки мусора браузера. Таким образом, проверяется способность приложения к правильному и эффективному освобождению памяти для более неиспользуемых объектов. Как показывает практика, выдержать удар браузеру удается далеко не всегда :). Код cross_fuzz написан на HTML/ JavaScript (lcamtuf.coredump.cx/cross_fuzz), поэтому для фаззинга достаточно открыть нужный HTML-файл (есть разные варианты с незначительными отличиями) в браузере. Чтобы нивелировать сетевые задержки, лучше всего скачать исходник на локальную машину. Не забудь при этом выкачать все зависимости (папку /targets и файлы mersenne.js/logo.jpg). Для корректной работы также потребуется включить popup’ы в браузере.
Начинаем практиковаться
В качестве подопытных кроликов мы взяли наиболее распространенные браузеры:
- Firefox 3.6.16;
- Firefox 4.0.1;
- Chrome 10;
- Internet Explorer 8/9;
- Safari 5.0.5.
Открываем HTML-страницу фаззера в каждом из них — и первые результаты не заставляют себя ждать.
Удивительно, но при запуске cross_fuzz в Safari, падение было моментальным — менее чем через 5 секунд. При тестировании Firefox 3.6.16 были падения через 15-30 минут после запуска.
В основном это были падения по DEP. Это своеобразный маркер уязвимостей use-after-free, креши на исполнении потенциально эксплуатабельны и поэтому ценны. Если поменять некоторые условия при запуске, можно уменьшить время срабатывания до 3-5 минут. При тестировании Chrome падения были в дочернем процессе. IE же, что опять же удивительно, ничем не отличился.
Все эти факты меня сильно удивили: я только начал исследовать браузеры, причем делал это публичным фаззером, а уже есть креши. Черт подери, сколько же там багов, если так быстро можно уронить почти любой браузер? Учитывая такие бодрые и быстрые результаты, я про себя отметил, что правильно выбрал вектор атаки.
Cross_fuzz отлично ищет уязвимости в работе с DOM, и этим глупо не воспользоваться.
Включаем логирование и воспроизводим креши
Алгоритм работы фаззера подразумевает генерацию огромного количества обращений к DOM. Фаззеру приходится перебирать множество вариантов, прежде чем браузер вылетит с ошибкой. Но нам мало просто крешнуть приложение — обязательно нужно понять, почему это произошло. Чтобы разобраться, какая последовательность вызовов вызывает креш, нужно включить режим логирования.
Самый простой способ сделать это — чуть подредактировать исходники cross_fuzz и убрать return из функции LOG(message). Убрав немедленный выход из функции логирования, мы добьемся того, что на странице cross_fuzz будут отображаться все вызываемые функции.
К сожалению, cross_fuzz не умеет записывать эти данные в файл. Но для этих целей можно использовать плагины, например, Firebug для Firefox. Для логирования чего-либо в JS нужно вставить следующий код:
try {
console.log('eval %s',name);
ret_value = eval('target.' + name + '(' + par_str + ')');
} catch (e) {
Я немного пропатчил Firebug, чтобы тот мог писать свой лог в файл (исправленный вариант ищи на нашем диске). В результате все действия фаззера (в том числе те, которые приводят к крешу) пишутся в лог в C:\Documents and Settings\username\Application Data\Mozilla\Firefox\ Profiles\XXXXXX.default\js. Тот же самый трюк можно провернуть и в других браузерах, воспользовавшись соответствующими аддонами:
- для Chrome: Firebug Lite for Google Chrome (есть еще встроенный в Chrome);
- для Opera: Opera Dragonfly;
- для Safari: WebKitDeveloperExtras.
После этого мы можем изучить все, что происходило в cross_fuzz, а, значит, и попытаться воспроизвести креши.
Как анализировать креши?
Уронить программу (пусть даже понимая, как) мало. Нам необходимо проанализировать креши и определить, эксплуатируемые они или нет.
Эта тема настолько многогранна, что она явно выходит за рамки этого материала. Наша задача на сегодня — определить ошибки в программе, которые теоретически можно эксплуатировать. А о том, как пишутся сплоиты, обходятся песочницы и защитные механизмы вроде DEP/ ASLR в журнале было немало статей (хотя одних только материалов журнала ][ тут, по правде говоря, недостаточно). Могу лишь дать пару полезных советов. Когда разбираешь креши в таких немаленьких программах как браузеры, немало нервов и времени спасают так называемые отладочные символы. Эта информация позволяет человеку использовать «символические» (отладочные) данные о двоичном файле, такие как имена переменных, процедур и функция из исходного кода. Эта информация может быть крайне полезной во время поиска ошибок в исходном коде, отладке программы и разного рода отказах.
Чтобы не включать ее в бинарный файл, разработчики выкладывают отладочную информацию в виде отдельных файлов или на специальном сервере отладочной информации. В случае браузеров ее, увы, не так много:
- для Internet Explorer (mzl.la/mC8XP5);
- для Firefox (bit.ly/jiHbQA).
При проверке отладочных символов для Safari (bit.ly/jiHbQA) выяснилось, что для ключевых модулей (Webkit.dll, JavaScriptCore.dll) отладочная информация на сервере отсутствует. Поэтому, чтобы получить ее, придется собирать WebKit самому. Исходники всегда доступны здесь — svn.webkit.org/repository/webkit, а инструкция для сборки здесь — trac.webkit.org/wiki/BuildingOnWindows. Еще один совет касается отладки Chrome. По умолчанию этот браузер создает для каждой вкладки отдельный процесс, что для нас не подходит.
Поэтому его лучше запускать с ключом «--single-process»: в этом режиме все вкладки будут запущены в единственном процессе. Что касается анализа крешей в Safari, для которого мы и написали 0dayсплоит, то тут есть еще один нюанс — подробнее о нем ты можешь прочитать во врезке.
Результаты фаззинга
Что получилось в результате нашего исследования?
Firefox третьей ветки:
Есть креши, но их не получилось воспроизвести, даже зная весь лог во время падения. Уязвимость сильно связана с состоянием динамической памяти (heap).
Firefox, ветка четыре:
Крешей не было зафиксировано.
Chrome:
Есть креши, но их не получилось воспроизвести — такая же история, как и с Firefox третьей ветки.
Safari:
Креши были, воспроизвести можно. Сплоит написан :). Браузеры — это очень сложное ПО. Поэтому нет ничего удивительного, что в них есть уязвимости. Всем известно: безопасность обратно пропорциональна объему и сложности кода. У браузера очень много векторов атаки: чего стоит один только SVG, про который в этой статье не упоминалось. Большая часть крешей, вызванных фаззером, не являются гарантией успеха и завязаны на большом числе факторов состояния кучи и многих других параметров. Из этого можно сделать вывод, что для реализации на 100% надежного сплоита нужно потратить очень много сил, времени и нервов. Тем не менее, используя данную методику, вполне можно найти уязвимости в браузере, что мы доказали, обнаружив уязвимость нулевого дня в Safari на Positive Hack Days.
Особенности анализа крешей Safari
Важный момент. Если Safari падает с исключением типа «User mode write access violations that are not near NULL are exploitable», нужно посмотреть дизасм по eip.
and dword ptr ds:0BBADBEEFh, 0
xor eax, eax
call eax
Если ты видишь что-то подобное, к сожалению, это Webkit’овский макрос CRASH(). Такие падения символизируют какую-то экстренную ситуацию, которая не должна была произойти. Но так как макрос CRASH просто так не вызывается, значит где-то произошла какая-то экстренная ситуация, которую обнаружили и поэтому прибили процесс.
Links
- Про функционал JS (DOM) в браузерах: www.webdevout.net/browser-supportecmascript.
- Про векторы атак: heideri.ch/jso.
- Про тестирование браузеров, списки компонентов, браузеры под мобильные устройства и т.д.: www.quirksmode.org.
- Про то, как устроен Sandbox в гугловском Chrome: dev.chromium.org/developers/design-documents/sandbox.